arkgate 3.0.4 → 3.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -1
- package/bin/ark-check.mjs +23 -50
- package/bin/ark.mjs +3 -3
- package/bin/lib/agent-gates.mjs +9 -0
- package/bin/lib/codex-home.mjs +10 -1
- package/bin/lib/doctor-plan.mjs +30 -5
- package/bin/lib/install-migrate.mjs +81 -25
- package/bin/lib/mcp-adoption.mjs +8 -0
- package/bin/lib/skill-install.mjs +302 -22
- package/bin/lib/weakest-link.mjs +61 -12
- package/bin/lib/write-path-capabilities.mjs +3 -1
- package/bin/lib/write-path-detect.mjs +14 -8
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/docs/agent-guide.md +1 -1
- package/docs/ai-gates.md +27 -2
- package/package.json +1 -1
- package/server.json +2 -2
- package/templates/skills/ark-upgrade.md +9 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,42 @@ All notable changes to ArkGate (`arkgate`; formerly `ark-runtime-kernel`) are do
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
-
No changes are scheduled after 3.0.
|
|
7
|
+
No changes are scheduled after 3.0.5.
|
|
8
|
+
|
|
9
|
+
## 3.0.5 — 2026-07-14
|
|
10
|
+
|
|
11
|
+
Codex host skill catalog + residual honesty. **No breaking** CLI or `ark.config.json`
|
|
12
|
+
changes. **No gate weaken.**
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **Codex `/ark-*` skills not invocable:** install wrote flat `.codex/prompts/*.md`, which
|
|
17
|
+
Codex does not load as skills. Repo catalog is now `.agents/skills/<name>/SKILL.md`
|
|
18
|
+
(Agent Skills REPO scope); optional home catalog is `$CODEX_HOME/skills/<name>/SKILL.md`
|
|
19
|
+
via `--codex-home`. Post-install verifies AGENTS.md `/ark-*` refs against each selected
|
|
20
|
+
host catalog.
|
|
21
|
+
- **Temp-root MCP footgun:** `--codex-home` no longer rebinds primary `[mcp_servers.ark]` in
|
|
22
|
+
the default `~/.codex/config.toml` when the project root is a temp/upgrade path (skills may
|
|
23
|
+
still refresh under an isolated or real home).
|
|
24
|
+
- **Multi-host skill hints:** Codex legacy-prompts-only debt no longer suppresses missing/stale
|
|
25
|
+
skill reports for Claude/Cursor/other hosts in doctor and `ark-check` human output.
|
|
26
|
+
- **Deferred Codex home debt severity:** outside a Codex session, home skill gaps are dim/info
|
|
27
|
+
(not warn) and are not Top actions; when the session host is Codex they stay warn + fix.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **Skill parity sensors:** missing / stale / legacy-prompts-only for repo and home catalogs,
|
|
32
|
+
with package `arkVersion` stamps; doctor and JSON expose concrete refresh fixes
|
|
33
|
+
(`--skills-only --tools codex` and/or `--codex-home`).
|
|
34
|
+
- **CI fail-closed detection:** workflows with ark-check but only `--strict-config` (or no
|
|
35
|
+
strict flags) surface `enforcement-ci-not-fail-closed` (warn) with a `--strict-merge` fix.
|
|
36
|
+
`--strict` / `--strict-merge` / `--require-gates` count as fail-closed. Merge-gate inventory
|
|
37
|
+
evidence requires that fail-closed profile.
|
|
38
|
+
- **Codex write-path honesty:** install and doctor state local Codex write is advisory (MCP +
|
|
39
|
+
best-effort hooks; not Claude/Grok hard-write + repair); CI `--strict-merge` + required
|
|
40
|
+
status is the hard merge backstop.
|
|
41
|
+
|
|
42
|
+
Release note: `docs/releases/3.0.5.md`.
|
|
8
43
|
|
|
9
44
|
## 3.0.4 — 2026-07-14
|
|
10
45
|
|
package/bin/ark-check.mjs
CHANGED
|
@@ -33,7 +33,9 @@ import {
|
|
|
33
33
|
loadTypeScript,
|
|
34
34
|
detectSkillGaps,
|
|
35
35
|
detectCodexHomeGap,
|
|
36
|
-
|
|
36
|
+
detectCodexRepoSkillGap,
|
|
37
|
+
codexConcernIsActive,
|
|
38
|
+
printSkillAndCodexGapHints,
|
|
37
39
|
missingGates,
|
|
38
40
|
staleRunnerGateFiles,
|
|
39
41
|
brokenMcpGateFiles,
|
|
@@ -44,7 +46,6 @@ import {
|
|
|
44
46
|
arkPackageVersion,
|
|
45
47
|
compactRouterHost,
|
|
46
48
|
REQUIRED_GATE_FILES,
|
|
47
|
-
codexPromptsDir,
|
|
48
49
|
detectWritePathCapabilities,
|
|
49
50
|
} from './lib/agent-gates.mjs';
|
|
50
51
|
import { syncBaselineIntoCheckSurfaces } from './lib/field-install.mjs';
|
|
@@ -301,8 +302,8 @@ function usage() {
|
|
|
301
302
|
'(instruction-tier rule files derived from the same contract).',
|
|
302
303
|
'It also installs the /ark-* skills shipped in templates/skills/ into each',
|
|
303
304
|
'detected tool\'s command location (.claude/skills/, .cursor/commands/,',
|
|
304
|
-
'.
|
|
305
|
-
'.github/prompts/).',
|
|
305
|
+
'.agents/skills/ (Codex REPO catalog), .grok/skills/, .windsurf/workflows/,',
|
|
306
|
+
'.clinerules/workflows/, .github/prompts/).',
|
|
306
307
|
'Kiro, Roo, Continue, and Gemini have no command mechanism and receive only their',
|
|
307
308
|
'rule file. Existing files are never overwritten without --force, so re-running',
|
|
308
309
|
'after an update only adds what is missing. --skills-only restricts the write to',
|
|
@@ -1229,6 +1230,8 @@ async function main() {
|
|
|
1229
1230
|
|
|
1230
1231
|
const skillGaps = detectSkillGaps(root);
|
|
1231
1232
|
const codexHomeGap = detectCodexHomeGap(root);
|
|
1233
|
+
const codexRepoSkillGap = detectCodexRepoSkillGap(root);
|
|
1234
|
+
const codexSessionActive = codexConcernIsActive();
|
|
1232
1235
|
|
|
1233
1236
|
if (args.report) {
|
|
1234
1237
|
const exampleByLayer = new Map();
|
|
@@ -1372,7 +1375,15 @@ async function main() {
|
|
|
1372
1375
|
warnings,
|
|
1373
1376
|
...(activeViolations.length > 0 ? { summary: summarizeViolations(activeViolations) } : {}),
|
|
1374
1377
|
...(skillGaps.length > 0 ? { skillGaps } : {}),
|
|
1375
|
-
...(codexHomeGap
|
|
1378
|
+
...(codexHomeGap
|
|
1379
|
+
? {
|
|
1380
|
+
codexHomeGap: {
|
|
1381
|
+
...codexHomeGap,
|
|
1382
|
+
deferred: !codexSessionActive,
|
|
1383
|
+
},
|
|
1384
|
+
}
|
|
1385
|
+
: {}),
|
|
1386
|
+
...(codexRepoSkillGap ? { codexRepoSkillGap } : {}),
|
|
1376
1387
|
}, null, 2));
|
|
1377
1388
|
} else {
|
|
1378
1389
|
for (const warning of warnings) {
|
|
@@ -1420,30 +1431,13 @@ async function main() {
|
|
|
1420
1431
|
printViolationBreakdown(summarizeViolations(activeViolations), { toStderr: true });
|
|
1421
1432
|
}
|
|
1422
1433
|
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
`${missingTotal} /ark-* skill(s) not installed for ${tools} (this Ark version ships them). ` +
|
|
1431
|
-
`Install: ${arkCommand(root, 'ark-check', '--install-agent-gates')}`
|
|
1432
|
-
)
|
|
1433
|
-
);
|
|
1434
|
-
}
|
|
1435
|
-
if (staleTotal > 0) {
|
|
1436
|
-
// Stale skills already exist, so refreshing needs --force. --skills-only
|
|
1437
|
-
// scopes the overwrite to the canonical skills, leaving a customized
|
|
1438
|
-
// AGENTS.md / settings / CI untouched (a bare --force would clobber them).
|
|
1439
|
-
console.log(
|
|
1440
|
-
color.dim(
|
|
1441
|
-
`${staleTotal} /ark-* skill(s) outdated for ${tools} (this Ark ships newer versions). ` +
|
|
1442
|
-
`Refresh: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --force')}`
|
|
1443
|
-
)
|
|
1444
|
-
);
|
|
1445
|
-
}
|
|
1446
|
-
}
|
|
1434
|
+
printSkillAndCodexGapHints(root, {
|
|
1435
|
+
skillGaps,
|
|
1436
|
+
codexHomeGap,
|
|
1437
|
+
codexRepoSkillGap,
|
|
1438
|
+
codexSessionActive,
|
|
1439
|
+
color,
|
|
1440
|
+
});
|
|
1447
1441
|
|
|
1448
1442
|
const staleRunners = staleRunnerGateFiles(root);
|
|
1449
1443
|
if (staleRunners.length > 0) {
|
|
@@ -1464,27 +1458,6 @@ async function main() {
|
|
|
1464
1458
|
)
|
|
1465
1459
|
);
|
|
1466
1460
|
}
|
|
1467
|
-
|
|
1468
|
-
if (codexHomeGap) {
|
|
1469
|
-
const parts = [];
|
|
1470
|
-
if (codexHomeGap.missing > 0) parts.push(`${codexHomeGap.missing} missing`);
|
|
1471
|
-
if (codexHomeGap.stale > 0) parts.push(`${codexHomeGap.stale} outdated`);
|
|
1472
|
-
// Advisory always; when session host is known and not Codex, say so so
|
|
1473
|
-
// /ark-upgrade does not chase home prompts as Incomplete.
|
|
1474
|
-
const activeHost = detectActiveAgentHost();
|
|
1475
|
-
const deferredNote =
|
|
1476
|
-
activeHost != null && activeHost !== 'codex'
|
|
1477
|
-
? ' Deferred unless you use Codex — not a blocker for Grok/Claude/Cursor. '
|
|
1478
|
-
: ' ';
|
|
1479
|
-
console.log(
|
|
1480
|
-
color.dim(
|
|
1481
|
-
`/ark-* skills in ${codexPromptsDir()} are behind this Ark (${parts.join(', ')}).` +
|
|
1482
|
-
deferredNote +
|
|
1483
|
-
`Codex loads them from $CODEX_HOME/prompts, not the repo. ` +
|
|
1484
|
-
`When using Codex: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --codex-home --force')}`
|
|
1485
|
-
)
|
|
1486
|
-
);
|
|
1487
|
-
}
|
|
1488
1461
|
}
|
|
1489
1462
|
|
|
1490
1463
|
if (args.watch) {
|
package/bin/ark.mjs
CHANGED
|
@@ -213,9 +213,9 @@ async function upgrade(args) {
|
|
|
213
213
|
let status = runArkCheck(['--root', root, '--install-agent-gates'], { cwd: root });
|
|
214
214
|
if (status !== 0) return status;
|
|
215
215
|
|
|
216
|
-
// Codex
|
|
217
|
-
// when a Codex home exists. --force rewrites temp/upgrade MCP roots to this
|
|
218
|
-
// Non-fatal: a permission error (e.g. sandbox) shouldn't fail
|
|
216
|
+
// Codex home skill catalog is $CODEX_HOME/skills/<name>/SKILL.md (repo uses .agents/skills/).
|
|
217
|
+
// Refresh home when a Codex home exists. --force rewrites temp/upgrade MCP roots to this
|
|
218
|
+
// project + arkgate-mcp. Non-fatal: a permission error (e.g. sandbox) shouldn't fail upgrade.
|
|
219
219
|
const codexHomeBase = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
|
220
220
|
if (fs.existsSync(codexHomeBase)) {
|
|
221
221
|
console.log(`\n Refreshing Codex home (${codexHomeBase})…`);
|
package/bin/lib/agent-gates.mjs
CHANGED
|
@@ -10,6 +10,7 @@ export {
|
|
|
10
10
|
codexPrimaryTable,
|
|
11
11
|
codexProjectSlug,
|
|
12
12
|
codexPromptsDir,
|
|
13
|
+
codexSkillsDir,
|
|
13
14
|
codexScopedTableForRoot,
|
|
14
15
|
extractCodexArkRootFromToml,
|
|
15
16
|
extractCodexRootFromBlock,
|
|
@@ -77,6 +78,7 @@ export {
|
|
|
77
78
|
normalizeToolsList,
|
|
78
79
|
resolveTools,
|
|
79
80
|
KNOWN_TOOLS,
|
|
81
|
+
SKILL_TOOL_TARGETS,
|
|
80
82
|
detectActiveAgentHost,
|
|
81
83
|
codexConcernIsActive,
|
|
82
84
|
arkPackageVersion,
|
|
@@ -86,7 +88,13 @@ export {
|
|
|
86
88
|
skillTemplates,
|
|
87
89
|
skillTemplateNames,
|
|
88
90
|
detectCodexHomeGap,
|
|
91
|
+
detectCodexRepoSkillGap,
|
|
92
|
+
assessCodexSkillParity,
|
|
93
|
+
assessSkillCatalogParity,
|
|
89
94
|
detectSkillGaps,
|
|
95
|
+
agentsMdSkillRefs,
|
|
96
|
+
verifyHostSkillCatalog,
|
|
97
|
+
printSkillAndCodexGapHints,
|
|
90
98
|
} from './skill-install.mjs';
|
|
91
99
|
|
|
92
100
|
export { detectDeployPathQuality } from './deploy-path.mjs';
|
|
@@ -101,6 +109,7 @@ export {
|
|
|
101
109
|
export {
|
|
102
110
|
detectPreCommitArk,
|
|
103
111
|
detectCiEnforcement,
|
|
112
|
+
classifyArkCheckFlags,
|
|
104
113
|
detectConfigGateDrift,
|
|
105
114
|
jobIdsThatRunArkCheck,
|
|
106
115
|
isArkRequiredStatusCheck,
|
package/bin/lib/codex-home.mjs
CHANGED
|
@@ -10,12 +10,21 @@ import { execCommandParts } from '../ark-shared.mjs';
|
|
|
10
10
|
|
|
11
11
|
export const PREFERRED_CODEX_MCP_BIN = 'arkgate-mcp';
|
|
12
12
|
|
|
13
|
-
/** Where Codex loads slash-command prompts ($CODEX_HOME/prompts). */
|
|
13
|
+
/** Where Codex loads slash-command prompts ($CODEX_HOME/prompts) — legacy, not the skill catalog. */
|
|
14
14
|
export function codexPromptsDir() {
|
|
15
15
|
const base = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
|
16
16
|
return path.join(base, 'prompts');
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Where Codex loads user/home SKILL.md skills ($CODEX_HOME/skills/<name>/SKILL.md).
|
|
21
|
+
* Repo-scoped skills live at `.agents/skills/<name>/SKILL.md` (Agent Skills standard).
|
|
22
|
+
*/
|
|
23
|
+
export function codexSkillsDir() {
|
|
24
|
+
const base = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
|
25
|
+
return path.join(base, 'skills');
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
/** Where Codex loads MCP servers ($CODEX_HOME/config.toml) — global, not project-local. */
|
|
20
29
|
export function codexConfigPath() {
|
|
21
30
|
const base = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
package/bin/lib/doctor-plan.mjs
CHANGED
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
import {
|
|
15
15
|
collectAdoptionGaps,
|
|
16
16
|
detectSkillGaps,
|
|
17
|
+
detectCodexHomeGap,
|
|
18
|
+
codexConcernIsActive,
|
|
17
19
|
detectWritePathCapabilities,
|
|
18
20
|
missingGates,
|
|
19
21
|
staleRunnerGateFiles,
|
|
@@ -405,8 +407,6 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
405
407
|
? [...baseline.keys].filter((key) => !currentKeys.has(key)).length
|
|
406
408
|
: 0;
|
|
407
409
|
const activeCount = violations.length - suppressed;
|
|
408
|
-
const missingSkills = skillGaps.reduce((sum, gap) => sum + gap.missing, 0);
|
|
409
|
-
const staleSkills = skillGaps.reduce((sum, gap) => sum + gap.stale, 0);
|
|
410
410
|
const designSmells = detectDesignSmells(root, config, files, cov);
|
|
411
411
|
const designFitness = summarizeDesignFitness(designSmells, {
|
|
412
412
|
activeViolations: activeCount,
|
|
@@ -769,11 +769,36 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
769
769
|
line(bad, `Missing gates: ${gatesMissing.join(', ')}`);
|
|
770
770
|
actions.push(`install gates (${arkCommand(root, 'ark-check', '--install-agent-gates')})`);
|
|
771
771
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
772
|
+
// Report Codex legacy prompts and other-host missing/stale independently (never exclusive).
|
|
773
|
+
const legacyCodex = skillGaps.some((g) => g.tool === 'codex' && g.legacyPromptsOnly);
|
|
774
|
+
const remainingGaps = skillGaps.filter((g) => !(g.tool === 'codex' && g.legacyPromptsOnly));
|
|
775
|
+
const remMiss = remainingGaps.reduce((s, g) => s + g.missing, 0);
|
|
776
|
+
const remStale = remainingGaps.reduce((s, g) => s + g.stale, 0);
|
|
777
|
+
if (remMiss + remStale === 0 && !legacyCodex) line(ok, '/ark-* skills current for detected tools');
|
|
778
|
+
if (legacyCodex) {
|
|
779
|
+
line(warn, 'Codex: legacy flat .codex/prompts only (not a loadable skill catalog)');
|
|
780
|
+
actions.push('install Codex SKILL.md catalog (--install-agent-gates --skills-only --tools codex --force)');
|
|
781
|
+
}
|
|
782
|
+
if (remMiss + remStale > 0) {
|
|
783
|
+
line(warn, `${remMiss} missing / ${remStale} outdated /ark-* skill(s) for ${remainingGaps.map((g) => g.tool).join(', ')}`);
|
|
775
784
|
actions.push('refresh /ark-* skills (--install-agent-gates --skills-only --force)');
|
|
776
785
|
}
|
|
786
|
+
const codexHomeGap = detectCodexHomeGap(root);
|
|
787
|
+
if (codexHomeGap) {
|
|
788
|
+
const parts = [
|
|
789
|
+
codexHomeGap.legacyPromptsOnly ? 'legacy-prompts-only' : null,
|
|
790
|
+
codexHomeGap.missing > 0 ? `${codexHomeGap.missing} missing` : null,
|
|
791
|
+
codexHomeGap.stale > 0 ? `${codexHomeGap.stale} outdated` : null,
|
|
792
|
+
].filter(Boolean);
|
|
793
|
+
const deferred = !codexConcernIsActive();
|
|
794
|
+
// Deferred home debt is dim/info (not warn) so non-Codex sessions are not "incomplete".
|
|
795
|
+
if (deferred) {
|
|
796
|
+
line(color.dim('·'), color.dim(`Codex home skills ${parts.join(', ')} (deferred — not on Codex session)`));
|
|
797
|
+
} else {
|
|
798
|
+
line(warn, `Codex home skills ${parts.join(', ')}`);
|
|
799
|
+
actions.push('refresh Codex home skills (--install-agent-gates --skills-only --codex-home --force)');
|
|
800
|
+
}
|
|
801
|
+
}
|
|
777
802
|
|
|
778
803
|
console.log('');
|
|
779
804
|
console.log(color.bold('Baseline'));
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from '../ark-shared.mjs';
|
|
13
13
|
import {
|
|
14
14
|
codexPromptsDir,
|
|
15
|
+
codexSkillsDir,
|
|
15
16
|
codexConfigPath,
|
|
16
17
|
isTempOrUpgradeRoot,
|
|
17
18
|
usesDefaultCodexHome,
|
|
@@ -52,6 +53,7 @@ import {
|
|
|
52
53
|
isVersionOlder,
|
|
53
54
|
detectSkillGaps,
|
|
54
55
|
arkPackageVersion,
|
|
56
|
+
verifyHostSkillCatalog,
|
|
55
57
|
} from './skill-install.mjs';
|
|
56
58
|
import { detectDeployPathQuality } from './deploy-path.mjs';
|
|
57
59
|
import {
|
|
@@ -383,15 +385,15 @@ export function runInstallAgentGates(args) {
|
|
|
383
385
|
console.log(` ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --force')}`);
|
|
384
386
|
}
|
|
385
387
|
|
|
386
|
-
// --codex-home writes
|
|
387
|
-
// Codex
|
|
388
|
-
//
|
|
389
|
-
//
|
|
388
|
+
// --codex-home writes SKILL.md skills to $CODEX_HOME/skills/<name>/SKILL.md.
|
|
389
|
+
// Codex's real catalog loads skill directories (not flat $CODEX_HOME/prompts).
|
|
390
|
+
// Repo installs already write `.agents/skills/<name>/SKILL.md` when `codex` is
|
|
391
|
+
// selected; home install is for multi-project / non-repo-local refresh.
|
|
390
392
|
const homeResults = [];
|
|
391
393
|
if (args.codexHome) {
|
|
392
|
-
const dir =
|
|
394
|
+
const dir = codexSkillsDir();
|
|
393
395
|
console.log('');
|
|
394
|
-
console.log(`Codex home skills (${dir}):`);
|
|
396
|
+
console.log(`Codex home skills (${dir}/<name>/SKILL.md):`);
|
|
395
397
|
try {
|
|
396
398
|
fs.mkdirSync(dir, { recursive: true });
|
|
397
399
|
} catch (error) {
|
|
@@ -400,23 +402,25 @@ export function runInstallAgentGates(args) {
|
|
|
400
402
|
}
|
|
401
403
|
if (homeResults.length === 0) {
|
|
402
404
|
for (const [name, content] of skills) {
|
|
403
|
-
const
|
|
405
|
+
const skillDir = path.join(dir, name);
|
|
406
|
+
const file = path.join(skillDir, 'SKILL.md');
|
|
404
407
|
if (fs.existsSync(file) && !args.force) {
|
|
405
408
|
const installed = installedSkillVersion(file);
|
|
406
409
|
const behind = installed === null || (version && isVersionOlder(installed, version));
|
|
407
410
|
const note = behind
|
|
408
411
|
? ` (stale: ${installed ?? 'no stamp'} < ${version}; use --force)`
|
|
409
412
|
: ' (up to date)';
|
|
410
|
-
console.log(` ${'skipped'.padEnd(7)} ${name}.md${note}`);
|
|
413
|
+
console.log(` ${'skipped'.padEnd(7)} ${name}/SKILL.md${note}`);
|
|
411
414
|
homeResults.push({ status: 'skipped' });
|
|
412
415
|
continue;
|
|
413
416
|
}
|
|
414
417
|
try {
|
|
418
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
415
419
|
fs.writeFileSync(file, content);
|
|
416
|
-
console.log(` ${'wrote'.padEnd(7)} ${name}.md`);
|
|
420
|
+
console.log(` ${'wrote'.padEnd(7)} ${name}/SKILL.md`);
|
|
417
421
|
homeResults.push({ status: 'written' });
|
|
418
422
|
} catch (error) {
|
|
419
|
-
console.log(` ${'FAILED'.padEnd(7)} ${name}.md (${error.message})`);
|
|
423
|
+
console.log(` ${'FAILED'.padEnd(7)} ${name}/SKILL.md (${error.message})`);
|
|
420
424
|
homeResults.push({ status: 'failed' });
|
|
421
425
|
}
|
|
422
426
|
}
|
|
@@ -429,18 +433,15 @@ export function runInstallAgentGates(args) {
|
|
|
429
433
|
// home-dir merge instead. Fires whenever Codex is in play so `ark://manifest` is live
|
|
430
434
|
// without a manual copy step.
|
|
431
435
|
//
|
|
432
|
-
// Skip home
|
|
433
|
-
// CODEX_HOME
|
|
434
|
-
//
|
|
435
|
-
//
|
|
436
|
-
//
|
|
436
|
+
// Skip home MCP mutation when the project root is a temp/upgrade scratch *and*
|
|
437
|
+
// CODEX_HOME is the default (~/.codex). Fixtures and agent smokes must not rewrite
|
|
438
|
+
// the developer's real config.toml with a temp --root. --codex-home still refreshes
|
|
439
|
+
// home *skills* below; MCP binding of a temp root into default home is never safe.
|
|
440
|
+
// A redirected CODEX_HOME (tests/isolation) may still wire as requested.
|
|
437
441
|
let codexMcp = null;
|
|
438
442
|
const wantCodexWire = !args.compact && (tools.has('codex') || args.codexHome);
|
|
439
443
|
const skipHomeWire =
|
|
440
|
-
wantCodexWire &&
|
|
441
|
-
isTempOrUpgradeRoot(root) &&
|
|
442
|
-
!args.codexHome &&
|
|
443
|
-
usesDefaultCodexHome();
|
|
444
|
+
wantCodexWire && isTempOrUpgradeRoot(root) && usesDefaultCodexHome();
|
|
444
445
|
if (wantCodexWire && !skipHomeWire) {
|
|
445
446
|
codexMcp = wireCodexMcp(root, args.force);
|
|
446
447
|
console.log('');
|
|
@@ -498,13 +499,68 @@ export function runInstallAgentGates(args) {
|
|
|
498
499
|
if (codexMcp && codexMcp.status !== 'failed') {
|
|
499
500
|
console.log(` Codex: ark MCP registered in ${codexMcp.file} — restart Codex so \`ark://manifest\` loads.`);
|
|
500
501
|
}
|
|
502
|
+
if (tools.has('codex') && !args.compact) {
|
|
503
|
+
console.log(' Codex write path (honest):');
|
|
504
|
+
console.log(' - Local: advisory MCP + best-effort .codex/hooks.json (not a hard boundary).');
|
|
505
|
+
console.log(' - Hard merge backstop: CI --strict-merge + required status check.');
|
|
506
|
+
console.log(' - Not equivalent to Claude/Grok PreToolUse hard-write + repair.');
|
|
507
|
+
}
|
|
501
508
|
if (args.codexHome) {
|
|
502
|
-
console.log(
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
console.log(
|
|
507
|
-
|
|
509
|
+
console.log(
|
|
510
|
+
` Codex: refreshed home skills under ${codexSkillsDir()}/<name>/SKILL.md (Codex skill catalog).`
|
|
511
|
+
);
|
|
512
|
+
} else if (tools.has('codex') && skills.length > 0 && !args.compact) {
|
|
513
|
+
console.log(
|
|
514
|
+
' Codex: project skills at `.agents/skills/<name>/SKILL.md` (Agent Skills REPO scope).'
|
|
515
|
+
);
|
|
516
|
+
console.log(
|
|
517
|
+
` Optional multi-project home copy: ${arkCommand(root, 'ark-check', '--install-agent-gates --codex-home')}`
|
|
518
|
+
);
|
|
519
|
+
console.log(
|
|
520
|
+
` (writes $CODEX_HOME/skills; legacy flat prompts at ${codexPromptsDir()} are not the skill catalog).`
|
|
521
|
+
);
|
|
522
|
+
// Best-effort note when dead prompts still sit under the repo.
|
|
523
|
+
try {
|
|
524
|
+
const promptDir = path.join(root, '.codex', 'prompts');
|
|
525
|
+
if (fs.existsSync(promptDir)) {
|
|
526
|
+
const legacy = fs.readdirSync(promptDir).filter((n) => /^ark-[a-z0-9-]+\.md$/.test(n));
|
|
527
|
+
if (legacy.length > 0) {
|
|
528
|
+
console.log(
|
|
529
|
+
` Note: ignoring ${legacy.length} legacy .codex/prompts/ark-*.md file(s) — not loadable as Codex skills.`
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
} catch {
|
|
534
|
+
// ignore
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Post-install: AGENTS.md /ark-* refs must exist in each selected host catalog.
|
|
540
|
+
// Compact routers intentionally omit those refs (package/MCP is the router).
|
|
541
|
+
if (!args.compact && skills.length > 0) {
|
|
542
|
+
const catalog = verifyHostSkillCatalog(root, tools, {
|
|
543
|
+
skillNames: skills.map(([name]) => name),
|
|
544
|
+
});
|
|
545
|
+
if (!catalog.ok) {
|
|
546
|
+
console.log('');
|
|
547
|
+
console.error(
|
|
548
|
+
`Skill catalog verification failed: ${catalog.missing.length} AGENTS.md /ark-* reference(s) missing from host catalogs.`
|
|
549
|
+
);
|
|
550
|
+
for (const miss of catalog.missing.slice(0, 12)) {
|
|
551
|
+
console.error(` missing ${miss.tool}: ${miss.path}`);
|
|
552
|
+
}
|
|
553
|
+
if (catalog.missing.length > 12) {
|
|
554
|
+
console.error(` …and ${catalog.missing.length - 12} more`);
|
|
555
|
+
}
|
|
556
|
+
process.exitCode = 1;
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
if (catalog.checkedTools.length > 0 && catalog.referenced.length > 0) {
|
|
560
|
+
console.log('');
|
|
561
|
+
console.log(
|
|
562
|
+
`Skill catalog verified: ${catalog.referenced.length} AGENTS.md /ark-* skill(s) present for ${catalog.checkedTools.join(', ')}.`
|
|
563
|
+
);
|
|
508
564
|
}
|
|
509
565
|
}
|
|
510
566
|
warnLockfileConflict(root);
|
package/bin/lib/mcp-adoption.mjs
CHANGED
|
@@ -143,6 +143,14 @@ export function collectAdoptionGaps(root, config, coverage) {
|
|
|
143
143
|
extras: [['.cursor/mcp.json', 'MCP config']],
|
|
144
144
|
toolsFlag: 'cursor',
|
|
145
145
|
},
|
|
146
|
+
{
|
|
147
|
+
host: 'codex',
|
|
148
|
+
dir: '.codex',
|
|
149
|
+
// Official Codex REPO skill catalog (Agent Skills standard) — not .codex/prompts.
|
|
150
|
+
skill: (n) => path.join(root, '.agents', 'skills', n, 'SKILL.md'),
|
|
151
|
+
extras: [['.codex/hooks.json', 'hooks']],
|
|
152
|
+
toolsFlag: 'codex',
|
|
153
|
+
},
|
|
146
154
|
];
|
|
147
155
|
for (const h of hostChecks) {
|
|
148
156
|
if (!fs.existsSync(path.join(root, h.dir))) continue;
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
import {
|
|
6
|
+
import { arkCommand } from '../ark-shared.mjs';
|
|
7
|
+
import { codexPromptsDir, codexSkillsDir } from './codex-home.mjs';
|
|
7
8
|
import { __packageRoot, isCompactRouterAgentsContent, readJson } from './gate-files.mjs';
|
|
8
9
|
|
|
9
10
|
export function normalizeToolsList(tools) {
|
|
@@ -144,13 +145,19 @@ export const KNOWN_TOOLS = [
|
|
|
144
145
|
];
|
|
145
146
|
|
|
146
147
|
// One canonical markdown per skill (templates/skills/*.md, shipped in the npm
|
|
147
|
-
// package); installed into each tool's slash-command location.
|
|
148
|
-
// frontmatter (name/description) is understood or harmlessly ignored
|
|
149
|
-
// host. Kiro has no command mechanism — its steering rule file is the
|
|
148
|
+
// package); installed into each tool's slash-command / skill-catalog location.
|
|
149
|
+
// The YAML frontmatter (name/description) is understood or harmlessly ignored
|
|
150
|
+
// by every host. Kiro has no command mechanism — its steering rule file is the
|
|
151
|
+
// only gate.
|
|
152
|
+
//
|
|
153
|
+
// Codex: discovers Agent Skills directories with SKILL.md — repo path is the
|
|
154
|
+
// official `.agents/skills/<name>/SKILL.md` (not dead `.codex/prompts/*.md`).
|
|
155
|
+
// Home install uses `$CODEX_HOME/skills/<name>/SKILL.md` via --codex-home.
|
|
150
156
|
export const SKILL_TOOL_TARGETS = {
|
|
151
157
|
claude: (name) => `.claude/skills/${name}/SKILL.md`,
|
|
152
158
|
cursor: (name) => `.cursor/commands/${name}.md`,
|
|
153
|
-
|
|
159
|
+
// Official Codex REPO skill scope (Agent Skills standard).
|
|
160
|
+
codex: (name) => `.agents/skills/${name}/SKILL.md`,
|
|
154
161
|
// Grok Build: project skills at .grok/skills/<name>/SKILL.md (slash-invocable).
|
|
155
162
|
grok: (name) => `.grok/skills/${name}/SKILL.md`,
|
|
156
163
|
windsurf: (name) => `.windsurf/workflows/${name}.md`,
|
|
@@ -254,6 +261,122 @@ export function skillTemplateNames() {
|
|
|
254
261
|
.map((entry) => path.basename(entry.name, '.md'));
|
|
255
262
|
}
|
|
256
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Count present / stale / legacy-only skill files for one catalog root.
|
|
266
|
+
* @param {string[]} skillNames
|
|
267
|
+
* @param {(name: string) => string} skillFile path builder
|
|
268
|
+
* @param {string|null} packageVersion
|
|
269
|
+
* @param {{ legacyFile?: (name: string) => string }} [opts]
|
|
270
|
+
*/
|
|
271
|
+
export function assessSkillCatalogParity(skillNames, skillFile, packageVersion, opts = {}) {
|
|
272
|
+
const expectedCount = skillNames.length;
|
|
273
|
+
const present = [];
|
|
274
|
+
let stale = 0;
|
|
275
|
+
for (const name of skillNames) {
|
|
276
|
+
const file = skillFile(name);
|
|
277
|
+
if (!fs.existsSync(file)) continue;
|
|
278
|
+
present.push(name);
|
|
279
|
+
if (packageVersion) {
|
|
280
|
+
const installed = installedSkillVersion(file);
|
|
281
|
+
if (installed === null || isVersionOlder(installed, packageVersion)) stale += 1;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
let legacyCount = 0;
|
|
285
|
+
if (typeof opts.legacyFile === 'function') {
|
|
286
|
+
for (const name of skillNames) {
|
|
287
|
+
if (fs.existsSync(opts.legacyFile(name))) legacyCount += 1;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const presentCount = present.length;
|
|
291
|
+
const missing = expectedCount - presentCount;
|
|
292
|
+
const legacyPromptsOnly = presentCount === 0 && legacyCount > 0;
|
|
293
|
+
const hasLegacyPrompts = legacyCount > 0;
|
|
294
|
+
const ok = missing === 0 && stale === 0 && !legacyPromptsOnly;
|
|
295
|
+
return {
|
|
296
|
+
ok,
|
|
297
|
+
missing,
|
|
298
|
+
stale,
|
|
299
|
+
presentCount,
|
|
300
|
+
expectedCount,
|
|
301
|
+
packageVersion: packageVersion ?? null,
|
|
302
|
+
legacyPromptsOnly,
|
|
303
|
+
hasLegacyPrompts,
|
|
304
|
+
legacyCount,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Repo + home Codex skill parity against the shipping package skill set.
|
|
310
|
+
* Producer trees (templates/skills) and projects without AGENTS.md return null.
|
|
311
|
+
*
|
|
312
|
+
* @param {string} root
|
|
313
|
+
* @returns {null | {
|
|
314
|
+
* packageVersion: string|null,
|
|
315
|
+
* expectedCount: number,
|
|
316
|
+
* repo: object,
|
|
317
|
+
* home: object,
|
|
318
|
+
* skillsDir: string,
|
|
319
|
+
* promptsDir: string,
|
|
320
|
+
* needsAttention: boolean,
|
|
321
|
+
* homeNeedsAttention: boolean,
|
|
322
|
+
* repoNeedsAttention: boolean,
|
|
323
|
+
* }}
|
|
324
|
+
*/
|
|
325
|
+
export function assessCodexSkillParity(root) {
|
|
326
|
+
if (!fs.existsSync(path.join(root, 'AGENTS.md'))) return null;
|
|
327
|
+
if (fs.existsSync(path.join(root, 'templates', 'skills'))) return null;
|
|
328
|
+
const skillNames = skillTemplateNames();
|
|
329
|
+
if (skillNames.length === 0) return null;
|
|
330
|
+
|
|
331
|
+
const packageVersion = arkPackageVersion();
|
|
332
|
+
const skillsDir = codexSkillsDir();
|
|
333
|
+
const promptsDir = codexPromptsDir();
|
|
334
|
+
const repoSkill = (name) => path.join(root, SKILL_TOOL_TARGETS.codex(name));
|
|
335
|
+
const repoLegacy = (name) => path.join(root, '.codex', 'prompts', `${name}.md`);
|
|
336
|
+
const homeSkill = (name) => path.join(skillsDir, name, 'SKILL.md');
|
|
337
|
+
const homeLegacy = (name) => path.join(promptsDir, `${name}.md`);
|
|
338
|
+
|
|
339
|
+
const repo = assessSkillCatalogParity(skillNames, repoSkill, packageVersion, {
|
|
340
|
+
legacyFile: repoLegacy,
|
|
341
|
+
});
|
|
342
|
+
const home = assessSkillCatalogParity(skillNames, homeSkill, packageVersion, {
|
|
343
|
+
legacyFile: homeLegacy,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// Repo catalog matters when .codex is present (Codex host adopted) or repo skills/prompts exist.
|
|
347
|
+
const repoInPlay =
|
|
348
|
+
fs.existsSync(path.join(root, '.codex')) ||
|
|
349
|
+
repo.presentCount > 0 ||
|
|
350
|
+
repo.hasLegacyPrompts;
|
|
351
|
+
// Home is "in play" only when ark skills or legacy prompts were actually installed there
|
|
352
|
+
// (empty $CODEX_HOME/skills is optional multi-project — not debt).
|
|
353
|
+
const homeInPlay = home.presentCount > 0 || home.hasLegacyPrompts;
|
|
354
|
+
|
|
355
|
+
if (!repoInPlay && !homeInPlay) return null;
|
|
356
|
+
|
|
357
|
+
const repoNeedsAttention =
|
|
358
|
+
repoInPlay && (repo.missing > 0 || repo.stale > 0 || repo.legacyPromptsOnly);
|
|
359
|
+
const homeNeedsAttention =
|
|
360
|
+
homeInPlay && (home.missing > 0 || home.stale > 0 || home.legacyPromptsOnly);
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
packageVersion,
|
|
364
|
+
expectedCount: skillNames.length,
|
|
365
|
+
repo: { ...repo, inPlay: repoInPlay },
|
|
366
|
+
home: {
|
|
367
|
+
...home,
|
|
368
|
+
inPlay: homeInPlay,
|
|
369
|
+
skillsDir,
|
|
370
|
+
promptsDir,
|
|
371
|
+
},
|
|
372
|
+
skillsDir,
|
|
373
|
+
promptsDir,
|
|
374
|
+
repoNeedsAttention,
|
|
375
|
+
homeNeedsAttention,
|
|
376
|
+
needsAttention: repoNeedsAttention || homeNeedsAttention,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
257
380
|
// A normal ark-check run is the reliable discovery point for new /ark-* skills.
|
|
258
381
|
// Ark ships no install lifecycle script (a postinstall banner would be blocked by
|
|
259
382
|
// modern package managers' script-approval policy anyway, so careful users never
|
|
@@ -263,24 +386,94 @@ export function skillTemplateNames() {
|
|
|
263
386
|
// Advisory only — never affects the exit code. Copilot has no reliable directory
|
|
264
387
|
// signal, so it is not auto-detected (explicit --tools only), matching resolveTools.
|
|
265
388
|
export function detectCodexHomeGap(root) {
|
|
266
|
-
|
|
267
|
-
if (
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
389
|
+
const parity = assessCodexSkillParity(root);
|
|
390
|
+
if (!parity || !parity.homeNeedsAttention) return null;
|
|
391
|
+
const { home, packageVersion, expectedCount, skillsDir } = parity;
|
|
392
|
+
return {
|
|
393
|
+
missing: home.missing,
|
|
394
|
+
stale: home.stale,
|
|
395
|
+
legacyPromptsOnly: Boolean(home.legacyPromptsOnly),
|
|
396
|
+
hasLegacyPrompts: Boolean(home.hasLegacyPrompts),
|
|
397
|
+
presentCount: home.presentCount,
|
|
398
|
+
expectedCount,
|
|
399
|
+
packageVersion,
|
|
400
|
+
skillsDir,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Repo-side Codex gaps: missing/stale .agents/skills or legacy .codex/prompts only.
|
|
406
|
+
* @param {string} root
|
|
407
|
+
* @returns {null | { missing: number, stale: number, legacyPromptsOnly: boolean, hasLegacyPrompts: boolean, presentCount: number, expectedCount: number, packageVersion: string|null }}
|
|
408
|
+
*/
|
|
409
|
+
export function detectCodexRepoSkillGap(root) {
|
|
410
|
+
const parity = assessCodexSkillParity(root);
|
|
411
|
+
if (!parity || !parity.repoNeedsAttention) return null;
|
|
412
|
+
const { repo, packageVersion, expectedCount } = parity;
|
|
413
|
+
return {
|
|
414
|
+
missing: repo.missing,
|
|
415
|
+
stale: repo.stale,
|
|
416
|
+
legacyPromptsOnly: Boolean(repo.legacyPromptsOnly),
|
|
417
|
+
hasLegacyPrompts: Boolean(repo.hasLegacyPrompts),
|
|
418
|
+
presentCount: repo.presentCount,
|
|
419
|
+
expectedCount,
|
|
420
|
+
packageVersion,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Skill names referenced as `/ark-*` in AGENTS.md (or any instruction text).
|
|
426
|
+
* @param {string} text
|
|
427
|
+
* @returns {string[]}
|
|
428
|
+
*/
|
|
429
|
+
export function agentsMdSkillRefs(text) {
|
|
430
|
+
if (!text || typeof text !== 'string') return [];
|
|
431
|
+
const refs = new Set();
|
|
432
|
+
const re = /\/(ark-[a-z0-9-]+)/g;
|
|
433
|
+
let match;
|
|
434
|
+
while ((match = re.exec(text)) !== null) refs.add(match[1]);
|
|
435
|
+
return [...refs].sort();
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Verify that every `/ark-*` skill referenced by AGENTS.md (and known to this
|
|
440
|
+
* package) is present in each selected host's skill catalog path.
|
|
441
|
+
*
|
|
442
|
+
* Compact routers intentionally omit `/ark-*` — they verify as ok with no checks.
|
|
443
|
+
*
|
|
444
|
+
* @param {string} root
|
|
445
|
+
* @param {Iterable<string>} tools
|
|
446
|
+
* @param {{ skillNames?: string[], agentsText?: string }} [options]
|
|
447
|
+
* @returns {{ ok: boolean, missing: Array<{ tool: string, name: string, path: string }>, referenced: string[], checkedTools: string[], compact?: boolean }}
|
|
448
|
+
*/
|
|
449
|
+
export function verifyHostSkillCatalog(root, tools, options = {}) {
|
|
450
|
+
const skillNames = new Set(options.skillNames ?? skillTemplateNames());
|
|
451
|
+
let agentsText = options.agentsText;
|
|
452
|
+
if (agentsText == null) {
|
|
453
|
+
try {
|
|
454
|
+
agentsText = fs.readFileSync(path.join(root, 'AGENTS.md'), 'utf8');
|
|
455
|
+
} catch {
|
|
456
|
+
return { ok: true, missing: [], referenced: [], checkedTools: [] };
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
if (isCompactRouterAgentsContent(agentsText)) {
|
|
460
|
+
return { ok: true, missing: [], referenced: [], checkedTools: [], compact: true };
|
|
461
|
+
}
|
|
462
|
+
const referenced = agentsMdSkillRefs(agentsText).filter((name) => skillNames.has(name));
|
|
463
|
+
const missing = [];
|
|
464
|
+
const checkedTools = [];
|
|
465
|
+
for (const tool of tools) {
|
|
466
|
+
const target = SKILL_TOOL_TARGETS[tool];
|
|
467
|
+
if (!target) continue;
|
|
468
|
+
checkedTools.push(tool);
|
|
469
|
+
for (const name of referenced) {
|
|
470
|
+
const relativePath = target(name);
|
|
471
|
+
if (!fs.existsSync(path.join(root, relativePath))) {
|
|
472
|
+
missing.push({ tool, name, path: relativePath });
|
|
473
|
+
}
|
|
281
474
|
}
|
|
282
475
|
}
|
|
283
|
-
return
|
|
476
|
+
return { ok: missing.length === 0, missing, referenced, checkedTools };
|
|
284
477
|
}
|
|
285
478
|
|
|
286
479
|
export function detectSkillGaps(root) {
|
|
@@ -325,7 +518,94 @@ export function detectSkillGaps(root) {
|
|
|
325
518
|
if (installed === null || isVersionOlder(installed, version)) stale += 1;
|
|
326
519
|
}
|
|
327
520
|
}
|
|
328
|
-
|
|
521
|
+
let legacyPromptsOnly = false;
|
|
522
|
+
let hasLegacyPrompts = false;
|
|
523
|
+
if (tool === 'codex') {
|
|
524
|
+
const legacyCount = skillNames.filter((name) =>
|
|
525
|
+
fs.existsSync(path.join(root, '.codex', 'prompts', `${name}.md`))
|
|
526
|
+
).length;
|
|
527
|
+
hasLegacyPrompts = legacyCount > 0;
|
|
528
|
+
// Flat prompts without any SKILL.md catalog entries are not loadable.
|
|
529
|
+
legacyPromptsOnly = hasLegacyPrompts && missing === skillNames.length;
|
|
530
|
+
}
|
|
531
|
+
if (missing > 0 || stale > 0 || legacyPromptsOnly) {
|
|
532
|
+
gaps.push({
|
|
533
|
+
tool,
|
|
534
|
+
missing,
|
|
535
|
+
stale,
|
|
536
|
+
...(legacyPromptsOnly ? { legacyPromptsOnly: true } : {}),
|
|
537
|
+
...(hasLegacyPrompts ? { hasLegacyPrompts: true } : {}),
|
|
538
|
+
});
|
|
539
|
+
}
|
|
329
540
|
}
|
|
330
541
|
return gaps;
|
|
331
542
|
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* Human-facing skill / Codex catalog gap lines for ark-check (non-JSON).
|
|
546
|
+
* @param {string} root
|
|
547
|
+
* @param {{ skillGaps: object[], codexHomeGap: object|null, codexRepoSkillGap: object|null, codexSessionActive: boolean, color: { dim: Function, yellow: Function } }} opts
|
|
548
|
+
*/
|
|
549
|
+
export function printSkillAndCodexGapHints(root, opts) {
|
|
550
|
+
const { skillGaps, codexHomeGap, codexRepoSkillGap, codexSessionActive, color } = opts;
|
|
551
|
+
if (skillGaps?.length > 0) {
|
|
552
|
+
const legacyCodex = skillGaps.some((gap) => gap.tool === 'codex' && gap.legacyPromptsOnly);
|
|
553
|
+
// Report Codex legacy separately; never suppress missing/stale for other hosts.
|
|
554
|
+
const remaining = skillGaps.filter((gap) => !(gap.tool === 'codex' && gap.legacyPromptsOnly));
|
|
555
|
+
const missingTotal = remaining.reduce((sum, gap) => sum + gap.missing, 0);
|
|
556
|
+
const staleTotal = remaining.reduce((sum, gap) => sum + gap.stale, 0);
|
|
557
|
+
const tools = remaining.map((gap) => gap.tool).join(', ');
|
|
558
|
+
if (legacyCodex) {
|
|
559
|
+
console.log(
|
|
560
|
+
color.yellow(
|
|
561
|
+
'Codex has legacy flat .codex/prompts/ark-*.md only — those are not loadable as skills. ' +
|
|
562
|
+
`Install the real catalog: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --tools codex --force')}`
|
|
563
|
+
)
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
if (missingTotal > 0) {
|
|
567
|
+
console.log(
|
|
568
|
+
color.dim(
|
|
569
|
+
`${missingTotal} /ark-* skill(s) not installed for ${tools} (this Ark version ships them). ` +
|
|
570
|
+
`Install: ${arkCommand(root, 'ark-check', '--install-agent-gates')}`
|
|
571
|
+
)
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
if (staleTotal > 0) {
|
|
575
|
+
console.log(
|
|
576
|
+
color.dim(
|
|
577
|
+
`${staleTotal} /ark-* skill(s) outdated for ${tools} (this Ark ships newer versions). ` +
|
|
578
|
+
`Refresh: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --force')}`
|
|
579
|
+
)
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (codexHomeGap) {
|
|
584
|
+
const parts = [];
|
|
585
|
+
if (codexHomeGap.legacyPromptsOnly) parts.push('legacy-prompts-only');
|
|
586
|
+
if (codexHomeGap.missing > 0) parts.push(`${codexHomeGap.missing} missing`);
|
|
587
|
+
if (codexHomeGap.stale > 0) parts.push(`${codexHomeGap.stale} outdated`);
|
|
588
|
+
const deferred = !codexSessionActive;
|
|
589
|
+
const deferredNote = deferred
|
|
590
|
+
? ' Deferred unless you use Codex — not a blocker for Grok/Claude/Cursor. '
|
|
591
|
+
: ' ';
|
|
592
|
+
const msg =
|
|
593
|
+
`Codex home skill catalog (${codexSkillsDir()}) behind this Ark (${parts.join(', ')}).` +
|
|
594
|
+
deferredNote +
|
|
595
|
+
`Catalog is $CODEX_HOME/skills/<name>/SKILL.md (not flat prompts). ` +
|
|
596
|
+
`When using Codex: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --codex-home --force')}`;
|
|
597
|
+
console.log(deferred ? color.dim(msg) : color.yellow(msg));
|
|
598
|
+
}
|
|
599
|
+
if (codexRepoSkillGap && codexSessionActive) {
|
|
600
|
+
const parts = [];
|
|
601
|
+
if (codexRepoSkillGap.legacyPromptsOnly) parts.push('legacy-prompts-only');
|
|
602
|
+
if (codexRepoSkillGap.missing > 0) parts.push(`${codexRepoSkillGap.missing} missing`);
|
|
603
|
+
if (codexRepoSkillGap.stale > 0) parts.push(`${codexRepoSkillGap.stale} outdated`);
|
|
604
|
+
console.log(
|
|
605
|
+
color.yellow(
|
|
606
|
+
`Codex repo skill catalog (.agents/skills) needs refresh (${parts.join(', ')}). ` +
|
|
607
|
+
`Fix: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --tools codex --force')}`
|
|
608
|
+
)
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
}
|
package/bin/lib/weakest-link.mjs
CHANGED
|
@@ -48,6 +48,30 @@ export function detectPreCommitArk(root) {
|
|
|
48
48
|
return { present, arkAware, path: hit };
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Classify ark-check flags in a workflow or package script body.
|
|
53
|
+
* CLI: `--strict` and `--strict-merge` both set strictConfig + requireGates (fail-closed).
|
|
54
|
+
* `--strict-config` alone does not require gate files.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} text
|
|
57
|
+
* @returns {{ hasFailClosedFlag: boolean, hasStrictConfigOnly: boolean, hasStrictFlag: boolean }}
|
|
58
|
+
*/
|
|
59
|
+
export function classifyArkCheckFlags(text) {
|
|
60
|
+
if (!text || typeof text !== 'string') {
|
|
61
|
+
return { hasFailClosedFlag: false, hasStrictConfigOnly: false, hasStrictFlag: false };
|
|
62
|
+
}
|
|
63
|
+
const hasStrictMerge = /--strict-merge\b/.test(text);
|
|
64
|
+
const hasRequireGates = /--require-gates\b/.test(text);
|
|
65
|
+
// Bare --strict (alias of --strict-merge), not --strict-config / already-matched merge.
|
|
66
|
+
const withoutLong = text.replace(/--strict-merge\b/g, ' ').replace(/--strict-config\b/g, ' ');
|
|
67
|
+
const hasBareStrict = /--strict\b/.test(withoutLong);
|
|
68
|
+
const hasFailClosedFlag = hasStrictMerge || hasRequireGates || hasBareStrict;
|
|
69
|
+
const hasStrictConfig = /--strict-config\b/.test(text);
|
|
70
|
+
const hasStrictConfigOnly = hasStrictConfig && !hasFailClosedFlag;
|
|
71
|
+
const hasStrictFlag = hasFailClosedFlag || hasStrictConfigOnly;
|
|
72
|
+
return { hasFailClosedFlag, hasStrictConfigOnly, hasStrictFlag };
|
|
73
|
+
}
|
|
74
|
+
|
|
51
75
|
/**
|
|
52
76
|
* @param {string} root
|
|
53
77
|
* @returns {{
|
|
@@ -56,6 +80,9 @@ export function detectPreCommitArk(root) {
|
|
|
56
80
|
* arkWorkflowFiles: string[],
|
|
57
81
|
* hasArkCheckWorkflow: boolean,
|
|
58
82
|
* hasStrictFlag: boolean,
|
|
83
|
+
* hasFailClosedFlag: boolean,
|
|
84
|
+
* hasStrictConfigOnly: boolean,
|
|
85
|
+
* failClosed: boolean,
|
|
59
86
|
* hasArchitectureJobName: boolean,
|
|
60
87
|
* }}
|
|
61
88
|
*/
|
|
@@ -67,6 +94,9 @@ export function detectCiEnforcement(root) {
|
|
|
67
94
|
arkWorkflowFiles: [],
|
|
68
95
|
hasArkCheckWorkflow: false,
|
|
69
96
|
hasStrictFlag: false,
|
|
97
|
+
hasFailClosedFlag: false,
|
|
98
|
+
hasStrictConfigOnly: false,
|
|
99
|
+
failClosed: false,
|
|
70
100
|
hasArchitectureJobName: false,
|
|
71
101
|
};
|
|
72
102
|
if (!out.hasWorkflowsDir) return out;
|
|
@@ -77,6 +107,19 @@ export function detectCiEnforcement(root) {
|
|
|
77
107
|
return out;
|
|
78
108
|
}
|
|
79
109
|
out.workflowFiles = files;
|
|
110
|
+
|
|
111
|
+
let checkArchScript = '';
|
|
112
|
+
try {
|
|
113
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
|
|
114
|
+
checkArchScript =
|
|
115
|
+
typeof pkg?.scripts?.['check:architecture'] === 'string'
|
|
116
|
+
? pkg.scripts['check:architecture']
|
|
117
|
+
: '';
|
|
118
|
+
} catch {
|
|
119
|
+
checkArchScript = '';
|
|
120
|
+
}
|
|
121
|
+
const scriptFlags = classifyArkCheckFlags(checkArchScript);
|
|
122
|
+
|
|
80
123
|
for (const f of files) {
|
|
81
124
|
let text = '';
|
|
82
125
|
try {
|
|
@@ -92,9 +135,19 @@ export function detectCiEnforcement(root) {
|
|
|
92
135
|
if (mentionsArk) {
|
|
93
136
|
out.hasArkCheckWorkflow = true;
|
|
94
137
|
out.arkWorkflowFiles.push(`.github/workflows/${f}`);
|
|
95
|
-
|
|
138
|
+
const flags = classifyArkCheckFlags(text);
|
|
139
|
+
const viaScript = /check:architecture/.test(text) && scriptFlags.hasFailClosedFlag;
|
|
140
|
+
if (flags.hasFailClosedFlag || viaScript) {
|
|
141
|
+
out.hasFailClosedFlag = true;
|
|
142
|
+
out.failClosed = true;
|
|
143
|
+
out.hasStrictFlag = true;
|
|
144
|
+
} else if (flags.hasStrictConfigOnly) {
|
|
145
|
+
out.hasStrictConfigOnly = true;
|
|
146
|
+
out.hasStrictFlag = true;
|
|
147
|
+
} else if (flags.hasStrictFlag) {
|
|
96
148
|
out.hasStrictFlag = true;
|
|
97
149
|
}
|
|
150
|
+
// check:architecture without fail-closed flags in the script is NOT fail-closed.
|
|
98
151
|
if (/architecture|ark-check|arkgate-check/i.test(f) || /name:\s*.*ark/i.test(text)) {
|
|
99
152
|
out.hasArchitectureJobName = true;
|
|
100
153
|
}
|
|
@@ -339,18 +392,14 @@ export function collectWeakestLinkGaps(root, opts = {}) {
|
|
|
339
392
|
'CI workflows exist but none run ark-check / arkgate-check / check:architecture',
|
|
340
393
|
fix: arkCommand(root, 'ark-check', '--install-agent-gates'),
|
|
341
394
|
});
|
|
342
|
-
} else if (
|
|
343
|
-
adopted &&
|
|
344
|
-
!isProducer &&
|
|
345
|
-
ci.hasArkCheckWorkflow &&
|
|
346
|
-
!ci.hasStrictFlag
|
|
347
|
-
) {
|
|
395
|
+
} else if (adopted && !isProducer && ci.hasArkCheckWorkflow && !ci.failClosed) {
|
|
348
396
|
gaps.push({
|
|
349
|
-
id: 'enforcement-ci-not-
|
|
350
|
-
severity: '
|
|
351
|
-
message:
|
|
352
|
-
'Architecture CI
|
|
353
|
-
|
|
397
|
+
id: 'enforcement-ci-not-fail-closed',
|
|
398
|
+
severity: 'warn',
|
|
399
|
+
message: ci.hasStrictConfigOnly
|
|
400
|
+
? 'Architecture CI uses --strict-config only (config coverage without gate-file presence). Prefer the fail-closed profile.'
|
|
401
|
+
: 'Architecture CI job found but does not use the fail-closed profile (--strict-merge / --strict / --require-gates)',
|
|
402
|
+
fix: 'ark-check --root . --config ark.config.json --strict-merge --baseline .ark-baseline.json',
|
|
354
403
|
});
|
|
355
404
|
}
|
|
356
405
|
|
|
@@ -121,7 +121,9 @@ function hostRecord(hard, advisory, repair, merge) {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
export function detectWritePathInventory(root) {
|
|
124
|
-
|
|
124
|
+
// Merge-gate evidence only when CI uses the fail-closed profile (not bare ark-check).
|
|
125
|
+
const ci = detectCiEnforcement(root);
|
|
126
|
+
const merge = ci.failClosed ? ci.arkWorkflowFiles : [];
|
|
125
127
|
const claudeHook = hookEvidence(root, '.claude/settings.json');
|
|
126
128
|
const grokHook = hookEvidence(root, '.grok/hooks/ark-write-gate.json');
|
|
127
129
|
const hosts = {
|
|
@@ -72,17 +72,23 @@ export function detectWritePathCapabilities(root, explicitHost) {
|
|
|
72
72
|
),
|
|
73
73
|
};
|
|
74
74
|
} else if (mode === 'mcp-only') {
|
|
75
|
+
const codexHonesty =
|
|
76
|
+
activeHost === 'codex'
|
|
77
|
+
? 'Codex local write is advisory (MCP + best-effort hooks.json — not a hard boundary; ' +
|
|
78
|
+
'not equivalent to Claude/Grok PreToolUse hard-write + repair). ' +
|
|
79
|
+
'The hard merge backstop is CI --strict-merge plus a required status check.'
|
|
80
|
+
: `Active host ${activeHost} has advisory prepare-write/autoPatch tools, ` +
|
|
81
|
+
'but no hard write boundary; the CI check can still reject the change before merge.';
|
|
75
82
|
gap = {
|
|
76
83
|
id: 'write-path-mcp-only',
|
|
77
84
|
severity: 'info',
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
),
|
|
85
|
+
host: activeHost,
|
|
86
|
+
message: codexHonesty,
|
|
87
|
+
fix:
|
|
88
|
+
activeHost === 'codex'
|
|
89
|
+
? 'Keep CI on --strict-merge and require the ark-check status on the default branch; ' +
|
|
90
|
+
`refresh Codex MCP/skills with ${arkCommand(root, 'ark-check', '--install-agent-gates --tools codex')}`
|
|
91
|
+
: arkCommand(root, 'ark-check', `--install-agent-gates --tools ${tools}`),
|
|
86
92
|
};
|
|
87
93
|
}
|
|
88
94
|
|
package/dist/index.cjs
CHANGED
package/dist/index.d.cts
CHANGED
|
@@ -2,7 +2,7 @@ import { a as ArkConfigRule, b as ArkConfigLayer, A as ArkConfig, c as ArkConfig
|
|
|
2
2
|
export { d as ARK_CONFIG_SCHEMA, e as ARK_CONFIG_SCHEMA_VERSION, l as loadArkConfigContract, p as parseArkConfigJson } from './configContract-BxSIwVRo.cjs';
|
|
3
3
|
|
|
4
4
|
/** ArkGate library version — single source of truth. */
|
|
5
|
-
declare const version = "3.0.
|
|
5
|
+
declare const version = "3.0.5";
|
|
6
6
|
|
|
7
7
|
/** Versioned public result contract shared by every ArkGate enforcement adapter. */
|
|
8
8
|
declare const ARK_ANALYSIS_RESULT_SCHEMA_VERSION: "1.0";
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { a as ArkConfigRule, b as ArkConfigLayer, A as ArkConfig, c as ArkConfig
|
|
|
2
2
|
export { d as ARK_CONFIG_SCHEMA, e as ARK_CONFIG_SCHEMA_VERSION, l as loadArkConfigContract, p as parseArkConfigJson } from './configContract-BxSIwVRo.js';
|
|
3
3
|
|
|
4
4
|
/** ArkGate library version — single source of truth. */
|
|
5
|
-
declare const version = "3.0.
|
|
5
|
+
declare const version = "3.0.5";
|
|
6
6
|
|
|
7
7
|
/** Versioned public result contract shared by every ArkGate enforcement adapter. */
|
|
8
8
|
declare const ARK_ANALYSIS_RESULT_SCHEMA_VERSION: "1.0";
|
package/dist/index.js
CHANGED
package/docs/agent-guide.md
CHANGED
|
@@ -307,7 +307,7 @@ npx arkgate-check --install-agent-gates --tools claude,cursor,codex,grok
|
|
|
307
307
|
|------|-----------------|-------------|
|
|
308
308
|
| Claude Code | `.claude/settings.json` hook + `.mcp.json` / `claude mcp add` | `.claude/skills/<name>/SKILL.md` |
|
|
309
309
|
| Cursor | `.cursor/mcp.json` + `.cursor/rules/ark.mdc` | `.cursor/commands/` |
|
|
310
|
-
| OpenAI Codex | `$CODEX_HOME/config.toml` (global; absolute `--root`; multi-project → secondary `ark_<slug>` unless `--force`; doctor defers non-temp home gaps when session host ≠ Codex — see [ai-gates.md](ai-gates.md)) | `$CODEX_HOME/
|
|
310
|
+
| OpenAI Codex | `$CODEX_HOME/config.toml` (global; absolute `--root`; multi-project → secondary `ark_<slug>` unless `--force`; doctor defers non-temp home gaps when session host ≠ Codex — see [ai-gates.md](ai-gates.md)) | **Repo:** `.agents/skills/<name>/SKILL.md`; **home:** `$CODEX_HOME/skills/<name>/SKILL.md` (`--codex-home`) |
|
|
311
311
|
| **Grok Build** | `.grok/hooks/ark-write-gate.json` + `.grok/config.toml` / `.mcp.json` | `.grok/skills/<name>/SKILL.md` |
|
|
312
312
|
|
|
313
313
|
This is a path reference, not a guarantee table. Full copy-paste setups:
|
package/docs/ai-gates.md
CHANGED
|
@@ -268,7 +268,7 @@ Hand-editing with relative `--root .` is wrong: Codex does not use the project a
|
|
|
268
268
|
|
|
269
269
|
```bash
|
|
270
270
|
npx ark-check --install-agent-gates --tools codex
|
|
271
|
-
# optional: install /ark-*
|
|
271
|
+
# optional: install /ark-* skills into $CODEX_HOME/skills/<name>/SKILL.md
|
|
272
272
|
npx ark-check --install-agent-gates --codex-home
|
|
273
273
|
```
|
|
274
274
|
|
|
@@ -309,11 +309,36 @@ primary A. It writes a **scoped secondary** table:
|
|
|
309
309
|
|
|
310
310
|
`ark-check --doctor` surfaces the multi-project state so you are not left thinking B owns
|
|
311
311
|
`ark://manifest` when only a secondary table exists. **Deferred (fix when using Codex):**
|
|
312
|
-
non-temp Codex-home gaps (`codex-home-multi-project`, stale `$CODEX_HOME/
|
|
312
|
+
non-temp Codex-home gaps (`codex-home-multi-project`, stale `$CODEX_HOME/skills`) are
|
|
313
313
|
severity **info**, marked `deferred: true`, and omitted from Top actions when the session
|
|
314
314
|
host is known and not Codex — `/ark-upgrade` on Grok/Claude is not Incomplete because of
|
|
315
315
|
them. **Temp/upgrade primary roots** stay fail-closed urgent (rewritten, not multi-project).
|
|
316
316
|
|
|
317
|
+
### Codex skill catalog (SKILL.md, not flat prompts)
|
|
318
|
+
|
|
319
|
+
Codex discovers skills as directories containing `SKILL.md` (Agent Skills standard):
|
|
320
|
+
|
|
321
|
+
| Scope | Path |
|
|
322
|
+
|-------|------|
|
|
323
|
+
| **Repo** (written by `--tools codex`) | `.agents/skills/<name>/SKILL.md` |
|
|
324
|
+
| **Home** (optional `--codex-home`) | `$CODEX_HOME/skills/<name>/SKILL.md` |
|
|
325
|
+
|
|
326
|
+
Flat `.codex/prompts/*.md` files are **not** the invocable skill catalog. Install writes the
|
|
327
|
+
repo catalog above so AGENTS.md `/ark-*` references match what Codex can load. After install,
|
|
328
|
+
Ark verifies those references against each selected host catalog.
|
|
329
|
+
|
|
330
|
+
**Parity & honesty (doctor / install):**
|
|
331
|
+
|
|
332
|
+
- Doctor distinguishes **missing / stale / legacy-prompts-only** for repo (`.agents/skills`) and
|
|
333
|
+
home (`$CODEX_HOME/skills`). Home debt is **deferred** when the session host is not Codex.
|
|
334
|
+
- Legacy flat prompts alone are reported as non-loadable skill debt with a
|
|
335
|
+
`--skills-only --tools codex --force` (repo) or `--codex-home --force` (home) fix.
|
|
336
|
+
- Codex **write path is advisory**: MCP + best-effort `.codex/hooks.json` is **not** a hard
|
|
337
|
+
write boundary and is **not** equivalent to Claude/Grok PreToolUse hard-write + repair.
|
|
338
|
+
The hard merge backstop is CI `--strict-merge` (or `--strict`) plus a required status check.
|
|
339
|
+
- CI workflows that run ark-check without the fail-closed profile (or with only
|
|
340
|
+
`--strict-config`) surface gap `enforcement-ci-not-fail-closed`.
|
|
341
|
+
|
|
317
342
|
## Grok Build (xAI)
|
|
318
343
|
|
|
319
344
|
Grok reads project rules from **`AGENTS.md`**, project MCP from **`.grok/config.toml`**
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/pedroknigge/arkgate",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "3.0.
|
|
9
|
+
"version": "3.0.5",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "arkgate",
|
|
14
|
-
"version": "3.0.
|
|
14
|
+
"version": "3.0.5",
|
|
15
15
|
"runtimeHint": "npx",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
@@ -123,11 +123,15 @@ npx arkgate-check --install-agent-gates --skills-only --force
|
|
|
123
123
|
**Active host first.** Refresh skills for the host running this skill (e.g.
|
|
124
124
|
`.grok/skills/`, `.claude/skills/`, `.cursor/commands/`). Repo-local copies for
|
|
125
125
|
other detected hosts are fine to refresh in the same pass when cheap.
|
|
126
|
-
**Codex is deferred when you are not on Codex.**
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
126
|
+
**Codex is deferred when you are not on Codex.** Repo skills live in
|
|
127
|
+
`.agents/skills/<name>/SKILL.md`; optional home skills in
|
|
128
|
+
`$CODEX_HOME/skills/<name>/SKILL.md` (not legacy flat `$CODEX_HOME/prompts`).
|
|
129
|
+
When **on Codex**, refresh **both** repo catalog and home if doctor reports
|
|
130
|
+
missing/stale/legacy-prompts-only parity gaps. `ark upgrade` may best-effort
|
|
131
|
+
refresh home when it exists; still list Codex under **Deferred hosts** when
|
|
132
|
+
not on Codex and do **not** chase MCP multi-project / stale home skills until
|
|
133
|
+
the user is on Codex (or asks). Fix when needed:
|
|
134
|
+
`ark-check --install-agent-gates --skills-only --tools codex --force` and/or
|
|
131
135
|
`ark-check --install-agent-gates --skills-only --codex-home --force`
|
|
132
136
|
(and `--tools codex` / `--force` for primary MCP rebind). Exception: temp or
|
|
133
137
|
`ark-upgrade` MCP `--root` paths — leave fail-closed rewrite to the CLI; do not
|