arkgate 3.0.3 → 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 +69 -1
- package/bin/ark-check.mjs +33 -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/html-report-depth.mjs +282 -0
- package/bin/lib/html-report.mjs +214 -21
- package/bin/lib/install-migrate.mjs +81 -25
- package/bin/lib/mcp-adoption.mjs +8 -0
- package/bin/lib/skill-install.mjs +304 -23
- package/bin/lib/weakest-link.mjs +61 -12
- package/bin/lib/write-path-capabilities.mjs +3 -1
- package/bin/lib/write-path-detect.mjs +39 -22
- 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,75 @@ 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`.
|
|
43
|
+
|
|
44
|
+
## 3.0.4 — 2026-07-14
|
|
45
|
+
|
|
46
|
+
Report honesty + showcase depth patch. **No breaking** CLI or `ark.config.json` changes.
|
|
47
|
+
**No gate weaken.**
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- **HTML report false ADAPT:** `computeReportFitness` counted *any* `optional: true` layer
|
|
52
|
+
with files as `coreOptionalWithFiles`, so doctor could report **ENFORCE** while
|
|
53
|
+
`ark-report.html` / `latest.json` mode stayed **ADAPT** (secondary layers like
|
|
54
|
+
SharedKernel / Integration / Workflow). Report now uses the same `CORE_LAYER_NAMES`
|
|
55
|
+
filter as doctor adoption (`DomainModel`, `ApplicationOrchestration`,
|
|
56
|
+
`PresentationAdapters`, `PersistenceAdapters`).
|
|
57
|
+
- **False adoption gap `write-path-none` on report/CI:** when `activeHost` is `unknown`
|
|
58
|
+
(plain `npx ark-check --report` outside an agent session) but the repo inventory already
|
|
59
|
+
has hard-write hooks or advisory MCP for Claude/Grok/Cursor/Codex, doctor/report no longer
|
|
60
|
+
open a `write-path-none` adoption gap. Session projection still reports `mode: none` for
|
|
61
|
+
honesty (other hosts' hooks are not a guarantee for this process). `detectActiveAgentHost`
|
|
62
|
+
also recognizes `GROK_AGENT`.
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
|
|
66
|
+
- **Report metric hints:** HTML showcase KPIs (hero, adoption, contract density, debt) show
|
|
67
|
+
plain-language micro-copy under each tile plus native tooltips; PASS/mode badges and score
|
|
68
|
+
parts (Coverage/Clean/Gates/Rules) explain what they mean for newcomers.
|
|
69
|
+
- **Report design-depth strip:** `ark-check --report` includes doctor-parity Shape residual
|
|
70
|
+
(design-weak badge, smell outcomes, one next pilot, post-green door, optional golden pattern).
|
|
71
|
+
Clean ENFORCE with no smells shows a short “Design depth · OK” note (only when sensors ran).
|
|
72
|
+
- **Report adoption extras:** write-path line (active host · mode · inventory on disk) and a
|
|
73
|
+
fixed baseline-policy legend (`keep-empty` / `active-ratchet` / `absent`).
|
|
74
|
+
|
|
75
|
+
Release note: `docs/releases/3.0.4.md`.
|
|
8
76
|
|
|
9
77
|
## 3.0.3 — 2026-07-13
|
|
10
78
|
|
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';
|
|
@@ -58,6 +59,7 @@ import {
|
|
|
58
59
|
reportsDir,
|
|
59
60
|
readJsonSafe,
|
|
60
61
|
} from './lib/html-report.mjs';
|
|
62
|
+
import { buildReportDepthPayload } from './lib/html-report-depth.mjs';
|
|
61
63
|
import { shouldOpenHtmlReport, openHtmlInBrowser } from './lib/open-html.mjs';
|
|
62
64
|
import {
|
|
63
65
|
computeCoverage,
|
|
@@ -300,8 +302,8 @@ function usage() {
|
|
|
300
302
|
'(instruction-tier rule files derived from the same contract).',
|
|
301
303
|
'It also installs the /ark-* skills shipped in templates/skills/ into each',
|
|
302
304
|
'detected tool\'s command location (.claude/skills/, .cursor/commands/,',
|
|
303
|
-
'.
|
|
304
|
-
'.github/prompts/).',
|
|
305
|
+
'.agents/skills/ (Codex REPO catalog), .grok/skills/, .windsurf/workflows/,',
|
|
306
|
+
'.clinerules/workflows/, .github/prompts/).',
|
|
305
307
|
'Kiro, Roo, Continue, and Gemini have no command mechanism and receive only their',
|
|
306
308
|
'rule file. Existing files are never overwritten without --force, so re-running',
|
|
307
309
|
'after an update only adds what is missing. --skills-only restricts the write to',
|
|
@@ -1228,6 +1230,8 @@ async function main() {
|
|
|
1228
1230
|
|
|
1229
1231
|
const skillGaps = detectSkillGaps(root);
|
|
1230
1232
|
const codexHomeGap = detectCodexHomeGap(root);
|
|
1233
|
+
const codexRepoSkillGap = detectCodexRepoSkillGap(root);
|
|
1234
|
+
const codexSessionActive = codexConcernIsActive();
|
|
1231
1235
|
|
|
1232
1236
|
if (args.report) {
|
|
1233
1237
|
const exampleByLayer = new Map();
|
|
@@ -1266,6 +1270,13 @@ async function main() {
|
|
|
1266
1270
|
const existingOrigin = args.resetOrigin
|
|
1267
1271
|
? null
|
|
1268
1272
|
: readJsonSafe(path.join(reportsDir(root), 'origin.json'));
|
|
1273
|
+
const { adoption: adoptionForReport, designDepth } = buildReportDepthPayload(
|
|
1274
|
+
root,
|
|
1275
|
+
config,
|
|
1276
|
+
files,
|
|
1277
|
+
coverage,
|
|
1278
|
+
activeViolations
|
|
1279
|
+
);
|
|
1269
1280
|
const reportPayload = {
|
|
1270
1281
|
root,
|
|
1271
1282
|
config,
|
|
@@ -1282,6 +1293,8 @@ async function main() {
|
|
|
1282
1293
|
originSnapshot: existingOrigin,
|
|
1283
1294
|
currentSnapshot,
|
|
1284
1295
|
originJustCreated: !existingOrigin,
|
|
1296
|
+
adoption: adoptionForReport,
|
|
1297
|
+
designDepth,
|
|
1285
1298
|
};
|
|
1286
1299
|
const html = args.beginner
|
|
1287
1300
|
? renderBeginnerHtmlReport(reportPayload)
|
|
@@ -1362,7 +1375,15 @@ async function main() {
|
|
|
1362
1375
|
warnings,
|
|
1363
1376
|
...(activeViolations.length > 0 ? { summary: summarizeViolations(activeViolations) } : {}),
|
|
1364
1377
|
...(skillGaps.length > 0 ? { skillGaps } : {}),
|
|
1365
|
-
...(codexHomeGap
|
|
1378
|
+
...(codexHomeGap
|
|
1379
|
+
? {
|
|
1380
|
+
codexHomeGap: {
|
|
1381
|
+
...codexHomeGap,
|
|
1382
|
+
deferred: !codexSessionActive,
|
|
1383
|
+
},
|
|
1384
|
+
}
|
|
1385
|
+
: {}),
|
|
1386
|
+
...(codexRepoSkillGap ? { codexRepoSkillGap } : {}),
|
|
1366
1387
|
}, null, 2));
|
|
1367
1388
|
} else {
|
|
1368
1389
|
for (const warning of warnings) {
|
|
@@ -1410,30 +1431,13 @@ async function main() {
|
|
|
1410
1431
|
printViolationBreakdown(summarizeViolations(activeViolations), { toStderr: true });
|
|
1411
1432
|
}
|
|
1412
1433
|
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
`${missingTotal} /ark-* skill(s) not installed for ${tools} (this Ark version ships them). ` +
|
|
1421
|
-
`Install: ${arkCommand(root, 'ark-check', '--install-agent-gates')}`
|
|
1422
|
-
)
|
|
1423
|
-
);
|
|
1424
|
-
}
|
|
1425
|
-
if (staleTotal > 0) {
|
|
1426
|
-
// Stale skills already exist, so refreshing needs --force. --skills-only
|
|
1427
|
-
// scopes the overwrite to the canonical skills, leaving a customized
|
|
1428
|
-
// AGENTS.md / settings / CI untouched (a bare --force would clobber them).
|
|
1429
|
-
console.log(
|
|
1430
|
-
color.dim(
|
|
1431
|
-
`${staleTotal} /ark-* skill(s) outdated for ${tools} (this Ark ships newer versions). ` +
|
|
1432
|
-
`Refresh: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --force')}`
|
|
1433
|
-
)
|
|
1434
|
-
);
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1434
|
+
printSkillAndCodexGapHints(root, {
|
|
1435
|
+
skillGaps,
|
|
1436
|
+
codexHomeGap,
|
|
1437
|
+
codexRepoSkillGap,
|
|
1438
|
+
codexSessionActive,
|
|
1439
|
+
color,
|
|
1440
|
+
});
|
|
1437
1441
|
|
|
1438
1442
|
const staleRunners = staleRunnerGateFiles(root);
|
|
1439
1443
|
if (staleRunners.length > 0) {
|
|
@@ -1454,27 +1458,6 @@ async function main() {
|
|
|
1454
1458
|
)
|
|
1455
1459
|
);
|
|
1456
1460
|
}
|
|
1457
|
-
|
|
1458
|
-
if (codexHomeGap) {
|
|
1459
|
-
const parts = [];
|
|
1460
|
-
if (codexHomeGap.missing > 0) parts.push(`${codexHomeGap.missing} missing`);
|
|
1461
|
-
if (codexHomeGap.stale > 0) parts.push(`${codexHomeGap.stale} outdated`);
|
|
1462
|
-
// Advisory always; when session host is known and not Codex, say so so
|
|
1463
|
-
// /ark-upgrade does not chase home prompts as Incomplete.
|
|
1464
|
-
const activeHost = detectActiveAgentHost();
|
|
1465
|
-
const deferredNote =
|
|
1466
|
-
activeHost != null && activeHost !== 'codex'
|
|
1467
|
-
? ' Deferred unless you use Codex — not a blocker for Grok/Claude/Cursor. '
|
|
1468
|
-
: ' ';
|
|
1469
|
-
console.log(
|
|
1470
|
-
color.dim(
|
|
1471
|
-
`/ark-* skills in ${codexPromptsDir()} are behind this Ark (${parts.join(', ')}).` +
|
|
1472
|
-
deferredNote +
|
|
1473
|
-
`Codex loads them from $CODEX_HOME/prompts, not the repo. ` +
|
|
1474
|
-
`When using Codex: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --codex-home --force')}`
|
|
1475
|
-
)
|
|
1476
|
-
);
|
|
1477
|
-
}
|
|
1478
1461
|
}
|
|
1479
1462
|
|
|
1480
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'));
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design-depth + adoption extras for the HTML showcase report.
|
|
3
|
+
* Kept separate from html-report.mjs so the main renderer stays under LOC budget.
|
|
4
|
+
* Does not import html-report.mjs (avoids a cycle).
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
detectDesignSmells,
|
|
8
|
+
summarizeDesignFitness,
|
|
9
|
+
buildPatternBetsFromSmells,
|
|
10
|
+
} from './design-smells.mjs';
|
|
11
|
+
import { summarizePilotLoop } from './pilot-loop.mjs';
|
|
12
|
+
import { buildPostGreenNextAction } from './post-green-path.mjs';
|
|
13
|
+
import { loadGoldenPattern, summarizeGoldenPattern } from './golden-pattern.mjs';
|
|
14
|
+
import { collectAdoptionGaps } from './mcp-adoption.mjs';
|
|
15
|
+
|
|
16
|
+
function esc(value) {
|
|
17
|
+
return String(value)
|
|
18
|
+
.replace(/&/g, '&')
|
|
19
|
+
.replace(/</g, '<')
|
|
20
|
+
.replace(/>/g, '>')
|
|
21
|
+
.replace(/"/g, '"');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Doctor-parity design depth + adoption for ark-check --report.
|
|
26
|
+
* @param {string} root
|
|
27
|
+
* @param {object} config
|
|
28
|
+
* @param {string[]} files
|
|
29
|
+
* @param {object} coverage
|
|
30
|
+
* @param {object[]} activeViolations
|
|
31
|
+
*/
|
|
32
|
+
export function buildReportDepthPayload(root, config, files, coverage, activeViolations = []) {
|
|
33
|
+
const designSmells = detectDesignSmells(root, config, files, coverage);
|
|
34
|
+
const designFitness = summarizeDesignFitness(designSmells, {
|
|
35
|
+
activeViolations: activeViolations.length,
|
|
36
|
+
governedPercent: coverage?.governed?.percent,
|
|
37
|
+
totalFiles: coverage?.governed?.totalFiles,
|
|
38
|
+
});
|
|
39
|
+
const postGreenPath = buildPostGreenNextAction(designFitness);
|
|
40
|
+
const patternBets = buildPatternBetsFromSmells(designSmells);
|
|
41
|
+
const pilotLoop = summarizePilotLoop({
|
|
42
|
+
designWeak: designFitness.designWeak,
|
|
43
|
+
patternBets,
|
|
44
|
+
designSmells,
|
|
45
|
+
});
|
|
46
|
+
const goldenPattern = summarizeGoldenPattern(loadGoldenPattern(root));
|
|
47
|
+
const adoption = collectAdoptionGaps(root, config, coverage);
|
|
48
|
+
return {
|
|
49
|
+
adoption,
|
|
50
|
+
designDepth: {
|
|
51
|
+
designFitness,
|
|
52
|
+
designSmells,
|
|
53
|
+
pilotLoop,
|
|
54
|
+
postGreenPath,
|
|
55
|
+
goldenPattern,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Write-path mode → human meaning (active host projection). */
|
|
61
|
+
export function writePathModeHint(mode) {
|
|
62
|
+
switch (String(mode || '')) {
|
|
63
|
+
case 'repair':
|
|
64
|
+
return 'Hard write hook with repair payload — best co-pilot path for the active host.';
|
|
65
|
+
case 'reject-only':
|
|
66
|
+
return 'Hard write boundary without repair payload; edits can be blocked without guided re-entry.';
|
|
67
|
+
case 'mcp-only':
|
|
68
|
+
return 'Advisory MCP only — prepare-write/autoPatch available, no hard PreToolUse for this host.';
|
|
69
|
+
case 'none':
|
|
70
|
+
return 'No hard write boundary or advisory MCP for the active host (or host is unknown in this process).';
|
|
71
|
+
default:
|
|
72
|
+
return 'Session write-path capability for the active agent host.';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Configured hosts from writePath inventory (hard or advisory evidence on disk).
|
|
78
|
+
* @param {object|null|undefined} writePath
|
|
79
|
+
* @returns {string[]}
|
|
80
|
+
*/
|
|
81
|
+
export function inventoryConfiguredHosts(writePath) {
|
|
82
|
+
const hosts = writePath?.inventory?.hosts;
|
|
83
|
+
if (!hosts || typeof hosts !== 'object') return [];
|
|
84
|
+
return Object.entries(hosts)
|
|
85
|
+
.filter(([, rec]) => rec && rec.configured)
|
|
86
|
+
.map(([name]) => name);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Compact write-path line for the Adoption card.
|
|
91
|
+
* @param {object|null|undefined} writePath
|
|
92
|
+
*/
|
|
93
|
+
export function renderWritePathAdoptionBlock(writePath) {
|
|
94
|
+
if (!writePath || typeof writePath !== 'object') return '';
|
|
95
|
+
const mode = writePath.mode || 'none';
|
|
96
|
+
const host = writePath.activeHost || 'unknown';
|
|
97
|
+
const inv = inventoryConfiguredHosts(writePath);
|
|
98
|
+
const invLine =
|
|
99
|
+
inv.length > 0
|
|
100
|
+
? `Inventory on disk: ${inv.map((h) => esc(h)).join(', ')}.`
|
|
101
|
+
: 'No host write gates found on disk yet.';
|
|
102
|
+
const unknownNote =
|
|
103
|
+
host === 'unknown' && inv.length > 0
|
|
104
|
+
? ' Session host unknown (shell/CI) — inventory is still real; set ARK_ACTIVE_HOST or run from an agent for session-accurate mode.'
|
|
105
|
+
: '';
|
|
106
|
+
const gapNote = writePath.gap
|
|
107
|
+
? ` Gap: <b>${esc(writePath.gap.id)}</b> — ${esc(writePath.gap.message || '')}`
|
|
108
|
+
: '';
|
|
109
|
+
// invLine / gapNote already include escaped user content; only plain strings go through esc().
|
|
110
|
+
return `<div class="write-path-block" title="${esc(writePathModeHint(mode))}">
|
|
111
|
+
<p class="dim" style="margin:.65rem 0 .2rem;font-size:.84rem">
|
|
112
|
+
<b>Write path</b> · active host <code>${esc(host)}</code>
|
|
113
|
+
· mode <code>${esc(mode)}</code>
|
|
114
|
+
${writePath.hookRepair ? '· repair ✓' : writePath.hookPresent ? '· reject-only' : ''}
|
|
115
|
+
${writePath.mcpPresent ? '· MCP ✓' : ''}
|
|
116
|
+
</p>
|
|
117
|
+
<p class="kpi-hint" style="max-width:none;margin:0">
|
|
118
|
+
${esc(writePathModeHint(mode))} ${invLine}${esc(unknownNote)}${gapNote}
|
|
119
|
+
</p>
|
|
120
|
+
</div>`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Fixed legend for baseline policy signals. */
|
|
124
|
+
export function renderBaselineSignalLegend() {
|
|
125
|
+
return `<details class="baseline-legend" style="margin-top:.75rem">
|
|
126
|
+
<summary>Baseline policy signals (legend)</summary>
|
|
127
|
+
<ul class="senior-list" style="margin-top:.4rem">
|
|
128
|
+
<li><b>keep-empty</b> — ${esc(baselineLegendBody('keep-empty'))}</li>
|
|
129
|
+
<li><b>active-ratchet</b> — ${esc(baselineLegendBody('active-ratchet'))}</li>
|
|
130
|
+
<li><b>absent</b> — ${esc(baselineLegendBody('absent'))}</li>
|
|
131
|
+
</ul>
|
|
132
|
+
</details>`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function baselineLegendBody(signal) {
|
|
136
|
+
switch (signal) {
|
|
137
|
+
case 'keep-empty':
|
|
138
|
+
return '`.ark-baseline.json` exists with 0 frozen keys; every violation is active (honest green).';
|
|
139
|
+
case 'active-ratchet':
|
|
140
|
+
return 'Known debt keys are frozen; new distinct violations still fail the check.';
|
|
141
|
+
case 'absent':
|
|
142
|
+
return 'No baseline file — all findings are active (or freeze not adopted).';
|
|
143
|
+
default:
|
|
144
|
+
return '';
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Design-weak / Shape residual strip for the showcase report.
|
|
150
|
+
* Null HTML when there is nothing useful to show.
|
|
151
|
+
*
|
|
152
|
+
* @param {{
|
|
153
|
+
* designFitness?: object|null,
|
|
154
|
+
* designSmells?: object[],
|
|
155
|
+
* pilotLoop?: object|null,
|
|
156
|
+
* postGreenPath?: object|null,
|
|
157
|
+
* goldenPattern?: object|null,
|
|
158
|
+
* mode?: string,
|
|
159
|
+
* }} depth
|
|
160
|
+
*/
|
|
161
|
+
export function renderDesignDepthStrip(depth = {}) {
|
|
162
|
+
const fitness = depth.designFitness;
|
|
163
|
+
const smells = Array.isArray(depth.designSmells) ? depth.designSmells : [];
|
|
164
|
+
const designWeak = fitness?.designWeak === true;
|
|
165
|
+
if (!designWeak && smells.length === 0) return '';
|
|
166
|
+
|
|
167
|
+
const mode = String(depth.mode || '').toLowerCase();
|
|
168
|
+
const title = designWeak
|
|
169
|
+
? mode === 'enforce'
|
|
170
|
+
? 'ENFORCE · design-weak'
|
|
171
|
+
: `${(mode || 'edges').toUpperCase()} · design-weak`
|
|
172
|
+
: 'Design smells (edges still open)';
|
|
173
|
+
const lede = designWeak
|
|
174
|
+
? 'Contract edges are clean, but lived design residual remains. This does not fail PASS — it blocks “healthy finished” until Shape work lands.'
|
|
175
|
+
: 'Design smells exist alongside open edge debt. Fix edges first; treat smells as Shape residual after green.';
|
|
176
|
+
|
|
177
|
+
const smellItems = smells
|
|
178
|
+
.slice(0, 6)
|
|
179
|
+
.map((s) => {
|
|
180
|
+
const outcome = s.outcome || s.message || s.id;
|
|
181
|
+
const evidence = (s.evidence || [])
|
|
182
|
+
.filter((e) => typeof e === 'string' && !e.startsWith('layer:') && !e.startsWith('layout:'))
|
|
183
|
+
.slice(0, 3);
|
|
184
|
+
const ev =
|
|
185
|
+
evidence.length > 0
|
|
186
|
+
? ` <span class="dim">· ${evidence.map((e) => `<code>${esc(e)}</code>`).join(' ')}</span>`
|
|
187
|
+
: '';
|
|
188
|
+
return `<li><b>${esc(s.id)}</b> — ${esc(outcome)}${ev}</li>`;
|
|
189
|
+
})
|
|
190
|
+
.join('');
|
|
191
|
+
|
|
192
|
+
const pilot = depth.pilotLoop?.active && depth.pilotLoop?.nextPilot ? depth.pilotLoop.nextPilot : null;
|
|
193
|
+
const pilotHtml = pilot
|
|
194
|
+
? `<div class="pilot-card">
|
|
195
|
+
<h3 style="margin-top:.85rem">Next pilot (one at a time)</h3>
|
|
196
|
+
<p class="dim" style="margin:.15rem 0 .4rem;font-size:.86rem">
|
|
197
|
+
Judgment only — never mechanical-safe · never multi-pilot batch
|
|
198
|
+
</p>
|
|
199
|
+
<ul class="senior-list">
|
|
200
|
+
<li><b>Smell</b> · <code>${esc(pilot.smellId || pilot.id || '—')}</code></li>
|
|
201
|
+
<li><b>Target</b> · <code>${esc(pilot.pilotTarget || pilot.pilot || '—')}</code></li>
|
|
202
|
+
${
|
|
203
|
+
pilot.move || pilot.fix
|
|
204
|
+
? `<li><b>Move</b> · ${esc(pilot.move || pilot.fix)}</li>`
|
|
205
|
+
: ''
|
|
206
|
+
}
|
|
207
|
+
${
|
|
208
|
+
pilot.successSignal
|
|
209
|
+
? `<li><b>Success</b> · ${esc(pilot.successSignal)}</li>`
|
|
210
|
+
: ''
|
|
211
|
+
}
|
|
212
|
+
${
|
|
213
|
+
pilot.killSwitch
|
|
214
|
+
? `<li><b>Kill-switch</b> · ${esc(pilot.killSwitch)}</li>`
|
|
215
|
+
: ''
|
|
216
|
+
}
|
|
217
|
+
</ul>
|
|
218
|
+
</div>`
|
|
219
|
+
: '';
|
|
220
|
+
|
|
221
|
+
const next =
|
|
222
|
+
depth.postGreenPath?.short ||
|
|
223
|
+
depth.postGreenPath?.action ||
|
|
224
|
+
(designWeak
|
|
225
|
+
? '/ark-explore shape-focus → dual-plan B, then /ark-autopilot only with OK'
|
|
226
|
+
: null);
|
|
227
|
+
const nextHtml = next
|
|
228
|
+
? `<p class="meta" style="margin-top:.75rem"><b>Primary next</b> · ${esc(next)}</p>`
|
|
229
|
+
: '';
|
|
230
|
+
|
|
231
|
+
const golden = depth.goldenPattern;
|
|
232
|
+
const goldenHtml =
|
|
233
|
+
golden && golden.present !== false && (golden.name || golden.norm)
|
|
234
|
+
? `<p class="dim" style="margin-top:.5rem;font-size:.84rem">
|
|
235
|
+
Golden pattern (advisory for <b>new</b> code only):
|
|
236
|
+
<code>${esc(golden.name || 'pattern')}</code>
|
|
237
|
+
${golden.norm ? ` — ${esc(golden.norm)}` : ''}
|
|
238
|
+
${golden.examplePath ? ` · e.g. <code>${esc(golden.examplePath)}</code>` : ''}
|
|
239
|
+
</p>`
|
|
240
|
+
: designWeak
|
|
241
|
+
? `<p class="dim" style="margin-top:.5rem;font-size:.84rem">
|
|
242
|
+
No <code>.ark/golden-pattern.json</code> yet — optional; helps agents place <b>new</b> code only.
|
|
243
|
+
</p>`
|
|
244
|
+
: '';
|
|
245
|
+
|
|
246
|
+
return `<div class="section card design-strip ${designWeak ? 'is-weak' : 'has-smells'}" id="design-depth">
|
|
247
|
+
<div class="design-head">
|
|
248
|
+
<span class="badge design" title="Shape residual — separate from PASS/FAIL edge honesty">${esc(title)}</span>
|
|
249
|
+
<span class="dim" style="font-size:.86rem">${designWeak ? 'Edges clean · residual remains' : 'Smells + open edges'}</span>
|
|
250
|
+
</div>
|
|
251
|
+
<p class="dim" style="margin:.55rem 0 .5rem;font-size:.9rem">${esc(lede)}</p>
|
|
252
|
+
${smellItems ? `<ul class="senior-list">${smellItems}</ul>` : ''}
|
|
253
|
+
${pilotHtml}
|
|
254
|
+
${nextHtml}
|
|
255
|
+
${goldenHtml}
|
|
256
|
+
</div>`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Optional clean-depth note when edges + design are both healthy.
|
|
261
|
+
* Requires designFitness from a real sensor run (object). Null/undefined means
|
|
262
|
+
* depth was not computed — do not claim “OK” from missing data.
|
|
263
|
+
* @param {{ designFitness?: object|null, ok?: boolean, mode?: string }} depth
|
|
264
|
+
*/
|
|
265
|
+
export function renderDesignCleanNote(depth = {}) {
|
|
266
|
+
if (!depth.ok) return '';
|
|
267
|
+
// Sensors never ran (callers that omit designDepth) → no strip.
|
|
268
|
+
if (depth.designFitness == null || typeof depth.designFitness !== 'object') return '';
|
|
269
|
+
if (depth.designFitness.designWeak) return '';
|
|
270
|
+
if ((depth.designFitness.smellCount ?? 0) > 0) return '';
|
|
271
|
+
if (String(depth.mode || '').toLowerCase() !== 'enforce') return '';
|
|
272
|
+
return `<div class="section card design-strip is-clean" id="design-depth">
|
|
273
|
+
<div class="design-head">
|
|
274
|
+
<span class="badge design-ok" title="No deterministic design smells with clean edges">Design depth · OK</span>
|
|
275
|
+
<span class="dim" style="font-size:.86rem">No design-weak residual detected</span>
|
|
276
|
+
</div>
|
|
277
|
+
<p class="dim" style="margin:.45rem 0 0;font-size:.88rem">
|
|
278
|
+
Edges and deterministic design sensors agree. Keep placing new code on the golden path;
|
|
279
|
+
re-run doctor after large refactors.
|
|
280
|
+
</p>
|
|
281
|
+
</div>`;
|
|
282
|
+
}
|