| /** | ||
| * HS post-write skill catalog: adapters, home skip/prune, Claude/Grok home skip. | ||
| * Kept out of install-migrate so that file stays inside its module budget. | ||
| */ | ||
| import fs from 'node:fs'; | ||
| import path from 'node:path'; | ||
| import { arkCommand } from '../ark-shared.mjs'; | ||
| import { installRequestedAgentHomes } from './agent-homes.mjs'; | ||
| import { codexSkillsDir } from './codex-home.mjs'; | ||
| import { | ||
| canonicalSkillPath, | ||
| linkSkillHostAdapters, | ||
| pruneHomeArkSkillDuplicates, | ||
| skillTemplateNames, | ||
| } from './skill-install.mjs'; | ||
| import { installSkillCatalog, skillInstallLine } from './skill-write.mjs'; | ||
| function skillName(skill) { | ||
| return Array.isArray(skill) ? skill[0] : skill?.name || skill; | ||
| } | ||
| export function projectCatalogReady(root, skillNames) { | ||
| return skillNames.some((name) => fs.existsSync(path.join(root, canonicalSkillPath(name)))); | ||
| } | ||
| export function applySkillCatalogFollowup({ | ||
| root, | ||
| tools, | ||
| skills, | ||
| version, | ||
| args, | ||
| }) { | ||
| const skillNames = skills.map(skillName); | ||
| if (!args.compact && skillNames.length > 0) { | ||
| const adapterResults = linkSkillHostAdapters(root, tools, skillNames, Boolean(args.force)); | ||
| for (const row of adapterResults) { | ||
| if (row.status === 'linked' || row.status === 'copied') { | ||
| console.log( | ||
| ` ${row.status.padEnd(7)} ${row.relativePath} (adapter → .agents/skills/${row.name})` | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| if (args.pruneHomeDuplicates) { | ||
| const pruned = pruneHomeArkSkillDuplicates( | ||
| root, | ||
| skillNames.length ? skillNames : skillTemplateNames() | ||
| ); | ||
| if (!pruned.ok) { | ||
| console.log(' skip --prune-home-duplicates (no project .agents/skills catalog yet)'); | ||
| } else if (pruned.removed.length === 0) { | ||
| console.log(' skip --prune-home-duplicates (no home ark-* copies)'); | ||
| } else { | ||
| console.log(` pruned ${pruned.removed.length} home ark-* path(s) (project catalog is enough)`); | ||
| } | ||
| } | ||
| const homeResults = []; | ||
| if (args.codexHome) { | ||
| const dir = codexSkillsDir(); | ||
| console.log(''); | ||
| console.log( | ||
| `Codex home skills (scope=home-shared; source=${version ? `arkgate@${version}` : 'arkgate@unknown'}; target=${dir}/<name>/SKILL.md):` | ||
| ); | ||
| console.log( | ||
| ' Compatibility: monotonic downgrade protection requires every shared-catalog writer ' + | ||
| 'to use ArkGate 4.2.0+; pre-4.2 --codex-home ignores this catalog. Upgrade legacy repos first.' | ||
| ); | ||
| try { | ||
| fs.mkdirSync(dir, { recursive: true }); | ||
| } catch (error) { | ||
| console.error(` FAILED to create ${dir} (${error.message})`); | ||
| homeResults.push({ relativePath: dir, status: 'failed' }); | ||
| } | ||
| if (homeResults.length === 0) { | ||
| const hasCatalog = skills.some((skill) => | ||
| fs.existsSync(path.join(root, '.agents', 'skills', skillName(skill), 'SKILL.md')) | ||
| ); | ||
| if (hasCatalog) { | ||
| console.log( | ||
| ' Skip home write — project .agents/skills is the catalog. Codex lists user+repo; a home copy duplicates every /ark-*.' | ||
| ); | ||
| console.log( | ||
| ` Remove leftover home copies: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --prune-home-duplicates')}` | ||
| ); | ||
| } else { | ||
| for (const result of installSkillCatalog({ | ||
| directory: dir, | ||
| skills, | ||
| packageVersion: version, | ||
| force: args.force, | ||
| scope: 'home', | ||
| })) { | ||
| console.log(skillInstallLine(result)); | ||
| homeResults.push(result); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| const catalogReady = projectCatalogReady(root, skillNames); | ||
| if ((args.claudeHome || args.grokHome || args.agentHomes) && catalogReady) { | ||
| if (!args.json) { | ||
| console.log(''); | ||
| console.log( | ||
| 'Skip Claude/Grok home skill write — project .agents/skills + adapters are the catalog. Home ark-* copies override or duplicate.' | ||
| ); | ||
| console.log( | ||
| ` Remove leftover home copies: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --prune-home-duplicates')}` | ||
| ); | ||
| } | ||
| } else { | ||
| installRequestedAgentHomes({ | ||
| root, | ||
| skills, | ||
| version, | ||
| force: args.force, | ||
| claudeHome: args.claudeHome, | ||
| grokHome: args.grokHome, | ||
| agentHomes: args.agentHomes, | ||
| json: args.json, | ||
| }); | ||
| } | ||
| return { skillNames, homeResults }; | ||
| } |
@@ -238,2 +238,31 @@ /** | ||
| /** | ||
| * Visible package stamp at the start of Agent Skills `description`. | ||
| * Hosts show `description` in the picker; `arkVersion:` in YAML is invisible there. | ||
| * Example: `arkgate@4.7.1. Session 0 — mark the Ark path.` | ||
| */ | ||
| export const ARK_SKILL_DESCRIPTION_VERSION_PATTERN = /^arkgate@(\S+)\.\s/; | ||
| /** Prefix written at install time (`arkgate@<version>. `). */ | ||
| export function skillDescriptionVersionPrefix(version) { | ||
| const v = String(version ?? '').trim(); | ||
| return v ? `arkgate@${v}. ` : ''; | ||
| } | ||
| /** Drop a leading `arkgate@<version>. ` stamp; other text is unchanged. */ | ||
| export function stripSkillDescriptionVersion(description) { | ||
| return String(description ?? '').replace(ARK_SKILL_DESCRIPTION_VERSION_PATTERN, ''); | ||
| } | ||
| /** Version inside a stamped description, or null when the prefix is absent. */ | ||
| export function parseSkillDescriptionVersion(description) { | ||
| const match = String(description ?? '').match(ARK_SKILL_DESCRIPTION_VERSION_PATTERN); | ||
| return match?.[1] ?? null; | ||
| } | ||
| /** | ||
| * Idempotent: replace an existing `arkgate@…` prefix or add one. | ||
| * Empty `version` strips the prefix (authoring templates stay unversioned). | ||
| */ | ||
| export function stampSkillDescription(description, version) { | ||
| const rest = stripSkillDescriptionVersion(description); | ||
| const v = typeof version === 'string' ? version.trim() : ''; | ||
| return v ? `${skillDescriptionVersionPrefix(v)}${rest}` : rest; | ||
| } | ||
| /** | ||
| * Normalize skill file content for identity compare (LF newlines, strip BOM). | ||
@@ -240,0 +269,0 @@ * Does not strip or rewrite frontmatter — Agent Skills export is 1:1 with flat templates. |
@@ -127,2 +127,3 @@ /** | ||
| else if (arg === '--codex-home') args.codexHome = true; | ||
| else if (arg === '--prune-home-duplicates') args.pruneHomeDuplicates = true; | ||
| else if (arg === '--claude-home') args.claudeHome = true; | ||
@@ -129,0 +130,0 @@ else if (arg === '--grok-home') args.grokHome = true; |
@@ -76,4 +76,8 @@ /** | ||
| } | ||
| if (ctx.codexHomeGap && ctx.codexConcernActive && ctx.codexHomeGap.preferProject !== true) { | ||
| if (ctx.codexHomeGap && ctx.codexConcernActive && ctx.codexHomeGap.duplicateHome) { | ||
| actions.push( | ||
| 'remove duplicate Codex home /ark-* skills (project .agents/skills is enough): --install-agent-gates --skills-only --prune-home-duplicates' | ||
| ); | ||
| } else if (ctx.codexHomeGap && ctx.codexConcernActive && ctx.codexHomeGap.preferProject !== true) { | ||
| actions.push( | ||
| ctx.codexHomeGap.catalogMetadataInvalid | ||
@@ -80,0 +84,0 @@ ? 'repair invalid Codex home catalog metadata after verifying the newest installed version' |
@@ -203,4 +203,4 @@ /** | ||
| 'It also installs the /ark-* skills shipped in templates/skills/ into each', | ||
| 'detected tool\'s command location (.claude/skills/, .cursor/commands/,', | ||
| '.agents/skills/ (Codex REPO catalog), .grok/skills/, .windsurf/workflows/,', | ||
| 'detected tool\'s command location (.agents/skills/ canonical catalog;', | ||
| '.claude/skills/ and .grok/skills/ adapters; .windsurf/workflows/,', | ||
| '.clinerules/workflows/, .github/prompts/).', | ||
@@ -207,0 +207,0 @@ 'Kiro, Roo, Continue, and Gemini have no command mechanism and receive only their', |
@@ -71,4 +71,7 @@ /** | ||
| verifyHostSkillCatalog, | ||
| canonicalSkillPath, | ||
| usesCanonicalSkillCatalog, | ||
| } from './skill-install.mjs'; | ||
| import { installRepoSkillFile, installSkillCatalog, skillInstallLine, skillInstallNote } from './skill-write.mjs'; | ||
| import { applySkillCatalogFollowup } from './skill-catalog-apply.mjs'; | ||
| import { installRepoSkillFile, skillInstallNote } from './skill-write.mjs'; | ||
| import { detectDeployPathQuality } from './deploy-path.mjs'; | ||
@@ -84,3 +87,2 @@ import { | ||
| import { inspectCodexInstallActivation, printCodexActivationHandoff, reportPartialInstall } from './install-activation.mjs'; | ||
| import { installRequestedAgentHomes } from './agent-homes.mjs'; | ||
| import { | ||
@@ -240,5 +242,14 @@ hasHardWriteHook, | ||
| if (!compact) { | ||
| for (const tool of selectedTools) { | ||
| const skillTools = [...selectedTools].filter((tool) => SKILL_TOOL_TARGETS[tool]); | ||
| const writeCanonical = skillTools.some((tool) => usesCanonicalSkillCatalog(tool)); | ||
| if (writeCanonical) { | ||
| for (const [name, content] of skills) { | ||
| const relativePath = canonicalSkillPath(name); | ||
| skillPaths.add(relativePath); | ||
| add(relativePath, content, 'skill'); | ||
| } | ||
| } | ||
| for (const tool of skillTools) { | ||
| if (usesCanonicalSkillCatalog(tool)) continue; | ||
| const target = SKILL_TOOL_TARGETS[tool]; | ||
| if (!target) continue; | ||
| for (const [name, content] of skills) { | ||
@@ -619,56 +630,8 @@ const relativePath = target(name); | ||
| // --codex-home writes SKILL.md skills to $CODEX_HOME/skills/<name>/SKILL.md. | ||
| // Codex's real catalog loads skill directories (not flat $CODEX_HOME/prompts). | ||
| // Repo installs already write `.agents/skills/<name>/SKILL.md` when `codex` is | ||
| // selected; home install is for multi-project / non-repo-local refresh. | ||
| const homeResults = []; | ||
| if (args.codexHome) { | ||
| const dir = codexSkillsDir(); | ||
| console.log(''); | ||
| console.log( | ||
| `Codex home skills (scope=home-shared; source=${version ? `arkgate@${version}` : 'arkgate@unknown'}; target=${dir}/<name>/SKILL.md):` | ||
| ); | ||
| console.log( | ||
| ' Compatibility: monotonic downgrade protection requires every shared-catalog writer ' + | ||
| 'to use ArkGate 4.2.0+; pre-4.2 --codex-home ignores this catalog. Upgrade legacy repos first.' | ||
| ); | ||
| try { | ||
| fs.mkdirSync(dir, { recursive: true }); | ||
| } catch (error) { | ||
| console.error(` FAILED to create ${dir} (${error.message})`); | ||
| homeResults.push({ relativePath: dir, status: 'failed' }); | ||
| } | ||
| if (homeResults.length === 0) { | ||
| const skillName = (skill) => | ||
| Array.isArray(skill) ? skill[0] : skill?.name || skill; | ||
| const projectHasCatalog = skills.some((skill) => | ||
| fs.existsSync(path.join(root, '.agents', 'skills', skillName(skill), 'SKILL.md')) | ||
| ); | ||
| if (projectHasCatalog) { | ||
| console.log( | ||
| ' Project .agents/skills already has this catalog; home write is optional. Prefer the project copy.' | ||
| ); | ||
| } | ||
| for (const result of installSkillCatalog({ | ||
| directory: dir, | ||
| skills, | ||
| packageVersion: version, | ||
| force: args.force, | ||
| scope: 'home', | ||
| })) { | ||
| console.log(skillInstallLine(result)); | ||
| homeResults.push(result); | ||
| } | ||
| } | ||
| } | ||
| installRequestedAgentHomes({ | ||
| const { skillNames, homeResults } = applySkillCatalogFollowup({ | ||
| root, | ||
| tools, | ||
| skills, | ||
| version, | ||
| force: args.force, | ||
| claudeHome: args.claudeHome, | ||
| grokHome: args.grokHome, | ||
| agentHomes: args.agentHomes, | ||
| json: args.json, | ||
| args, | ||
| }); | ||
@@ -686,4 +649,6 @@ | ||
| const wantCodexWire = !args.compact && !args.skillsOnly && args.codexHome; | ||
| const projectCodexMcp = fs.existsSync(path.join(root, '.codex', 'config.toml')); | ||
| const skipHomeWire = | ||
| wantCodexWire && isTempOrUpgradeRoot(root) && usesDefaultCodexHome(); | ||
| wantCodexWire && | ||
| ((isTempOrUpgradeRoot(root) && usesDefaultCodexHome()) || projectCodexMcp); | ||
| if (wantCodexWire && !skipHomeWire) { | ||
@@ -710,3 +675,10 @@ codexMcp = wireCodexMcp(root, args.force); | ||
| } else if (skipHomeWire) { | ||
| codexMcp = { status: 'skipped', file: codexConfigPath(), reason: 'temp-root' }; | ||
| const reason = projectCodexMcp ? 'project-config' : 'temp-root'; | ||
| codexMcp = { status: 'skipped', file: codexConfigPath(), reason }; | ||
| if (projectCodexMcp && !args.json) { | ||
| console.log(''); | ||
| console.log( | ||
| 'Skip Codex home MCP — project .codex/config.toml is the binding. Home config pointing at another checkout is leftover; do not rebind it from this install.' | ||
| ); | ||
| } | ||
| } | ||
@@ -713,0 +685,0 @@ |
@@ -129,9 +129,4 @@ import { createHash } from 'node:crypto'; | ||
| claude: ['.claude/settings.json', '.claude/skills/ark-upgrade/SKILL.md'], | ||
| cursor: [ | ||
| '.cursor/mcp.json', | ||
| '.cursor/hooks.json', | ||
| '.cursor/rules/ark.mdc', | ||
| '.cursor/commands/ark-upgrade.md', | ||
| ], | ||
| codex: ['.codex/hooks.json', '.codex/config.toml', '.agents/skills/ark-upgrade/SKILL.md'], | ||
| cursor: ['.cursor/mcp.json', '.cursor/hooks.json', '.cursor/rules/ark.mdc'], | ||
| codex: ['.codex/hooks.json', '.codex/config.toml'], | ||
| grok: ['.grok/config.toml', '.grok/hooks/ark-write-gate.json', '.grok/skills/ark-upgrade/SKILL.md'], | ||
@@ -138,0 +133,0 @@ antigravity: ['.agents/hooks.json'], |
+264
-33
@@ -6,4 +6,10 @@ /** | ||
| import fs from 'node:fs'; | ||
| import os from 'node:os'; | ||
| import path from 'node:path'; | ||
| import { arkCommand } from '../ark-shared.mjs'; | ||
| import { | ||
| parseSkillDescriptionVersion, | ||
| stampSkillDescription, | ||
| stripSkillDescriptionVersion, | ||
| } from './agent-skills-package.mjs'; | ||
| import { codexPromptsDir, codexSkillsDir } from './codex-home.mjs'; | ||
@@ -198,12 +204,43 @@ import { __packageRoot, isCompactRouterAgentsContent, readJson } from './gate-files.mjs'; | ||
| // Home install uses `$CODEX_HOME/skills/<name>/SKILL.md` via --codex-home. | ||
| /** Project-canonical Agent Skills catalog (Codex, Cursor, Antigravity all read this). */ | ||
| export const SKILL_CANONICAL_DIR = '.agents/skills'; | ||
| export function canonicalSkillPath(name) { | ||
| return `${SKILL_CANONICAL_DIR}/${name}/SKILL.md`; | ||
| } | ||
| /** | ||
| * Hosts that natively load `.agents/skills` — do not also copy bytes there under | ||
| * a second name. Cursor/Codex list every path they scan; two copies = two picker rows. | ||
| */ | ||
| export const SKILL_NATIVE_AGENTS_HOSTS = Object.freeze(['codex', 'cursor', 'antigravity']); | ||
| /** | ||
| * Hosts that do not scan `.agents/skills`. Adapter is a relative symlink to the | ||
| * canonical catalog so Grok/Claude/OpenCode see the same bytes. | ||
| * Cursor also scans `.claude/skills` — doctor warns; still one body + visible version. | ||
| */ | ||
| export const SKILL_ADAPTER_LINKS = Object.freeze({ | ||
| claude: (name) => ({ | ||
| link: `.claude/skills/${name}`, | ||
| target: `../../${SKILL_CANONICAL_DIR}/${name}`, | ||
| }), | ||
| grok: (name) => ({ | ||
| link: `.grok/skills/${name}`, | ||
| target: `../../${SKILL_CANONICAL_DIR}/${name}`, | ||
| }), | ||
| opencode: (name) => ({ | ||
| link: `.opencode/skills/${name}`, | ||
| target: `../../${SKILL_CANONICAL_DIR}/${name}`, | ||
| }), | ||
| }); | ||
| export const SKILL_TOOL_TARGETS = { | ||
| claude: (name) => `.claude/skills/${name}/SKILL.md`, | ||
| cursor: (name) => `.cursor/commands/${name}.md`, | ||
| // Cursor 2026 Agent Skills: `.agents/skills` (not a second `.cursor/commands` copy). | ||
| cursor: (name) => canonicalSkillPath(name), | ||
| // Official Codex REPO skill scope (Agent Skills standard). | ||
| codex: (name) => `.agents/skills/${name}/SKILL.md`, | ||
| // Grok Build: project skills at .grok/skills/<name>/SKILL.md (slash-invocable). | ||
| codex: (name) => canonicalSkillPath(name), | ||
| grok: (name) => `.grok/skills/${name}/SKILL.md`, | ||
| // Antigravity loads Agent Skills from `.agents/skills` (shared path with Codex). | ||
| antigravity: (name) => `.agents/skills/${name}/SKILL.md`, | ||
| // OpenCode project skills under `.opencode/skills`. | ||
| antigravity: (name) => canonicalSkillPath(name), | ||
| opencode: (name) => `.opencode/skills/${name}/SKILL.md`, | ||
@@ -215,2 +252,10 @@ windsurf: (name) => `.windsurf/workflows/${name}.md`, | ||
| /** Hosts whose catalog is the project `.agents/skills` tree (write once). */ | ||
| export function usesCanonicalSkillCatalog(tool) { | ||
| return ( | ||
| SKILL_NATIVE_AGENTS_HOSTS.includes(tool) || | ||
| Object.prototype.hasOwnProperty.call(SKILL_ADAPTER_LINKS, tool) | ||
| ); | ||
| } | ||
| // The version of the arkgate package these bins ship with. Used to | ||
@@ -229,4 +274,39 @@ // stamp installed skills so a normal ark-check can tell "outdated skill from an | ||
| // Insert `arkVersion: <v>` into a skill's YAML frontmatter (before its closing | ||
| // `---`). No frontmatter → returned unchanged. Idempotent for a given version | ||
| /** | ||
| * Rewrite a YAML `description:` line with a visible `arkgate@<version>. ` prefix. | ||
| * Preserves quoting. Hosts show this string in the skill picker (unlike arkVersion). | ||
| */ | ||
| function stampDescriptionYamlLine(line, version) { | ||
| const match = String(line).match(/^(description:\s*)(.*)$/); | ||
| if (!match) return line; | ||
| let raw = match[2] ?? ''; | ||
| const quoted = | ||
| (raw.startsWith('"') && raw.endsWith('"') && raw.length >= 2) || | ||
| (raw.startsWith("'") && raw.endsWith("'") && raw.length >= 2); | ||
| const quote = quoted ? raw[0] : ''; | ||
| const value = quoted ? raw.slice(1, -1) : raw; | ||
| const stamped = stampSkillDescription(value, version); | ||
| if (!quoted) return `${match[1]}${stamped}`; | ||
| const escaped = stamped.replaceAll('\\', '\\\\').replaceAll(quote, `\\${quote}`); | ||
| return `${match[1]}${quote}${escaped}${quote}`; | ||
| } | ||
| function managedDescriptionYamlLine(line) { | ||
| const match = String(line).match(/^(description:\s*)(.*)$/); | ||
| if (!match) return line; | ||
| let raw = match[2] ?? ''; | ||
| const quoted = | ||
| (raw.startsWith('"') && raw.endsWith('"') && raw.length >= 2) || | ||
| (raw.startsWith("'") && raw.endsWith("'") && raw.length >= 2); | ||
| const quote = quoted ? raw[0] : ''; | ||
| const value = quoted ? raw.slice(1, -1) : raw; | ||
| const stripped = stripSkillDescriptionVersion(value); | ||
| if (stripped === value) return line; | ||
| if (!quoted) return `${match[1]}${stripped}`; | ||
| const escaped = stripped.replaceAll('\\', '\\\\').replaceAll(quote, `\\${quote}`); | ||
| return `${match[1]}${quote}${escaped}${quote}`; | ||
| } | ||
| // Insert `arkVersion: <v>` and a visible `arkgate@<v>. ` description prefix. | ||
| // No frontmatter → returned unchanged. Idempotent for a given version | ||
| // and preserves the checked-out line ending on Windows. | ||
@@ -248,2 +328,8 @@ export function stampSkill(content, version) { | ||
| } | ||
| const descIdx = lines.findIndex( | ||
| (line, i) => i > 0 && i < lines.indexOf('---', 1) && /^description:\s*/.test(line) | ||
| ); | ||
| if (descIdx !== -1) { | ||
| lines[descIdx] = stampDescriptionYamlLine(lines[descIdx], version); | ||
| } | ||
| return lines.join(newline); | ||
@@ -344,2 +430,5 @@ } | ||
| if (/^arkVersion:/.test(lines[index])) lines[index] = 'arkVersion:<managed>'; | ||
| else if (/^description:\s*/.test(lines[index])) { | ||
| lines[index] = managedDescriptionYamlLine(lines[index]); | ||
| } | ||
| } | ||
@@ -388,3 +477,3 @@ text = lines.join('\n'); | ||
| * action: 'write'|'skip', | ||
| * reason: 'missing'|'content-current'|'newer-home-version'|'unknown-source-version'|'existing-preserved'|'content-update', | ||
| * reason: 'missing'|'content-current'|'stamp-refresh'|'newer-home-version'|'unknown-source-version'|'existing-preserved'|'content-update', | ||
| * scope: 'repo'|'home', | ||
@@ -415,9 +504,5 @@ * sourceVersion: string|null, | ||
| if (existingContent === null) return result('write', 'missing'); | ||
| if ( | ||
| existingContent === targetContent || | ||
| skillContentIdentity(existingContent) === skillContentIdentity(targetContent) | ||
| ) { | ||
| if (existingContent === targetContent) { | ||
| return result('skip', 'content-current'); | ||
| } | ||
| if (scope === 'home') { | ||
@@ -435,2 +520,7 @@ if (installedVersion && !sourceVersion) { | ||
| } | ||
| if (skillContentIdentity(existingContent) === skillContentIdentity(targetContent)) { | ||
| // Body matches; only arkVersion / visible description prefix drifted. | ||
| // Refresh the stamp without --force so the picker shows arkgate@this-package. | ||
| return result('write', 'stamp-refresh'); | ||
| } | ||
@@ -479,2 +569,109 @@ if (!input.force) return result('skip', 'existing-preserved', true); | ||
| /** | ||
| * Point a host-native skills dir at the project canonical catalog. | ||
| * Relative symlink so clones keep working. Fallback copy when the OS refuses links. | ||
| * @returns {'linked'|'copied'|'current'|'skipped-customized'|'missing-canonical'} | ||
| */ | ||
| export function ensureSkillAdapterLink(root, name, adapter, force = false) { | ||
| const canonicalDir = path.join(root, SKILL_CANONICAL_DIR, name); | ||
| const canonicalFile = path.join(canonicalDir, 'SKILL.md'); | ||
| if (!fs.existsSync(canonicalFile)) return 'missing-canonical'; | ||
| const linkPath = path.join(root, adapter.link); | ||
| fs.mkdirSync(path.dirname(linkPath), { recursive: true }); | ||
| const existing = fs.lstatSync(linkPath, { throwIfNoEntry: false }); | ||
| if (existing?.isSymbolicLink()) { | ||
| const current = fs.readlinkSync(linkPath).replaceAll('\\', '/'); | ||
| if (current === adapter.target) return 'current'; | ||
| fs.unlinkSync(linkPath); | ||
| } else if (existing) { | ||
| const adapterFile = path.join(linkPath, 'SKILL.md'); | ||
| let adapterContent = null; | ||
| try { | ||
| adapterContent = fs.readFileSync(adapterFile, 'utf8'); | ||
| } catch { | ||
| adapterContent = null; | ||
| } | ||
| const canonicalContent = fs.readFileSync(canonicalFile, 'utf8'); | ||
| if ( | ||
| !force && | ||
| adapterContent && | ||
| skillContentIdentity(adapterContent) !== skillContentIdentity(canonicalContent) | ||
| ) { | ||
| return 'skipped-customized'; | ||
| } | ||
| fs.rmSync(linkPath, { recursive: true, force: true }); | ||
| } | ||
| try { | ||
| fs.symlinkSync(adapter.target, linkPath); | ||
| return 'linked'; | ||
| } catch { | ||
| fs.cpSync(canonicalDir, linkPath, { recursive: true }); | ||
| return 'copied'; | ||
| } | ||
| } | ||
| export function linkSkillHostAdapters(root, tools, skillNames, force = false) { | ||
| const results = []; | ||
| for (const tool of tools) { | ||
| const adapterFor = SKILL_ADAPTER_LINKS[tool]; | ||
| if (!adapterFor) continue; | ||
| for (const name of skillNames) { | ||
| const adapter = adapterFor(name); | ||
| results.push({ | ||
| tool, | ||
| name, | ||
| status: ensureSkillAdapterLink(root, name, adapter, force), | ||
| relativePath: `${adapter.link}/SKILL.md`, | ||
| }); | ||
| } | ||
| } | ||
| return results; | ||
| } | ||
| const HOME_ARK_SKILL_ROOTS = [ | ||
| () => path.join(codexSkillsDir()), | ||
| () => path.join(os.homedir(), '.claude', 'skills'), | ||
| () => path.join(os.homedir(), '.grok', 'skills'), | ||
| ]; | ||
| function projectHasCanonicalCatalog(root, skillNames) { | ||
| return skillNames.some((name) => | ||
| fs.existsSync(path.join(root, canonicalSkillPath(name))) | ||
| ); | ||
| } | ||
| /** | ||
| * Remove frozen `/ark-*` directories from agent home catalogs when the project | ||
| * already has `.agents/skills`. Codex/Cursor list user+repo; same name twice. | ||
| * Never deletes non-Ark skills. | ||
| */ | ||
| export function pruneHomeArkSkillDuplicates(root, skillNames = skillTemplateNames()) { | ||
| const names = skillNames.length ? skillNames : skillTemplateNames(); | ||
| const removed = []; | ||
| if (!projectHasCanonicalCatalog(root, names)) { | ||
| return { ok: false, reason: 'no-project-catalog', removed }; | ||
| } | ||
| for (const dirFn of HOME_ARK_SKILL_ROOTS) { | ||
| const dir = dirFn(); | ||
| for (const name of names) { | ||
| const skillDir = path.join(dir, name); | ||
| const stat = fs.lstatSync(skillDir, { throwIfNoEntry: false }); | ||
| if (!stat) continue; | ||
| fs.rmSync(skillDir, { recursive: true, force: true }); | ||
| removed.push(skillDir); | ||
| } | ||
| const catalog = path.join(dir, '.arkgate-catalog.json'); | ||
| const pending = path.join(dir, '.arkgate-catalog.pending.json'); | ||
| for (const meta of [catalog, pending]) { | ||
| if (fs.existsSync(meta)) { | ||
| fs.rmSync(meta, { force: true }); | ||
| removed.push(meta); | ||
| } | ||
| } | ||
| } | ||
| return { ok: true, reason: 'pruned', removed }; | ||
| } | ||
| export { parseSkillDescriptionVersion, stripSkillDescriptionVersion }; | ||
| // Skill names only, silent on a missing templates dir — for the freshness | ||
@@ -745,4 +942,26 @@ // advisory below, which must not print packaging warnings on every check run. | ||
| const parity = assessCodexSkillParity(root); | ||
| if (!parity || !parity.homeNeedsAttention) return null; | ||
| const { home, packageVersion, expectedCount, skillsDir } = parity; | ||
| if (!parity) return null; | ||
| const { home, packageVersion, expectedCount, skillsDir, repo } = parity; | ||
| const repoComplete = | ||
| Boolean(repo?.inPlay) && repo.missing === 0 && !repo.legacyPromptsOnly; | ||
| const homePresent = Boolean(home?.inPlay) && home.presentCount > 0; | ||
| if (repoComplete && homePresent) { | ||
| return { | ||
| missing: 0, | ||
| stale: 0, | ||
| legacyPromptsOnly: false, | ||
| hasLegacyPrompts: Boolean(home.hasLegacyPrompts), | ||
| presentCount: home.presentCount, | ||
| expectedCount, | ||
| packageVersion, | ||
| skillsDir, | ||
| catalogVersion: home.catalogVersion, | ||
| pendingRecoveryRequired: false, | ||
| catalogMetadataInvalid: false, | ||
| catalogStateReason: null, | ||
| preferProject: true, | ||
| duplicateHome: true, | ||
| }; | ||
| } | ||
| if (!parity.homeNeedsAttention) return null; | ||
| return { | ||
@@ -765,2 +984,4 @@ missing: home.missing, | ||
| : null, | ||
| preferProject: false, | ||
| duplicateHome: false, | ||
| }; | ||
@@ -986,20 +1207,30 @@ } | ||
| if (codexHomeGap) { | ||
| const parts = []; | ||
| if (codexHomeGap.legacyPromptsOnly) parts.push('legacy-prompts-only'); | ||
| if (codexHomeGap.missing > 0) parts.push(`${codexHomeGap.missing} missing`); | ||
| if (codexHomeGap.stale > 0) parts.push(`${codexHomeGap.stale} content-behind-package`); | ||
| if (codexHomeGap.pendingRecoveryRequired) parts.push('interrupted catalog commit'); | ||
| if (codexHomeGap.catalogMetadataInvalid) parts.push('invalid catalog metadata'); | ||
| const deferred = !codexSessionActive; | ||
| const deferredNote = deferred | ||
| ? ' Deferred unless you use Codex — not a blocker for Grok/Claude/Cursor. ' | ||
| : ' '; | ||
| const msg = | ||
| `Codex home skill catalog (${codexSkillsDir()}) behind this Ark (${parts.join(', ')}).` + | ||
| deferredNote + | ||
| `Catalog is $CODEX_HOME/skills/<name>/SKILL.md (not flat prompts). ` + | ||
| (codexHomeGap.catalogMetadataInvalid | ||
| ? 'Inspect the shared catalog metadata before retrying; invalid metadata fails safe.' | ||
| : `When using Codex: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --codex-home --force')}`); | ||
| console.log(deferred ? color.dim(msg) : color.yellow(msg)); | ||
| if (codexHomeGap.duplicateHome) { | ||
| const msg = | ||
| `Codex home $CODEX_HOME/skills/ark-* duplicates project .agents/skills (picker shows two copies). ` + | ||
| (deferred | ||
| ? 'Deferred unless you use Codex. ' | ||
| : '') + | ||
| `Remove home copies: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --prune-home-duplicates')}`; | ||
| console.log(deferred ? color.dim(msg) : color.yellow(msg)); | ||
| } else { | ||
| const parts = []; | ||
| if (codexHomeGap.legacyPromptsOnly) parts.push('legacy-prompts-only'); | ||
| if (codexHomeGap.missing > 0) parts.push(`${codexHomeGap.missing} missing`); | ||
| if (codexHomeGap.stale > 0) parts.push(`${codexHomeGap.stale} content-behind-package`); | ||
| if (codexHomeGap.pendingRecoveryRequired) parts.push('interrupted catalog commit'); | ||
| if (codexHomeGap.catalogMetadataInvalid) parts.push('invalid catalog metadata'); | ||
| const deferredNote = deferred | ||
| ? ' Deferred unless you use Codex — not a blocker for Grok/Claude/Cursor. ' | ||
| : ' '; | ||
| const msg = | ||
| `Codex home skill catalog (${codexSkillsDir()}) behind this Ark (${parts.join(', ')}).` + | ||
| deferredNote + | ||
| `Catalog is $CODEX_HOME/skills/<name>/SKILL.md (not flat prompts). ` + | ||
| (codexHomeGap.catalogMetadataInvalid | ||
| ? 'Inspect the shared catalog metadata before retrying; invalid metadata fails safe.' | ||
| : `When using Codex: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --codex-home --force')}`); | ||
| console.log(deferred ? color.dim(msg) : color.yellow(msg)); | ||
| } | ||
| } | ||
@@ -1006,0 +1237,0 @@ if (codexRepoSkillGap && codexSessionActive) { |
@@ -396,2 +396,5 @@ /** | ||
| } | ||
| if (plan.reason === 'stamp-refresh') { | ||
| return `scope=${scope}; stamp refresh; installed=${installed}; source=${source}`; | ||
| } | ||
| if (plan.reason === 'newer-home-version') { | ||
@@ -398,0 +401,0 @@ return `scope=${scope}; CONFLICT installed=${installed} newer than source=${source}; downgrade blocked`; |
+33
-2
@@ -8,2 +8,34 @@ # Changelog | ||
| ## 4.7.1 — 2026-08-25 | ||
| **Patch** over **4.7.0**. One project skill catalog, visible package version in the | ||
| skill picker, no home duplicates, ArkRun routed through existing skill names. | ||
| **No required config migration.** Does not close Z09 / K01. | ||
| **Status: unpublished** (implementation on `main`; npm `latest` remains **4.7.0** until publish). | ||
| ### Added | ||
| - **Visible skill version (picker):** install stamps `description` with | ||
| `arkgate@<version>. ` so Codex/Claude/Cursor/Grok show the package pin without | ||
| opening the file. `arkVersion:` in YAML stays for doctor. Same-body stamp drift | ||
| refreshes without `--force` (`stamp-refresh`). | ||
| - **`--prune-home-duplicates`:** removes frozen `/ark-*` copies from | ||
| `$CODEX_HOME/skills`, `~/.claude/skills`, and `~/.grok/skills` when the project | ||
| already has `.agents/skills`. Never deletes non-Ark skills. | ||
| ### Changed | ||
| - **One project catalog:** `.agents/skills/<name>/SKILL.md` is the byte source. | ||
| Claude / Grok / OpenCode get relative adapter links. Cursor/Codex/Antigravity | ||
| already read `.agents/skills` — no second copy. `.cursor/commands/ark-*.md` is | ||
| no longer written (Cursor listed commands + skills as two copies). | ||
| - **`--codex-home` / `--agent-homes`:** skip home skill write (and home MCP bind) | ||
| when the project catalog or `.codex/config.toml` already exists. Codex lists | ||
| user+repo; a home copy is why `/ark-*` appeared twice and stayed old. | ||
| - **Doctor:** when home `ark-*` and project `.agents/skills` both exist, next | ||
| action is prune, not `--codex-home --force`. | ||
| - **`/ark-contract`:** routes ArkRun extra edits (first extra `/ark-adopt`, | ||
| companion `/ark-runtime`, new files `/ark-place`). No new skill names. | ||
| ## 4.7.0 — 2026-08-25 | ||
@@ -16,4 +48,3 @@ | ||
| **Status: prepared** (see `docs/releases/4.7.0.md`). npm `latest` remains **4.6.7** | ||
| until publish. | ||
| **Status: published** (on npm `latest`; see `docs/releases/4.7.0.md`). | ||
@@ -20,0 +51,0 @@ ### Added |
+2
-2
@@ -452,3 +452,3 @@ # Gating AI Agents with ArkGate | ||
| | Doctor: primary points at another permanent project | gap id `codex-home-multi-project` (warn if no secondary yet and session host is unknown/Codex; **info + `deferred`** when the session host is known and not Codex — e.g. Grok/Claude/Cursor; info if a scoped secondary is already present) | | ||
| | When using Codex: refresh home skills | `ark-check --install-agent-gates --skills-only --codex-home --force` | | ||
| | When using Codex: refresh home skills | Prefer project `.agents/skills`. If Codex lists `/ark-*` twice, prune home copies: `ark-check --install-agent-gates --skills-only --prune-home-duplicates`. `--codex-home` skips when the project catalog exists. | | ||
@@ -492,3 +492,3 @@ When a valid project `.codex/config.toml` exists, it is the expected binding, but files alone | ||
| | **Repo** (written by `--tools codex`) | `.agents/skills/<name>/SKILL.md` | | ||
| | **Home** (optional `--codex-home`) | `$CODEX_HOME/skills/<name>/SKILL.md` | | ||
| | **Home** (optional `--codex-home`) | `$CODEX_HOME/skills/<name>/SKILL.md` — skipped when the project catalog exists; Codex lists both otherwise | | ||
@@ -495,0 +495,0 @@ Flat `.codex/prompts/*.md` files are **not** the invocable skill catalog. Install writes the |
@@ -24,3 +24,3 @@ # How to install agent gates | ||
| | Claude Code | `.claude/settings.json`, `.claude/skills/` | | ||
| | Cursor | `.cursor/mcp.json`, `.cursor/hooks.json`, `.cursor/rules/ark.mdc`, `.cursor/commands/` | | ||
| | Cursor | `.cursor/mcp.json`, `.cursor/hooks.json`, `.cursor/rules/ark.mdc`, `.agents/skills/` | | ||
| | Codex | `.codex/hooks.json`, `.codex/config.toml`, `.agents/skills/` | | ||
@@ -27,0 +27,0 @@ | **Grok Build** | `.grok/config.toml`, `.grok/hooks/`, `.grok/skills/` | |
@@ -216,4 +216,4 @@ # ArkGate package surface policy | ||
| Ship notes for a version live under [releases/](https://github.com/pedroknigge/arkgate/tree/main/docs/releases) | ||
| (current tree: [4.7.0.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.7.0.md); | ||
| current published: [4.6.7.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.6.7.md); | ||
| (current published: [4.7.0.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.7.0.md); | ||
| prior published: [4.6.7.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.6.7.md); | ||
| prior published: [4.6.6.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.6.6.md); | ||
@@ -220,0 +220,0 @@ prior published: [4.6.5.md](https://github.com/pedroknigge/arkgate/blob/main/docs/releases/4.6.5.md); |
+3
-4
@@ -56,3 +56,3 @@ # ArkGate documentation | ||
| | Release notes (by version) | [releases/](releases/) · npm [CHANGELOG.md](../CHANGELOG.md) (Unreleased + 4.6.x) · [pre-4.6 archive](archive/CHANGELOG-pre-4.6.md) | | ||
| | Epic plans | [plans/](plans/) — maintainer seeds, not required to use the package. Live: [alive-in-six-months](plans/alive-in-six-months/README.md) (`AL01`–`AL04` done; `AL05` parked). [arkrun](plans/arkrun/README.md) (Phase RN; `RN01`–`RN15` done; `RN16` preparing **4.7.0**; ADRs [0020](adr/0020-arkrun-gated-extra-plane.md)–[0024](adr/0024-arkrun-transport-ports.md) accepted). | | ||
| | Epic plans | [plans/](plans/) — maintainer seeds, not required to use the package. Live: [alive-in-six-months](plans/alive-in-six-months/README.md) (`AL01`–`AL04` done; `AL05` parked). [arkrun](plans/arkrun/README.md) (Phase RN; `RN01`–`RN16` done; shipped **4.7.0**; ADRs [0020](adr/0020-arkrun-gated-extra-plane.md)–[0024](adr/0024-arkrun-transport-ports.md) accepted). | | ||
| | Claims audit | [audit/claims-matrix.md](audit/claims-matrix.md) | | ||
@@ -62,5 +62,4 @@ | Field adoption kit (scaffolding, not closed) | [field/](field/) | | ||
| Current tree: [releases/4.7.0.md](releases/4.7.0.md) (`arkgate@4.7.0` prepared). | ||
| Current published: [releases/4.6.7.md](releases/4.6.7.md) (`arkgate@4.6.7` on npm `latest`). | ||
| Prior: [releases/4.6.6.md](releases/4.6.6.md) · [4.6.5](releases/4.6.5.md) · [4.6.4](releases/4.6.4.md) · [4.6.3](releases/4.6.3.md) · [4.6.2](releases/4.6.2.md) · [4.6.1](releases/4.6.1.md) · [4.6.0](releases/4.6.0.md). | ||
| Current published: [releases/4.7.0.md](releases/4.7.0.md) (`arkgate@4.7.0` on npm `latest`). | ||
| Prior: [releases/4.6.7.md](releases/4.6.7.md) · [4.6.6](releases/4.6.6.md) · [4.6.5](releases/4.6.5.md) · [4.6.4](releases/4.6.4.md) · [4.6.3](releases/4.6.3.md) · [4.6.2](releases/4.6.2.md) · [4.6.1](releases/4.6.1.md) · [4.6.0](releases/4.6.0.md). | ||
| Older notes: [releases/](releases/). Config: [configuration.md](configuration.md). | ||
@@ -67,0 +66,0 @@ |
+1
-1
| { | ||
| "name": "arkgate", | ||
| "version": "4.7.0", | ||
| "version": "4.7.1", | ||
| "description": "One architecture config. One check. One coach.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+3
-3
@@ -19,3 +19,3 @@ <div align="center"> | ||
| > **ArkGate 4.7.0** is prepared on this tree. **4.6.7** remains npm `latest` until publish. | ||
| > **ArkGate 4.7.0** is on npm `latest`. Optional **ArkRun** extra on schema `1.2`. | ||
| > A tree is **adopted** only with a required GitHub status running `arkgate-check --strict-merge`, | ||
@@ -231,4 +231,4 @@ > or `.ark/adoption-stance.json` `stance: "advisory-only"`. Doctor is compact (`--doctor --all` | ||
| | Security | [SECURITY.md](SECURITY.md) | | ||
| | Current tree (4.7.0 prepared) | [docs/releases/4.7.0.md](docs/releases/4.7.0.md) · [CHANGELOG](CHANGELOG.md) | | ||
| | Current published (4.6.7 on npm `latest`) | [docs/releases/4.6.7.md](docs/releases/4.6.7.md) | | ||
| | Current published (4.7.0 on npm `latest`) | [docs/releases/4.7.0.md](docs/releases/4.7.0.md) · [CHANGELOG](CHANGELOG.md) | | ||
| | Prior published (4.6.7) | [docs/releases/4.6.7.md](docs/releases/4.6.7.md) | | ||
| | Prior published (4.6.6) | [docs/releases/4.6.6.md](docs/releases/4.6.6.md) | | ||
@@ -235,0 +235,0 @@ | Prior published (4.6.5) | [docs/releases/4.6.5.md](docs/releases/4.6.5.md) | |
+2
-2
@@ -9,3 +9,3 @@ { | ||
| }, | ||
| "version": "4.7.0", | ||
| "version": "4.7.1", | ||
| "packages": [ | ||
@@ -15,3 +15,3 @@ { | ||
| "identifier": "arkgate", | ||
| "version": "4.7.0", | ||
| "version": "4.7.1", | ||
| "runtimeHint": "npx", | ||
@@ -18,0 +18,0 @@ "transport": { |
| --- | ||
| name: ark-contract | ||
| description: Shortcut to /ark-adopt (session 0) or /ark-autopilot (later config tighten). Deprecated as a first-class door. | ||
| description: Shortcut to /ark-adopt (session 0) or /ark-autopilot (later config tighten). Edit layers, ArkRules, or the ArkRun extra. Deprecated as a first-class door. | ||
| --- | ||
@@ -20,4 +20,5 @@ | ||
| |------------------------------|----------------| | ||
| | Layers / include / ArkRules need an edit | **`/ark-adopt`** (path) or **`/ark-autopilot`** (tighten) | | ||
| | Layers / include / ArkRules / **ArkRun extra** need an edit | **`/ark-adopt`** (path, first `arkRun`) or **`/ark-autopilot`** (tighten) | | ||
| | False-green / concentrated edge | **`/ark-adopt`** — write the honest config | | ||
| | Companion install / one kernel candidate | **`/ark-runtime`** — this leftover name does not wire `@arkgate/runtime` | | ||
@@ -44,3 +45,3 @@ ## Dual engine (mandatory) | ||
| Label findings **`[Layer]`** vs **`[ArkRules]`**. Absence of `arkRules` is valid. | ||
| Label findings **`[Layer]`** vs **`[ArkRules]`** vs **`[ArkRun]`**. Absence of `arkRules` or `arkRun` is valid. First-time extra is **`/ark-adopt`** (advisory). Wire one candidate with **`/ark-runtime`**. New kernel-managed file with **`/ark-place`**. Do not invent `/ark-run`. | ||
@@ -59,5 +60,5 @@ ## Subagent fan-out (optional, host-dependent) | ||
| 1. If the path is missing or lying → execute **`/ark-adopt`**. | ||
| 2. If the path is honest and you are tightening rules → execute **`/ark-autopilot`**. | ||
| 3. `ark-check --strict-config`. | ||
| 1. If the path is missing or lying → execute **`/ark-adopt`** (including first advisory `arkRun`). | ||
| 2. If the path is honest and you are tightening rules or the ArkRun extra → execute **`/ark-autopilot`**. | ||
| 3. Companion / one candidate → **`/ark-runtime`**. `ark-check --strict-config`. | ||
@@ -72,5 +73,5 @@ ## Completion contract (skill incomplete if missing) | ||
| - **Result:** one-line outcome | ||
| - **Planes:** one-line split of residual **[Layer]** vs **[ArkRules]** (or `n/a` if unused) | ||
| - **Planes:** one-line split of residual **[Layer]** vs **[ArkRules]** vs **[ArkRun]** (or `n/a` if unused) | ||
| - **Compass:** top residual lenses | `n/a` | ||
| - **Handoff:** `/ark-adopt` / `/ark-autopilot` / `none` | ||
| - **Incomplete?** `no` | `yes — <what is missing>` |
@@ -10,3 +10,3 @@ # ArkGate Agent Skills package | ||
| Package version when last generated context: **arkgate@4.7.0** | ||
| Package version when last generated context: **arkgate@4.7.1** | ||
| Schema: agent-skills package contract `1.0` | ||
@@ -13,0 +13,0 @@ |
| --- | ||
| name: ark-contract | ||
| description: Shortcut to /ark-adopt (session 0) or /ark-autopilot (later config tighten). Deprecated as a first-class door. | ||
| description: Shortcut to /ark-adopt (session 0) or /ark-autopilot (later config tighten). Edit layers, ArkRules, or the ArkRun extra. Deprecated as a first-class door. | ||
| --- | ||
@@ -20,4 +20,5 @@ | ||
| |------------------------------|----------------| | ||
| | Layers / include / ArkRules need an edit | **`/ark-adopt`** (path) or **`/ark-autopilot`** (tighten) | | ||
| | Layers / include / ArkRules / **ArkRun extra** need an edit | **`/ark-adopt`** (path, first `arkRun`) or **`/ark-autopilot`** (tighten) | | ||
| | False-green / concentrated edge | **`/ark-adopt`** — write the honest config | | ||
| | Companion install / one kernel candidate | **`/ark-runtime`** — this leftover name does not wire `@arkgate/runtime` | | ||
@@ -44,3 +45,3 @@ ## Dual engine (mandatory) | ||
| Label findings **`[Layer]`** vs **`[ArkRules]`**. Absence of `arkRules` is valid. | ||
| Label findings **`[Layer]`** vs **`[ArkRules]`** vs **`[ArkRun]`**. Absence of `arkRules` or `arkRun` is valid. First-time extra is **`/ark-adopt`** (advisory). Wire one candidate with **`/ark-runtime`**. New kernel-managed file with **`/ark-place`**. Do not invent `/ark-run`. | ||
@@ -59,5 +60,5 @@ ## Subagent fan-out (optional, host-dependent) | ||
| 1. If the path is missing or lying → execute **`/ark-adopt`**. | ||
| 2. If the path is honest and you are tightening rules → execute **`/ark-autopilot`**. | ||
| 3. `ark-check --strict-config`. | ||
| 1. If the path is missing or lying → execute **`/ark-adopt`** (including first advisory `arkRun`). | ||
| 2. If the path is honest and you are tightening rules or the ArkRun extra → execute **`/ark-autopilot`**. | ||
| 3. Companion / one candidate → **`/ark-runtime`**. `ark-check --strict-config`. | ||
@@ -72,5 +73,5 @@ ## Completion contract (skill incomplete if missing) | ||
| - **Result:** one-line outcome | ||
| - **Planes:** one-line split of residual **[Layer]** vs **[ArkRules]** (or `n/a` if unused) | ||
| - **Planes:** one-line split of residual **[Layer]** vs **[ArkRules]** vs **[ArkRun]** (or `n/a` if unused) | ||
| - **Compass:** top residual lenses | `n/a` | ||
| - **Handoff:** `/ark-adopt` / `/ark-autopilot` / `none` | ||
| - **Incomplete?** `no` | `yes — <what is missing>` |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
3407419
0.55%220
0.46%52655
0.7%140
0.72%