arkgate 4.6.6 → 4.6.7
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 +30 -2
- package/README.md +5 -4
- package/bin/ark-check-runtime.mjs +8 -340
- package/bin/ark-mcp-runtime.mjs +7 -323
- package/bin/ark-shared.mjs +24 -158
- package/bin/lib/check-args.mjs +173 -0
- package/bin/lib/check-config-detect.mjs +101 -0
- package/bin/lib/check-watch.mjs +80 -0
- package/bin/lib/deep-module-coach.mjs +3 -0
- package/bin/lib/doctor-human.mjs +509 -0
- package/bin/lib/doctor-plan.mjs +42 -429
- package/bin/lib/github-enforcement.mjs +22 -9
- package/bin/lib/html-report.mjs +24 -7
- package/bin/lib/mcp-hook-payload.mjs +328 -0
- package/bin/lib/package-manager.mjs +174 -0
- package/bin/lib/policy-delta-io.mjs +4 -0
- package/bin/lib/team-parliament-io.mjs +4 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/docs/README.md +2 -2
- package/docs/package-surface.md +2 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human doctor screens (AL06): compact first screen vs Details encyclopedia.
|
|
3
|
+
* Independently invocable. JSON / compass / coach stay in runDoctor.
|
|
4
|
+
*/
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { arkCommand } from '../ark-shared.mjs';
|
|
7
|
+
import { operatingModeTitle } from './product-copy.mjs';
|
|
8
|
+
import { isDoctorHealthyNothingToDo } from './post-green-path.mjs';
|
|
9
|
+
import { printParseHealthSection } from './parse-health.mjs';
|
|
10
|
+
import { printDoctorAdvisories } from './doctor-advisories.mjs';
|
|
11
|
+
import { designDeltaDoctorLines } from './design-delta.mjs';
|
|
12
|
+
import { enforcementDoctorLines } from './enforcement-state.mjs';
|
|
13
|
+
import { analysisIncompleteStatement } from './analysis-completeness.mjs';
|
|
14
|
+
import { skillGapsForActiveHost, detectCodexHomeGap, codexConcernIsActive } from './agent-gates.mjs';
|
|
15
|
+
import { agentHomeConcernIsActive } from './agent-homes.mjs';
|
|
16
|
+
|
|
17
|
+
function lineWith(ok, warn, bad, color) {
|
|
18
|
+
return (mark, text) => console.log(` ${mark} ${text}`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function marks(color) {
|
|
22
|
+
return {
|
|
23
|
+
ok: color.green('✓'),
|
|
24
|
+
warn: color.yellow('!'),
|
|
25
|
+
bad: color.red('✗'),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Compact first doctor screen. Always ends with `More: --doctor --all`.
|
|
31
|
+
* Includes thin-coverage and incomplete-analysis honesty. Never prints Details.
|
|
32
|
+
*/
|
|
33
|
+
export function printDoctorCompactHuman(view) {
|
|
34
|
+
const color = view.color;
|
|
35
|
+
const { ok, warn, bad } = marks(color);
|
|
36
|
+
const line = lineWith(ok, warn, bad, color);
|
|
37
|
+
const {
|
|
38
|
+
root,
|
|
39
|
+
analysisComplete,
|
|
40
|
+
completeness,
|
|
41
|
+
doctorAdvisories,
|
|
42
|
+
operatingMode,
|
|
43
|
+
designFitness,
|
|
44
|
+
adopted,
|
|
45
|
+
stewardUnfinished,
|
|
46
|
+
emptyScope,
|
|
47
|
+
uniqueActions,
|
|
48
|
+
ciMergeBoundary,
|
|
49
|
+
cov,
|
|
50
|
+
writePath,
|
|
51
|
+
writePathHonesty,
|
|
52
|
+
gatesMissing,
|
|
53
|
+
violations,
|
|
54
|
+
} = view;
|
|
55
|
+
|
|
56
|
+
console.log(color.bold(`Ark doctor — ${path.basename(path.resolve(root)) || '.'}`));
|
|
57
|
+
if (!analysisComplete) line(warn, analysisIncompleteStatement(completeness));
|
|
58
|
+
printParseHealthSection(doctorAdvisories.parseHealth, { color, warn, line });
|
|
59
|
+
|
|
60
|
+
const mode = operatingMode;
|
|
61
|
+
console.log('');
|
|
62
|
+
console.log(color.bold('Operating mode'));
|
|
63
|
+
const modeMark =
|
|
64
|
+
mode === 'enforce' &&
|
|
65
|
+
!designFitness.designWeak &&
|
|
66
|
+
adopted !== 'not-adopted' &&
|
|
67
|
+
!stewardUnfinished
|
|
68
|
+
? ok
|
|
69
|
+
: warn;
|
|
70
|
+
const modeHelp = {
|
|
71
|
+
suggest: 'thin or new tree. Next: ark start --apply, then doctor.',
|
|
72
|
+
adapt: 'config and tree still disagree. Next: do #1.',
|
|
73
|
+
enforce:
|
|
74
|
+
adopted === 'not-adopted'
|
|
75
|
+
? 'import rules check out; merge boundary not adopted.'
|
|
76
|
+
: 'import rules check out. Keep host + CI.',
|
|
77
|
+
};
|
|
78
|
+
const modeTitle = operatingModeTitle(mode, designFitness.designWeak, stewardUnfinished);
|
|
79
|
+
line(
|
|
80
|
+
modeMark,
|
|
81
|
+
`${modeTitle} — ${
|
|
82
|
+
designFitness.designWeak
|
|
83
|
+
? 'import rules check out; leftover design work remains.'
|
|
84
|
+
: modeHelp[mode]
|
|
85
|
+
}`
|
|
86
|
+
);
|
|
87
|
+
if (emptyScope) {
|
|
88
|
+
line(
|
|
89
|
+
bad,
|
|
90
|
+
'Empty scope: include paths match 0 source files — a green check is meaningless until include/layers match the tree (monorepo → apps/packages, or /ark-adopt).'
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log('');
|
|
95
|
+
if (ciMergeBoundary?.ci?.state) {
|
|
96
|
+
line(
|
|
97
|
+
ciMergeBoundary.ci.state === 'required' ? ok : warn,
|
|
98
|
+
`CI merge: ${ciMergeBoundary.ci.state}`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
if (adopted === 'advisory-only-acked') {
|
|
102
|
+
line(warn, 'Adoption: advisory-only ack — not a required GitHub status.');
|
|
103
|
+
}
|
|
104
|
+
if (isDoctorHealthyNothingToDo(designFitness, uniqueActions, adopted)) {
|
|
105
|
+
console.log(color.green('✔ Healthy — nothing to do.'));
|
|
106
|
+
console.log(color.dim(' Keep write path + CI.'));
|
|
107
|
+
} else {
|
|
108
|
+
console.log(color.bold('Primary next action'));
|
|
109
|
+
console.log(` 1. ${uniqueActions[0]}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log('');
|
|
113
|
+
console.log(color.bold('Coverage'));
|
|
114
|
+
const govMark =
|
|
115
|
+
emptyScope || cov.governed.percent < 50
|
|
116
|
+
? bad
|
|
117
|
+
: cov.governed.percent >= 80
|
|
118
|
+
? ok
|
|
119
|
+
: warn;
|
|
120
|
+
line(govMark, `Governed: ${cov.governed.percent}% (${cov.governed.classifiedFiles}/${cov.governed.totalFiles} files)`);
|
|
121
|
+
|
|
122
|
+
const hostRed =
|
|
123
|
+
gatesMissing.length > 0 ||
|
|
124
|
+
Boolean(writePath.gap) ||
|
|
125
|
+
writePathHonesty?.softWriteHost === true;
|
|
126
|
+
if (hostRed) {
|
|
127
|
+
console.log('');
|
|
128
|
+
console.log(color.bold('Host / CI'));
|
|
129
|
+
if (writePath.activeHost) line(' ', `Active host: ${writePath.activeHost}`);
|
|
130
|
+
if (gatesMissing.length > 0) line(bad, `Missing gates: ${gatesMissing.join(', ')}`);
|
|
131
|
+
else if (writePath.gap || writePathHonesty?.softWriteHost) {
|
|
132
|
+
line(warn, 'Local writes are advisory; required CI is the merge boundary.');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const nudge = doctorAdvisories.stewardNudge;
|
|
137
|
+
if ((nudge?.needsStewards || nudge?.drift || nudge?.emptyStewardsPastGrace) && nudge.ask) {
|
|
138
|
+
console.log('');
|
|
139
|
+
console.log(color.bold('Stewards'));
|
|
140
|
+
line(warn, nudge.ask);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (violations.length === 0) {
|
|
144
|
+
if (!analysisComplete) {
|
|
145
|
+
console.log('');
|
|
146
|
+
line(
|
|
147
|
+
warn,
|
|
148
|
+
'No reported violations — contract compliance is not verified until analysis is complete'
|
|
149
|
+
);
|
|
150
|
+
} else if (emptyScope || cov.governed.percent < 50) {
|
|
151
|
+
console.log('');
|
|
152
|
+
line(
|
|
153
|
+
warn,
|
|
154
|
+
'No active violations — coverage is still thin, so green is not yet honest enforcement'
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log('');
|
|
160
|
+
console.log(color.dim('More: --doctor --all'));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Details encyclopedia. Only invoked for `--doctor --all` / `all: true`.
|
|
165
|
+
*/
|
|
166
|
+
export function printDoctorDetailsHuman(view) {
|
|
167
|
+
const color = view.color;
|
|
168
|
+
const { ok, warn, bad } = marks(color);
|
|
169
|
+
const line = lineWith(ok, warn, bad, color);
|
|
170
|
+
const {
|
|
171
|
+
root,
|
|
172
|
+
analysisComplete,
|
|
173
|
+
doctorAdvisories,
|
|
174
|
+
operatingMode,
|
|
175
|
+
designFitness,
|
|
176
|
+
adopted,
|
|
177
|
+
stewardUnfinished,
|
|
178
|
+
emptyScope,
|
|
179
|
+
options,
|
|
180
|
+
cov,
|
|
181
|
+
writePath,
|
|
182
|
+
writePathHonesty,
|
|
183
|
+
gatesMissing,
|
|
184
|
+
violations,
|
|
185
|
+
coverageHonesty,
|
|
186
|
+
packageVersionTruth,
|
|
187
|
+
designSmells,
|
|
188
|
+
pilotLoop,
|
|
189
|
+
goldenPattern,
|
|
190
|
+
pureLayerOptIn,
|
|
191
|
+
summary,
|
|
192
|
+
suppressed,
|
|
193
|
+
activeCount,
|
|
194
|
+
skillGaps,
|
|
195
|
+
agentHomeGaps,
|
|
196
|
+
baseline,
|
|
197
|
+
baselineHonesty,
|
|
198
|
+
staleBaseline,
|
|
199
|
+
staleRunners,
|
|
200
|
+
adoption,
|
|
201
|
+
} = view;
|
|
202
|
+
const modeTitle = operatingModeTitle(operatingMode, designFitness.designWeak, stewardUnfinished);
|
|
203
|
+
|
|
204
|
+
console.log('');
|
|
205
|
+
console.log(color.dim('---'));
|
|
206
|
+
console.log(color.bold('Details'));
|
|
207
|
+
|
|
208
|
+
if (coverageHonesty.greenIsNotEnforcement) {
|
|
209
|
+
line(coverageHonesty.worseThanNoGate ? bad : warn, coverageHonesty.message);
|
|
210
|
+
}
|
|
211
|
+
if (cov.suggestions.length > 0) {
|
|
212
|
+
line(warn, `${cov.suggestions.length} ungoverned director(y/ies) — proposals: ${arkCommand(root, 'ark-check', '--coverage')}`);
|
|
213
|
+
}
|
|
214
|
+
if (cov.emptyLayers.length > 0) line(warn, `Empty layers (pattern matches nothing): ${cov.emptyLayers.join(', ')}`);
|
|
215
|
+
if (cov.layersWithoutRules.length > 0) line(warn, `Layers with no rule edge: ${cov.layersWithoutRules.join(', ')}`);
|
|
216
|
+
if (cov.dualMembership?.count > 0) {
|
|
217
|
+
line(
|
|
218
|
+
warn,
|
|
219
|
+
`Dual-match: ${cov.dualMembership.count} file(s) match multiple layers — ${cov.dualMembership.note ?? 'review overlapping globs'}`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
if (cov.suggestions.length === 0 && cov.emptyLayers.length === 0) line(ok, 'Every layer classifies files; no empty layers');
|
|
223
|
+
|
|
224
|
+
if (packageVersionTruth?.dualTruth) {
|
|
225
|
+
console.log('');
|
|
226
|
+
console.log(color.bold('Package pin (dual-truth)'));
|
|
227
|
+
line(warn, packageVersionTruth.note);
|
|
228
|
+
} else if (packageVersionTruth?.code === 'PACKAGE_PIN_ABSENT') {
|
|
229
|
+
console.log('');
|
|
230
|
+
console.log(color.bold('Package pin'));
|
|
231
|
+
line(warn, packageVersionTruth.note);
|
|
232
|
+
}
|
|
233
|
+
if (options.configWalkedUp && options.configRoot) {
|
|
234
|
+
line(
|
|
235
|
+
ok,
|
|
236
|
+
`Config walk-up: using monorepo root ${options.configRoot} (ark.config.json not in cwd package)`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
console.log('');
|
|
241
|
+
console.log(color.bold('Design fitness'));
|
|
242
|
+
if (designSmells.length === 0) {
|
|
243
|
+
line(analysisComplete ? ok : warn, designFitness.label);
|
|
244
|
+
} else {
|
|
245
|
+
line(designFitness.designWeak ? warn : warn, designFitness.label);
|
|
246
|
+
for (const smell of designSmells.slice(0, 5)) {
|
|
247
|
+
const outcome = smell.outcome || smell.message;
|
|
248
|
+
line(' ', `[${smell.id}] ${outcome}`);
|
|
249
|
+
if (smell.outcome && smell.message && smell.message !== smell.outcome) {
|
|
250
|
+
line(' ', color.dim(`detail: ${smell.message}`));
|
|
251
|
+
}
|
|
252
|
+
if (smell.evidence?.length) {
|
|
253
|
+
line(' ', color.dim(`evidence: ${smell.evidence.slice(0, 4).join(', ')}`));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (pilotLoop?.active && pilotLoop.nextPilot) {
|
|
257
|
+
const np = pilotLoop.nextPilot;
|
|
258
|
+
line(
|
|
259
|
+
warn,
|
|
260
|
+
`Next pilot (one at a time): ${np.pilotTarget || np.pilot} [${np.smellId}] → re-doctor after change`
|
|
261
|
+
);
|
|
262
|
+
line(' ', color.dim(`success: ${np.successSignal}`));
|
|
263
|
+
line(' ', color.dim('never multi-pilot batch; pattern bets are never auto-applied'));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (options.designDelta) {
|
|
268
|
+
console.log('');
|
|
269
|
+
console.log(color.bold('Design delta (opt-in)'));
|
|
270
|
+
for (const row of designDeltaDoctorLines(options.designDelta))
|
|
271
|
+
line(row.level === 'bad' ? bad : row.level === 'ok' ? ok : ' ', row.level === 'dim' ? color.dim(row.text) : row.text);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (goldenPattern.present) {
|
|
275
|
+
console.log('');
|
|
276
|
+
console.log(color.bold('Golden pattern (new code)'));
|
|
277
|
+
line(
|
|
278
|
+
ok,
|
|
279
|
+
`"${goldenPattern.name}" — ${goldenPattern.norm}` +
|
|
280
|
+
(goldenPattern.newCodeHome ? ` Prefer: ${goldenPattern.newCodeHome}.` : '') +
|
|
281
|
+
' Advisory only — does not clear leftover design work or replace the gate.'
|
|
282
|
+
);
|
|
283
|
+
} else if (goldenPattern.invalid) {
|
|
284
|
+
console.log('');
|
|
285
|
+
console.log(color.bold('Golden pattern (new code)'));
|
|
286
|
+
line(
|
|
287
|
+
warn,
|
|
288
|
+
`${goldenPattern.path} is present but invalid (${goldenPattern.error || 'invalid'}). ` +
|
|
289
|
+
'Fix or remove it — absence is fine; a bad file is not guidance.'
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
if (pureLayerOptIn) {
|
|
293
|
+
line(' ', color.dim(pureLayerOptIn.message));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
printDoctorAdvisories(doctorAdvisories, { line, warn, color });
|
|
297
|
+
|
|
298
|
+
console.log('');
|
|
299
|
+
console.log(color.bold('Violations'));
|
|
300
|
+
if (violations.length === 0) {
|
|
301
|
+
if (!analysisComplete) line(warn, 'No reported violations — contract compliance is not verified until analysis is complete');
|
|
302
|
+
else if (emptyScope || cov.governed.percent < 50) {
|
|
303
|
+
line(
|
|
304
|
+
warn,
|
|
305
|
+
'No active violations — coverage is still thin, so green is not yet honest enforcement'
|
|
306
|
+
);
|
|
307
|
+
} else if (designFitness.designWeak) {
|
|
308
|
+
line(warn, `None on checked imports — import rules match the config; leftover design work remains (${modeTitle}). Not healthy finished.`);
|
|
309
|
+
} else {
|
|
310
|
+
line(ok, 'None — the code matches the contract on checked edges');
|
|
311
|
+
}
|
|
312
|
+
} else {
|
|
313
|
+
const typeNote = summary.typeOnlyCount > 0 ? ` (${summary.valueCount} value · ${summary.typeOnlyCount} type-only)` : '';
|
|
314
|
+
const supNote = suppressed > 0 ? `, ${suppressed} frozen` : '';
|
|
315
|
+
line(
|
|
316
|
+
activeCount > 0 ? warn : ok,
|
|
317
|
+
`${violations.length} total${typeNote}${supNote}${activeCount > 0 ? ` — ${activeCount} NOT baselined` : ''}`
|
|
318
|
+
);
|
|
319
|
+
for (const edge of summary.edges.slice(0, 3)) line(' ', color.dim(`${edge.count} ${edge.edge}`));
|
|
320
|
+
if (summary.concentrated) {
|
|
321
|
+
line(warn, color.dim(`${Math.round(summary.dominantShare * 100)}% on one edge (${summary.dominant}) — likely a contract fix, not debt`));
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
console.log('');
|
|
326
|
+
console.log(color.bold('Write path (agent)'));
|
|
327
|
+
const capabilities = writePath.capabilities;
|
|
328
|
+
const writePathLabels = {
|
|
329
|
+
repair: 'repair-capable — hard block + machine-readable autoPatch / ARK_REPAIR_JSON',
|
|
330
|
+
'reject-only': 'reject-only — hard block with prose; no repair payload',
|
|
331
|
+
'mcp-only': 'MCP tools only — prepare-write/autoPatch available; no PreToolUse hook',
|
|
332
|
+
none: 'no write gate hook and no Ark MCP',
|
|
333
|
+
};
|
|
334
|
+
const wpMark =
|
|
335
|
+
capabilities['hard-write']
|
|
336
|
+
? ok
|
|
337
|
+
: capabilities['advisory-write'] || capabilities['merge-gate']
|
|
338
|
+
? warn
|
|
339
|
+
: bad;
|
|
340
|
+
line(' ', `Active host: ${writePath.activeHost}`);
|
|
341
|
+
line(' ', `Supported profile: ${writePath.supportSummary}`);
|
|
342
|
+
line(wpMark, `Mode: ${writePath.mode} — ${writePathLabels[writePath.mode] || writePath.mode}`);
|
|
343
|
+
if (writePathHonesty.message) line(warn, writePathHonesty.message);
|
|
344
|
+
if (writePath.sessionNote) {
|
|
345
|
+
line(warn, writePath.sessionNote);
|
|
346
|
+
}
|
|
347
|
+
const enforcement = writePath.enforcementState;
|
|
348
|
+
for (const row of enforcementDoctorLines(enforcement)) line(row.level === 'ok' ? ok : row.level === 'bad' ? bad : warn, row.text);
|
|
349
|
+
const supportCaps = writePath.support?.capabilities || {};
|
|
350
|
+
const repairReinjection = supportCaps['repair-reinjection-guaranteed'] === true;
|
|
351
|
+
const repairEnvelope = supportCaps['repair-envelope-emitted'] === true || supportCaps['repair-payload'] === true;
|
|
352
|
+
line(
|
|
353
|
+
repairReinjection ? ok : warn,
|
|
354
|
+
repairReinjection
|
|
355
|
+
? 'Repair: envelope + reinjection guaranteed on hard path when installed + trusted'
|
|
356
|
+
: repairEnvelope
|
|
357
|
+
? 'Repair: envelope may emit (`--hook-repair`); reinjection not guaranteed (advisory host)'
|
|
358
|
+
: 'Repair: no hard-boundary payload'
|
|
359
|
+
);
|
|
360
|
+
if (writePath.gap) {
|
|
361
|
+
line(writePath.gap.severity === 'warn' ? warn : warn, writePath.gap.message);
|
|
362
|
+
if (writePath.gap.fix) {
|
|
363
|
+
line(' ', color.dim(`Fix: ${writePath.gap.fix}`));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
console.log('');
|
|
368
|
+
console.log(color.bold('Gates & skills'));
|
|
369
|
+
if (gatesMissing.length === 0) line(ok, 'Shared gate artifacts found on disk (AGENTS.md, .mcp.json, CI); runtime activation is reported separately');
|
|
370
|
+
else {
|
|
371
|
+
line(bad, `Missing gates: ${gatesMissing.join(', ')}`);
|
|
372
|
+
}
|
|
373
|
+
const humanSkillGaps = skillGapsForActiveHost(skillGaps);
|
|
374
|
+
const legacyCodex = humanSkillGaps.some((g) => g.tool === 'codex' && g.legacyPromptsOnly);
|
|
375
|
+
const codexLegacySafeDelete = humanSkillGaps.some(
|
|
376
|
+
(g) => g.tool === 'codex' && g.legacyAdvisory && g.catalogComplete
|
|
377
|
+
);
|
|
378
|
+
const remainingGaps = humanSkillGaps.filter(
|
|
379
|
+
(g) => !(g.tool === 'codex' && (g.legacyPromptsOnly || g.legacyAdvisory))
|
|
380
|
+
);
|
|
381
|
+
const remMiss = remainingGaps.reduce((s, g) => s + g.missing, 0);
|
|
382
|
+
const remStale = remainingGaps.reduce((s, g) => s + g.stale, 0);
|
|
383
|
+
if (remMiss + remStale === 0 && !legacyCodex) line(ok, '/ark-* skills current for detected tools');
|
|
384
|
+
if (legacyCodex) {
|
|
385
|
+
line(warn, 'Codex: legacy flat .codex/prompts only (not a loadable skill catalog)');
|
|
386
|
+
}
|
|
387
|
+
if (codexLegacySafeDelete) {
|
|
388
|
+
line(
|
|
389
|
+
' ',
|
|
390
|
+
color.dim(
|
|
391
|
+
'Codex catalog complete — leftover .codex/prompts/ark-*.md are safe to delete (not loadable; not required).'
|
|
392
|
+
)
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
if (remMiss + remStale > 0) {
|
|
396
|
+
line(
|
|
397
|
+
warn,
|
|
398
|
+
`${remMiss} missing / ${remStale} content-behind-package /ark-* skill(s) for ${remainingGaps.map((g) => g.tool).join(', ')}`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
const codexHomeGap = detectCodexHomeGap(root);
|
|
402
|
+
if (codexHomeGap) {
|
|
403
|
+
const parts = [
|
|
404
|
+
codexHomeGap.legacyPromptsOnly ? 'legacy-prompts-only' : null,
|
|
405
|
+
codexHomeGap.missing > 0 ? `${codexHomeGap.missing} missing` : null,
|
|
406
|
+
codexHomeGap.stale > 0 ? `${codexHomeGap.stale} content-behind-package` : null, codexHomeGap.catalogStateReason,
|
|
407
|
+
].filter(Boolean);
|
|
408
|
+
const deferred = !codexConcernIsActive();
|
|
409
|
+
if (deferred) {
|
|
410
|
+
line(color.dim('·'), color.dim(`Codex home skills ${parts.join(', ')} (deferred — not on Codex session)`));
|
|
411
|
+
} else {
|
|
412
|
+
line(warn, `Codex home skills ${parts.join(', ')}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
for (const gap of agentHomeGaps) {
|
|
416
|
+
const parts = [
|
|
417
|
+
gap.missing > 0 ? `${gap.missing} missing` : null,
|
|
418
|
+
gap.stale > 0 ? `${gap.stale} content-behind-package` : null,
|
|
419
|
+
gap.catalogStateReason,
|
|
420
|
+
].filter(Boolean);
|
|
421
|
+
const deferred = !agentHomeConcernIsActive(gap.host);
|
|
422
|
+
const gapSummary = `${gap.label} shared agent skills ${parts.join(', ')}`;
|
|
423
|
+
if (deferred) {
|
|
424
|
+
line(color.dim('·'), color.dim(`${gapSummary} (deferred — not this session)`));
|
|
425
|
+
} else {
|
|
426
|
+
line(warn, gapSummary);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
console.log('');
|
|
431
|
+
console.log(color.bold('Baseline'));
|
|
432
|
+
if (!baseline.exists) {
|
|
433
|
+
line(!analysisComplete || violations.length > 0 ? warn : ok, !analysisComplete ? 'No baseline — current violations were not fully evaluated' : violations.length > 0 ? 'No baseline — adopting a dirty repo? freeze with --update-baseline' : 'No baseline (nothing to freeze)');
|
|
434
|
+
} else {
|
|
435
|
+
const baseMark = !analysisComplete || baselineHonesty.dirtyBaselineRisk ? warn : ok;
|
|
436
|
+
line(baseMark, `${baseline.keys.size} frozen key(s)${analysisComplete ? '' : ' — stale comparison not verified'}`);
|
|
437
|
+
if (analysisComplete && baselineHonesty.dirtyBaselineRisk) {
|
|
438
|
+
line(warn, baselineHonesty.message);
|
|
439
|
+
}
|
|
440
|
+
if (analysisComplete && staleBaseline > 0) {
|
|
441
|
+
line(warn, `${staleBaseline} stale entr(y/ies) no longer occur — tighten with --update-baseline`);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
console.log('');
|
|
446
|
+
console.log(color.bold('Command runners'));
|
|
447
|
+
if (staleRunners.length === 0) line(ok, 'Emitted commands match the package manager');
|
|
448
|
+
else {
|
|
449
|
+
line(warn, `Stale runner in ${staleRunners.join(', ')}`);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
console.log('');
|
|
453
|
+
console.log(color.bold('Adoption (separate from fitness score)'));
|
|
454
|
+
if (adoption.gaps.length === 0 && !adoption.layerBalance) {
|
|
455
|
+
line(
|
|
456
|
+
ok,
|
|
457
|
+
'Hosts, MCP argv, core optionality, origin report, baseline policy, and deploy-path lint/types look complete'
|
|
458
|
+
);
|
|
459
|
+
} else {
|
|
460
|
+
for (const gap of adoption.gaps) {
|
|
461
|
+
const mark = gap.deferred
|
|
462
|
+
? color.dim('·')
|
|
463
|
+
: gap.severity === 'warn'
|
|
464
|
+
? warn
|
|
465
|
+
: gap.severity === 'info'
|
|
466
|
+
? warn
|
|
467
|
+
: bad;
|
|
468
|
+
line(mark, gap.message);
|
|
469
|
+
if (gap.fix) {
|
|
470
|
+
line(' ', color.dim(gap.deferred ? `When using Codex: ${gap.fix}` : `Fix: ${gap.fix}`));
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (adoption.layerBalance) {
|
|
474
|
+
line(warn, color.dim(adoption.layerBalance.educational));
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (adoption.baseline) {
|
|
478
|
+
line(
|
|
479
|
+
' ',
|
|
480
|
+
color.dim(
|
|
481
|
+
`Baseline policy: ${adoption.baseline.signal}` +
|
|
482
|
+
(adoption.baseline.primaryPathUsesBaseline
|
|
483
|
+
? ' · primary path uses --baseline'
|
|
484
|
+
: ' · primary path does not use --baseline')
|
|
485
|
+
)
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
if (adoption.originReport.present) {
|
|
489
|
+
line(ok, 'Origin architecture snapshot present (.ark/reports/origin.json)');
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
console.log('');
|
|
493
|
+
console.log(color.bold('Safety / bypass resistance'));
|
|
494
|
+
const safety = options.safety;
|
|
495
|
+
if (!safety) {
|
|
496
|
+
line(warn, 'Safety diagnostics unavailable');
|
|
497
|
+
} else {
|
|
498
|
+
const rows = [
|
|
499
|
+
['Non-literal dynamic dependencies', safety.nonLiteralDynamicImports],
|
|
500
|
+
['@ts-ignore / @ts-nocheck', safety.tsSuppressions],
|
|
501
|
+
['Explicit any casts', safety.anyCasts],
|
|
502
|
+
['InMemory stores in production source', safety.inMemoryProductionStores],
|
|
503
|
+
['Rules with peerIsolation: false', safety.disabledPeerIsolationRules],
|
|
504
|
+
];
|
|
505
|
+
for (const [label, entries] of rows) {
|
|
506
|
+
line(entries.length === 0 ? ok : warn, `${label}: ${entries.length}`);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|