arkgate 4.5.7 → 4.6.1
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 +48 -1
- package/README.md +18 -12
- package/bin/ark-check-runtime.mjs +107 -8
- package/bin/ark-mcp-runtime.mjs +17 -1
- package/bin/ark.mjs +5 -3
- package/bin/lib/agent-homes.mjs +296 -0
- package/bin/lib/agent-projection.mjs +2 -2
- package/bin/lib/analysis-engine.mjs +3 -3
- package/bin/lib/ci-and-commands.mjs +16 -16
- package/bin/lib/config-contract.mjs +2 -0
- package/bin/lib/contract-smells.mjs +5 -5
- package/bin/lib/design-smells.mjs +4 -8
- package/bin/lib/doctor-advisories.mjs +9 -0
- package/bin/lib/doctor-plan.mjs +39 -15
- package/bin/lib/field-install.mjs +1 -1
- package/bin/lib/golden-pattern.mjs +1 -1
- package/bin/lib/host-support-matrix.mjs +4 -4
- package/bin/lib/html-report-advisories.mjs +20 -0
- package/bin/lib/html-report-depth.mjs +7 -8
- package/bin/lib/html-report.mjs +2 -1
- package/bin/lib/improvement-compass-map.mjs +20 -20
- package/bin/lib/install-migrate.mjs +12 -0
- package/bin/lib/pilot-loop.mjs +1 -1
- package/bin/lib/post-green-path.mjs +5 -4
- package/bin/lib/prepare-change.mjs +9 -0
- package/bin/lib/product-copy.mjs +32 -0
- package/bin/lib/skill-write.mjs +1 -1
- package/bin/lib/status-command.mjs +14 -0
- package/bin/lib/status-manifest.mjs +17 -0
- package/bin/lib/team-parliament-io.mjs +338 -0
- package/bin/lib/team-parliament.mjs +383 -0
- package/bin/lib/upgrade-whats-new.mjs +32 -0
- package/bin/lib/violations.mjs +8 -4
- package/dist/{configTypes-CC0FEXoF.d.ts → configTypes-B8uIcLaG.d.ts} +5 -0
- package/dist/eslint/index.cjs +2 -2
- package/dist/eslint/index.d.ts +1 -1
- package/dist/eslint/index.js +2 -2
- package/dist/index.cjs +8 -8
- package/dist/index.d.ts +61 -2
- package/dist/index.js +8 -8
- package/docs/README.md +5 -4
- package/docs/agent-guide.md +31 -24
- package/docs/ai-gates.md +19 -0
- package/docs/configuration.md +36 -2
- package/docs/develop.md +16 -4
- package/docs/enthusiast/README.md +1 -1
- package/docs/enthusiast/how-to-agent-gates.md +3 -3
- package/docs/enthusiast/how-to-pick-shape.md +2 -2
- package/docs/enthusiast/tutorial-first-project.md +4 -3
- package/docs/package-surface.md +5 -3
- package/docs/product-voice.md +126 -82
- package/docs/use.md +13 -6
- package/package.json +1 -1
- package/schemas/ark.config.schema.json +9 -0
- package/schemas/ark.status-manifest.schema.json +51 -0
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +46 -19
- package/templates/agent-skills/ark-architect/SKILL.md +24 -145
- package/templates/agent-skills/ark-autopilot/SKILL.md +40 -30
- package/templates/agent-skills/ark-contract/SKILL.md +21 -105
- package/templates/agent-skills/ark-coverage/SKILL.md +3 -0
- package/templates/agent-skills/ark-explain/SKILL.md +7 -4
- package/templates/agent-skills/ark-explore/SKILL.md +29 -21
- package/templates/agent-skills/ark-fix/SKILL.md +34 -157
- package/templates/agent-skills/ark-loop/SKILL.md +31 -153
- package/templates/agent-skills/ark-place/SKILL.md +25 -13
- package/templates/agent-skills/ark-runtime/SKILL.md +3 -3
- package/templates/agent-skills/ark-think/SKILL.md +5 -2
- package/templates/agent-skills/ark-upgrade/SKILL.md +13 -8
- package/templates/skills/ark-adopt.md +46 -19
- package/templates/skills/ark-architect.md +24 -145
- package/templates/skills/ark-autopilot.md +40 -30
- package/templates/skills/ark-contract.md +21 -105
- package/templates/skills/ark-coverage.md +3 -0
- package/templates/skills/ark-explain.md +7 -4
- package/templates/skills/ark-explore.md +29 -21
- package/templates/skills/ark-fix.md +34 -157
- package/templates/skills/ark-loop.md +31 -153
- package/templates/skills/ark-place.md +25 -13
- package/templates/skills/ark-runtime.md +3 -3
- package/templates/skills/ark-think.md +5 -2
- package/templates/skills/ark-upgrade.md +13 -8
|
@@ -403,15 +403,15 @@ function messageFor(id, entries) {
|
|
|
403
403
|
function fixFor(id) {
|
|
404
404
|
switch (id) {
|
|
405
405
|
case 'contract-bidirectional-allow':
|
|
406
|
-
return `Keep one direction (edit via /ark-
|
|
406
|
+
return `Keep one direction (edit via /ark-adopt), or record the deliberate loop in ${CONTRACT_SMELL_ACKS_PATH} with a reason.`;
|
|
407
407
|
case 'contract-peripheral-depends-core':
|
|
408
|
-
return `Invert the edge: core emits events/ports the peripheral layer consumes (/ark-
|
|
408
|
+
return `Invert the edge: core emits events/ports the peripheral layer consumes (/ark-adopt), or acknowledge with a reason in ${CONTRACT_SMELL_ACKS_PATH}.`;
|
|
409
409
|
case 'contract-lateral-adapter-allow':
|
|
410
|
-
return `Move shared shapes into Domain/shared kernel and drop the lateral allow (/ark-
|
|
410
|
+
return `Move shared shapes into Domain/shared kernel and drop the lateral allow (/ark-adopt), or acknowledge with a reason in ${CONTRACT_SMELL_ACKS_PATH}.`;
|
|
411
411
|
case 'contract-dead-rule':
|
|
412
|
-
return 'Fix the layer patterns so the layer matches real files, or delete the stale/self rule via /ark-
|
|
412
|
+
return 'Fix the layer patterns so the layer matches real files, or delete the stale/self rule via /ark-adopt.';
|
|
413
413
|
default:
|
|
414
|
-
return 'Review the contract edge via /ark-
|
|
414
|
+
return 'Review the contract edge via /ark-adopt; never weaken the gate to silence a smell.';
|
|
415
415
|
}
|
|
416
416
|
}
|
|
417
417
|
|
|
@@ -9,6 +9,7 @@ import fs from 'node:fs';
|
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { layerForFile } from '../ark-shared.mjs';
|
|
11
11
|
import { detectContractFalseGreenRisk } from './field-install.mjs';
|
|
12
|
+
import { operatingModeTitle, LEFTOVER_DESIGN_LABEL } from './product-copy.mjs';
|
|
12
13
|
|
|
13
14
|
/** Stable smell ids (doctor JSON + plan B + skills). */
|
|
14
15
|
export const DESIGN_SMELL_IDS = Object.freeze([
|
|
@@ -284,7 +285,7 @@ export function detectDesignSmells(root, config, files = [], coverage = null) {
|
|
|
284
285
|
severity: 'warn',
|
|
285
286
|
message: `Layers classify files but have no deny/allow rule edges: ${withoutRules.join(', ')}. Soft green — peer leaks may go unchecked.`,
|
|
286
287
|
evidence: withoutRules.map((n) => `layer:${n}`),
|
|
287
|
-
fix: 'Add rules via /ark-
|
|
288
|
+
fix: 'Add rules via /ark-adopt (or a policy pack) so every populated layer participates in enforcement.',
|
|
288
289
|
})
|
|
289
290
|
);
|
|
290
291
|
}
|
|
@@ -476,20 +477,15 @@ export function isDesignWeak(smells, ctx = {}) {
|
|
|
476
477
|
*/
|
|
477
478
|
export function summarizeDesignFitness(smells, ctx = {}) {
|
|
478
479
|
const designWeak = isDesignWeak(smells, ctx);
|
|
479
|
-
const mode =
|
|
480
|
-
typeof ctx.operatingMode === 'string' &&
|
|
481
|
-
/^(?:suggest|adapt|enforce)$/.test(ctx.operatingMode)
|
|
482
|
-
? ctx.operatingMode.toUpperCase()
|
|
483
|
-
: null;
|
|
484
480
|
return {
|
|
485
481
|
status: designWeak ? 'design-weak' : smells.length > 0 ? 'smells-with-open-edges' : 'ok',
|
|
486
482
|
designWeak,
|
|
487
483
|
smellCount: Array.isArray(smells) ? smells.length : 0,
|
|
488
484
|
ids: (smells || []).map((s) => s.id),
|
|
489
485
|
label: designWeak
|
|
490
|
-
? `${
|
|
486
|
+
? `${operatingModeTitle(ctx.operatingMode, true)} — import rules check out; leftover design work remains (see designSmells / plan B)`
|
|
491
487
|
: smells.length > 0
|
|
492
|
-
? 'Design smells present alongside open
|
|
488
|
+
? 'Design smells present alongside open import-rule debt'
|
|
493
489
|
: 'No deterministic design smells detected',
|
|
494
490
|
};
|
|
495
491
|
}
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import { printParseHealthSection, summarizeParseHealth } from './parse-health.mjs';
|
|
20
20
|
import { detectGraphBlindSpots, printGraphBlindSection } from './graph-blind.mjs';
|
|
21
21
|
import { summarizeRulesUnderContract } from './rules-under-contract.mjs';
|
|
22
|
+
import { collectStewardNudge } from './team-parliament-io.mjs';
|
|
22
23
|
|
|
23
24
|
export function computeDoctorAdvisories(root, config, cov, rules, files, ts, parseHealth, facts) {
|
|
24
25
|
const physicalCohesion = computePhysicalCohesion(root, files);
|
|
@@ -49,6 +50,7 @@ export function computeDoctorAdvisories(root, config, cov, rules, files, ts, par
|
|
|
49
50
|
graphBlindSpots: detectGraphBlindSpots(ts, root, files),
|
|
50
51
|
// AR12 — Rules under contract (honest counts; real test I/O, never empty-fileContents stub).
|
|
51
52
|
// P1M: pass classification so extraMergeTeeth cannot arm at 0% governed.
|
|
53
|
+
stewardNudge: collectStewardNudge(root, config),
|
|
52
54
|
rulesUnderContract: summarizeRulesUnderContract(root, config, factPaths, {
|
|
53
55
|
governedPercent: cov?.governed?.percent ?? null,
|
|
54
56
|
populatedLayerCount: Array.isArray(cov?.layers)
|
|
@@ -70,4 +72,11 @@ export function printDoctorAdvisories(advisories, io) {
|
|
|
70
72
|
printReshapeDecisionsSection(advisories.physicalCohesion?.reshapeDecisions, io);
|
|
71
73
|
printParseHealthSection(advisories.parseHealth, io);
|
|
72
74
|
printGraphBlindSection(advisories.graphBlindSpots, io);
|
|
75
|
+
const nudge = advisories.stewardNudge;
|
|
76
|
+
if ((nudge?.needsStewards || nudge?.drift) && nudge.ask) {
|
|
77
|
+
console.log('');
|
|
78
|
+
console.log(io.color.bold('Stewards (advisory)'));
|
|
79
|
+
io.line(io.warn, nudge.ask);
|
|
80
|
+
if (nudge.nextAction) io.line(' ', io.color.dim(`Next: ${nudge.nextAction}`));
|
|
81
|
+
}
|
|
73
82
|
}
|
package/bin/lib/doctor-plan.mjs
CHANGED
|
@@ -12,6 +12,12 @@ import {
|
|
|
12
12
|
import * as arkShared from '../ark-shared.mjs';
|
|
13
13
|
import { summarizeRulesUnderContract } from './rules-under-contract.mjs';
|
|
14
14
|
import { describePackageVersionDualTruth } from './field-install.mjs';
|
|
15
|
+
import {
|
|
16
|
+
detectAgentHomeGaps,
|
|
17
|
+
agentHomeConcernIsActive,
|
|
18
|
+
agentHomeRefreshCommand,
|
|
19
|
+
} from './agent-homes.mjs';
|
|
20
|
+
import { operatingModeTitle } from './product-copy.mjs';
|
|
15
21
|
export { summarizeRulesUnderContract };
|
|
16
22
|
|
|
17
23
|
/** Optional S3 dual-match classifier when ark-shared exports it (soft dep for S5 landing). */
|
|
@@ -202,7 +208,7 @@ export function runCoverage(root, config, files, rules, asJson) {
|
|
|
202
208
|
}
|
|
203
209
|
}
|
|
204
210
|
console.log('');
|
|
205
|
-
console.log('Apply these via /ark-
|
|
211
|
+
console.log('Apply these via /ark-adopt (adds the layer patterns to ark.config.json).');
|
|
206
212
|
}
|
|
207
213
|
if (layersWithoutRules.length > 0) {
|
|
208
214
|
console.log('');
|
|
@@ -340,7 +346,7 @@ export function buildRemediationPlan(
|
|
|
340
346
|
: 'No active violations — the architecture already meets its contract.';
|
|
341
347
|
if (designWeak) {
|
|
342
348
|
statement =
|
|
343
|
-
'No active
|
|
349
|
+
'No active import-rule violations — imports check out, but design smells remain (leftover design work). Shape work is plan B only; not healthy finished.';
|
|
344
350
|
}
|
|
345
351
|
if (completeness !== ANALYSIS_COMPLETENESS.complete) statement = analysisIncompleteStatement(completeness);
|
|
346
352
|
|
|
@@ -523,6 +529,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
523
529
|
}
|
|
524
530
|
const gatesMissing = missingGates(root);
|
|
525
531
|
const skillGaps = detectSkillGaps(root);
|
|
532
|
+
const agentHomeGaps = detectAgentHomeGaps(root);
|
|
526
533
|
// Dual-truth: CLI version vs package.json pin (field residual after upgrade --no-install).
|
|
527
534
|
const packageVersionTruth = describePackageVersionDualTruth(root);
|
|
528
535
|
const staleRunners = staleRunnerGateFiles(root);
|
|
@@ -759,6 +766,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
759
766
|
},
|
|
760
767
|
gatesMissing,
|
|
761
768
|
skillGaps,
|
|
769
|
+
...(agentHomeGaps.length > 0 ? { agentHomeGaps } : {}),
|
|
762
770
|
staleRunnerFiles: staleRunners,
|
|
763
771
|
writePath: {
|
|
764
772
|
activeHost: writePath.activeHost,
|
|
@@ -836,21 +844,18 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
836
844
|
// modeTitle alone names the light — bodies must not re-prefix Suggest/Adapt/Enforce.
|
|
837
845
|
const modeHelp = {
|
|
838
846
|
suggest:
|
|
839
|
-
'thin or new tree;
|
|
847
|
+
'thin or new tree; architecture config is not yet in charge. You do not pick this light. Next: ark start (preview), then ark start --apply; re-check with --doctor.',
|
|
840
848
|
adapt:
|
|
841
|
-
'
|
|
849
|
+
'config and tree still disagree, or debt is open. The write path does not fully protect you yet. You do not pick this light. Next: do doctor top action #1 (often /ark-adopt or /ark-autopilot).',
|
|
842
850
|
enforce:
|
|
843
|
-
'honest coverage and clean checked
|
|
851
|
+
'honest coverage and clean checked imports. You arrived here; you never turn Enforce on. Next: keep the host write path and CI check; only NEW violations should fail.',
|
|
844
852
|
};
|
|
845
|
-
const modeTitle =
|
|
846
|
-
designFitness.designWeak
|
|
847
|
-
? `${mode.toUpperCase()} · design-weak`
|
|
848
|
-
: mode.toUpperCase();
|
|
853
|
+
const modeTitle = operatingModeTitle(mode, designFitness.designWeak);
|
|
849
854
|
line(
|
|
850
855
|
modeMark,
|
|
851
856
|
`${modeTitle} — ${
|
|
852
857
|
designFitness.designWeak
|
|
853
|
-
? '
|
|
858
|
+
? 'import rules check out; design smells remain. Green is not elegant design. You do not pick this light. Next: one Shape door — /ark-explore shape-focus → plan B; apply B only with /ark-autopilot and your OK. A clean import check is not done.'
|
|
854
859
|
: modeHelp[mode]
|
|
855
860
|
}`
|
|
856
861
|
);
|
|
@@ -914,7 +919,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
914
919
|
`Next pilot (one at a time): ${np.pilotTarget || np.pilot} [${np.smellId}] → re-doctor after change`
|
|
915
920
|
);
|
|
916
921
|
line(' ', color.dim(`success: ${np.successSignal}`));
|
|
917
|
-
line(' ', color.dim('never multi-pilot batch;
|
|
922
|
+
line(' ', color.dim('never multi-pilot batch; pattern bets are never auto-applied'));
|
|
918
923
|
}
|
|
919
924
|
}
|
|
920
925
|
|
|
@@ -933,7 +938,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
933
938
|
ok,
|
|
934
939
|
`"${goldenPattern.name}" — ${goldenPattern.norm}` +
|
|
935
940
|
(goldenPattern.newCodeHome ? ` Prefer: ${goldenPattern.newCodeHome}.` : '') +
|
|
936
|
-
' Advisory only — does not clear design
|
|
941
|
+
' Advisory only — does not clear leftover design work or replace the gate.'
|
|
937
942
|
);
|
|
938
943
|
} else if (goldenPattern.invalid) {
|
|
939
944
|
console.log('');
|
|
@@ -968,7 +973,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
968
973
|
}
|
|
969
974
|
if (cov.suggestions.length > 0) {
|
|
970
975
|
line(warn, `${cov.suggestions.length} ungoverned director(y/ies) — proposals: ${arkCommand(root, 'ark-check', '--coverage')}`);
|
|
971
|
-
actions.push('classify the ungoverned directories (/ark-
|
|
976
|
+
actions.push('classify the ungoverned directories (/ark-adopt)');
|
|
972
977
|
}
|
|
973
978
|
if (cov.emptyLayers.length > 0) line(warn, `Empty layers (pattern matches nothing): ${cov.emptyLayers.join(', ')}`);
|
|
974
979
|
if (cov.layersWithoutRules.length > 0) line(warn, `Layers with no rule edge: ${cov.layersWithoutRules.join(', ')}`);
|
|
@@ -1038,7 +1043,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
1038
1043
|
'No active violations — coverage is still thin, so green is not yet honest enforcement'
|
|
1039
1044
|
);
|
|
1040
1045
|
} else if (designFitness.designWeak) {
|
|
1041
|
-
line(warn, `None on checked
|
|
1046
|
+
line(warn, `None on checked imports — import rules match the config; leftover design work remains (${modeTitle}). Not healthy finished.`);
|
|
1042
1047
|
} else {
|
|
1043
1048
|
line(ok, 'None — the code matches the contract on checked edges');
|
|
1044
1049
|
}
|
|
@@ -1055,7 +1060,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
1055
1060
|
}
|
|
1056
1061
|
if (activeCount > 0) {
|
|
1057
1062
|
actions.push(
|
|
1058
|
-
`resolve the non-baselined violations — see the classified plan (${arkCommand(root, 'ark-check', '--plan')}), then /ark-
|
|
1063
|
+
`resolve the non-baselined violations — see the classified plan (${arkCommand(root, 'ark-check', '--plan')}), then /ark-autopilot`
|
|
1059
1064
|
);
|
|
1060
1065
|
}
|
|
1061
1066
|
}
|
|
@@ -1157,6 +1162,25 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
|
|
|
1157
1162
|
actions.push(codexHomeGap.catalogMetadataInvalid ? 'repair invalid Codex home catalog metadata after verifying the newest installed version' : 'refresh Codex home skills (--install-agent-gates --skills-only --codex-home --force)');
|
|
1158
1163
|
}
|
|
1159
1164
|
}
|
|
1165
|
+
for (const gap of agentHomeGaps) {
|
|
1166
|
+
const parts = [
|
|
1167
|
+
gap.missing > 0 ? `${gap.missing} missing` : null,
|
|
1168
|
+
gap.stale > 0 ? `${gap.stale} content-behind-package` : null,
|
|
1169
|
+
gap.catalogStateReason,
|
|
1170
|
+
].filter(Boolean);
|
|
1171
|
+
const deferred = !agentHomeConcernIsActive(gap.host);
|
|
1172
|
+
const summary = `${gap.label} shared agent skills ${parts.join(', ')}`;
|
|
1173
|
+
if (deferred) {
|
|
1174
|
+
line(color.dim('·'), color.dim(`${summary} (deferred — not this session)`));
|
|
1175
|
+
} else {
|
|
1176
|
+
line(warn, summary);
|
|
1177
|
+
actions.push(
|
|
1178
|
+
gap.catalogMetadataInvalid
|
|
1179
|
+
? `repair invalid ${gap.label} home catalog metadata after verifying the newest installed version`
|
|
1180
|
+
: `refresh ${gap.label} shared agent skills (${agentHomeRefreshCommand(root, gap)})`
|
|
1181
|
+
);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1160
1184
|
|
|
1161
1185
|
console.log('');
|
|
1162
1186
|
console.log(color.bold('Baseline'));
|
|
@@ -506,7 +506,7 @@ export function detectContractFalseGreenRisk(root, config, coverage) {
|
|
|
506
506
|
`Contract may be a false green: empty core layer(s) [${emptyCores.join(', ')}] while ` +
|
|
507
507
|
`Application-class globs still cover I/O paths (${ioPaths.slice(0, 5).join(', ')}` +
|
|
508
508
|
`${ioPaths.length > 5 ? ', …' : ''}). A clean check can miss real coupling.`,
|
|
509
|
-
fix: 'Run /ark-adopt
|
|
509
|
+
fix: 'Run /ark-adopt — reclassify persistence/auth dirs out of Application before claiming ENFORCE.',
|
|
510
510
|
};
|
|
511
511
|
}
|
|
512
512
|
|
|
@@ -154,7 +154,7 @@ export function formatGoldenPatternNote(result) {
|
|
|
154
154
|
let s = `Golden pattern (advisory for NEW code only): "${g.name}" — ${g.norm}`;
|
|
155
155
|
if (g.newCodeHome) s += ` Prefer new files under ${g.newCodeHome}.`;
|
|
156
156
|
if (g.examplePath) s += ` Example: ${g.examplePath}.`;
|
|
157
|
-
s += ' Does not clear design
|
|
157
|
+
s += ' Does not clear leftover design work or replace the gate.';
|
|
158
158
|
return s;
|
|
159
159
|
}
|
|
160
160
|
|
|
@@ -224,18 +224,18 @@ export function doctorWritePathHonestyMessage(activeHost, hardWriteActive) {
|
|
|
224
224
|
const mergeBoundary =
|
|
225
225
|
'Required CI hard merge boundary = a required GitHub status context that runs arkgate-check --strict-merge (alias ark-check --strict-merge)';
|
|
226
226
|
if (host === 'cursor' && !hardWriteActive) {
|
|
227
|
-
return `Cursor:
|
|
227
|
+
return `Cursor: pre-write block is supported for Write/StrReplace when .cursor/hooks.json is installed + trusted; without runtime-observed hook evidence, the block is unverified. ${mergeBoundary}.`;
|
|
228
228
|
}
|
|
229
229
|
if (host === 'codex') {
|
|
230
|
-
return `Codex:
|
|
230
|
+
return `Codex: edits are warning only (not blocked) at write time. ${mergeBoundary}.`;
|
|
231
231
|
}
|
|
232
232
|
if (host === 'opencode') {
|
|
233
|
-
return `OpenCode:
|
|
233
|
+
return `OpenCode: edits are warning only (not blocked). ${mergeBoundary}.`;
|
|
234
234
|
}
|
|
235
235
|
if ((host === 'claude' || host === 'grok' || host === 'antigravity') && !hardWriteActive) {
|
|
236
236
|
const label =
|
|
237
237
|
host === 'claude' ? 'Claude' : host === 'grok' ? 'Grok' : 'Antigravity';
|
|
238
|
-
return `${label}:
|
|
238
|
+
return `${label}: pre-write block is supported for listed ops when installed + trusted; without runtime-observed hook evidence, the block is unverified. ${mergeBoundary}.`;
|
|
239
239
|
}
|
|
240
240
|
return null;
|
|
241
241
|
}
|
|
@@ -311,12 +311,32 @@ function deepModuleCoachHtml(coach) {
|
|
|
311
311
|
</section>`;
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
+
function stewardNudgeHtml(nudge) {
|
|
315
|
+
if (!nudge || nudge.notAScore !== true) return '';
|
|
316
|
+
const ask =
|
|
317
|
+
(nudge.needsStewards || nudge.drift) && typeof nudge.ask === 'string' && nudge.ask
|
|
318
|
+
? `<p>${esc(nudge.ask)}</p>`
|
|
319
|
+
: '<p class="muted">No steward list gap (advisory).</p>';
|
|
320
|
+
const next =
|
|
321
|
+
typeof nudge.nextAction === 'string' && nudge.nextAction
|
|
322
|
+
? `<p class="muted">Next: ${esc(nudge.nextAction)}</p>`
|
|
323
|
+
: '';
|
|
324
|
+
return `
|
|
325
|
+
<section class="section card" data-advisory="stewardNudge">
|
|
326
|
+
<h2>Stewards <span class="muted">(advisory — never changes the verdict)</span></h2>
|
|
327
|
+
${ask}
|
|
328
|
+
${next}
|
|
329
|
+
<p class="muted">GitHub handle or email. Never invent names. Always <code>notAScore</code>.</p>
|
|
330
|
+
</section>`;
|
|
331
|
+
}
|
|
332
|
+
|
|
314
333
|
export function renderAdvisorySections(advisories, escape) {
|
|
315
334
|
if (!advisories || typeof advisories !== 'object') return '';
|
|
316
335
|
if (typeof escape === 'function') esc = escape;
|
|
317
336
|
return [
|
|
318
337
|
improvementCompassHtml(advisories.improvementCompass),
|
|
319
338
|
deepModuleCoachHtml(advisories.deepModuleCoach),
|
|
339
|
+
stewardNudgeHtml(advisories.stewardNudge),
|
|
320
340
|
contractHealthHtml(advisories.contractHealth),
|
|
321
341
|
ambientStateHtml(advisories.ambientState),
|
|
322
342
|
physicalCohesionHtml(advisories.physicalCohesion),
|
|
@@ -24,6 +24,7 @@ import { describePackageVersionDualTruth } from './field-install.mjs';
|
|
|
24
24
|
import { buildDoctorImprovementCompass } from './improvement-compass-doctor.mjs';
|
|
25
25
|
import { buildDeepModuleCoachAdvisory } from './deep-module-coach.mjs';
|
|
26
26
|
import { computePhysicalCohesion } from './physical-cohesion.mjs';
|
|
27
|
+
import { POST_GREEN_LEDE, operatingModeTitle } from './product-copy.mjs';
|
|
27
28
|
|
|
28
29
|
function esc(value) {
|
|
29
30
|
return String(value)
|
|
@@ -383,13 +384,11 @@ export function renderDesignDepthStrip(depth = {}) {
|
|
|
383
384
|
|
|
384
385
|
const mode = String(depth.mode || '').toLowerCase();
|
|
385
386
|
const title = designWeak
|
|
386
|
-
? mode
|
|
387
|
-
|
|
388
|
-
: `${(mode || 'edges').toUpperCase()} · design-weak`
|
|
389
|
-
: 'Design smells (edges still open)';
|
|
387
|
+
? operatingModeTitle(mode || 'enforce', true)
|
|
388
|
+
: 'Design smells (imports still open)';
|
|
390
389
|
const lede = designWeak
|
|
391
|
-
?
|
|
392
|
-
: 'Design smells exist alongside open
|
|
390
|
+
? POST_GREEN_LEDE
|
|
391
|
+
: 'Design smells exist alongside open import-rule debt. Fix imports first; treat smells as leftover design work after green.';
|
|
393
392
|
|
|
394
393
|
const smellItems = smells
|
|
395
394
|
.slice(0, 6)
|
|
@@ -411,7 +410,7 @@ export function renderDesignDepthStrip(depth = {}) {
|
|
|
411
410
|
? `<div class="pilot-card">
|
|
412
411
|
<h3 style="margin-top:.85rem">Next pilot (one at a time)</h3>
|
|
413
412
|
<p class="dim" style="margin:.15rem 0 .4rem;font-size:.86rem">
|
|
414
|
-
Judgment only — never
|
|
413
|
+
Judgment only — never auto-applied · never multi-pilot batch
|
|
415
414
|
</p>
|
|
416
415
|
<ul class="senior-list">
|
|
417
416
|
<li><b>Smell</b> · <code>${esc(pilot.smellId || pilot.id || '—')}</code></li>
|
|
@@ -489,7 +488,7 @@ export function renderDesignCleanNote(depth = {}) {
|
|
|
489
488
|
return `<div class="section card design-strip is-clean" id="design-depth">
|
|
490
489
|
<div class="design-head">
|
|
491
490
|
<span class="badge design-ok" title="No deterministic design smells with clean edges">Design depth · OK</span>
|
|
492
|
-
<span class="dim" style="font-size:.86rem">No design
|
|
491
|
+
<span class="dim" style="font-size:.86rem">No leftover design work detected</span>
|
|
493
492
|
</div>
|
|
494
493
|
<p class="dim" style="margin:.45rem 0 0;font-size:.88rem">
|
|
495
494
|
Edges and deterministic design sensors agree. Keep placing new code on the golden path;
|
package/bin/lib/html-report.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
resolveOperatingMode,
|
|
12
12
|
} from '../ark-shared.mjs';
|
|
13
13
|
import { collectAdoptionGaps, arkCheckCommand } from './agent-gates.mjs';
|
|
14
|
+
import { LEFTOVER_DESIGN_BADGE } from './product-copy.mjs';
|
|
14
15
|
import { CORE_LAYER_NAMES } from './core-layers.mjs';
|
|
15
16
|
import {
|
|
16
17
|
renderBaselineSignalLegend,
|
|
@@ -525,7 +526,7 @@ export function renderHtmlReport({
|
|
|
525
526
|
const designSmells = Array.isArray(depth.designSmells) ? depth.designSmells : [];
|
|
526
527
|
const designWeakBadge =
|
|
527
528
|
designFitness?.designWeak === true
|
|
528
|
-
? ` <span class="badge design" title="
|
|
529
|
+
? ` <span class="badge design" title="Import rules can be green while leftover design work remains. Not a FAIL.">${LEFTOVER_DESIGN_BADGE}</span>`
|
|
529
530
|
: '';
|
|
530
531
|
const designStripHtml =
|
|
531
532
|
renderDesignDepthStrip({
|
|
@@ -118,11 +118,11 @@ function mapDesignSmells(byId, smells) {
|
|
|
118
118
|
const shapeAction = {
|
|
119
119
|
kind: 'skill',
|
|
120
120
|
ref: '/ark-explore',
|
|
121
|
-
summary: 'Map Shape residual (shape-focus), then one extraction pilot with
|
|
121
|
+
summary: 'Map Shape residual (shape-focus), then apply one extraction pilot with /ark-autopilot.',
|
|
122
122
|
};
|
|
123
123
|
const dipAction = {
|
|
124
124
|
kind: 'skill',
|
|
125
|
-
ref: '/ark-
|
|
125
|
+
ref: '/ark-autopilot',
|
|
126
126
|
summary: 'Inject a port/adapter for I/O; keep domain pure.',
|
|
127
127
|
};
|
|
128
128
|
switch (id) {
|
|
@@ -147,7 +147,7 @@ function mapDesignSmells(byId, smells) {
|
|
|
147
147
|
attach('srp', 'God modules own too many responsibilities — split by concern (one pilot).', {
|
|
148
148
|
kind: 'skill',
|
|
149
149
|
ref: '/ark-autopilot',
|
|
150
|
-
summary: 'One Shape pilot
|
|
150
|
+
summary: 'One Shape pilot this turn — never multi-pilot batch.',
|
|
151
151
|
});
|
|
152
152
|
break;
|
|
153
153
|
case 'mixed-pattern-cluster':
|
|
@@ -161,12 +161,12 @@ function mapDesignSmells(byId, smells) {
|
|
|
161
161
|
case 'soft-contract':
|
|
162
162
|
attach('maintainability', 'Soft contract walls (layers without deny rules) hide maintainability debt.', {
|
|
163
163
|
kind: 'skill',
|
|
164
|
-
ref: '/ark-
|
|
164
|
+
ref: '/ark-adopt',
|
|
165
165
|
summary: 'Add real layer rules so the AI has hard walls.',
|
|
166
166
|
});
|
|
167
167
|
attach('coupling', 'Layers with files but almost no deny rules allow free peer coupling.', {
|
|
168
168
|
kind: 'skill',
|
|
169
|
-
ref: '/ark-
|
|
169
|
+
ref: '/ark-adopt',
|
|
170
170
|
summary: 'Tighten inter-layer allows/denies without weakening enforcement.',
|
|
171
171
|
});
|
|
172
172
|
break;
|
|
@@ -213,7 +213,7 @@ function mapViolations(byId, violations) {
|
|
|
213
213
|
}
|
|
214
214
|
const edgeAction = {
|
|
215
215
|
kind: 'skill',
|
|
216
|
-
ref: '/ark-
|
|
216
|
+
ref: '/ark-autopilot',
|
|
217
217
|
summary: 'Clear the active edge residual, then re-doctor.',
|
|
218
218
|
};
|
|
219
219
|
if (upper === 'LAYER_IMPORT_VIOLATION' ||
|
|
@@ -229,7 +229,7 @@ function mapViolations(byId, violations) {
|
|
|
229
229
|
if (upper.includes('PEER_ISOLATION') || upper === 'PEER_ISOLATION_VIOLATION') {
|
|
230
230
|
attach('coupling', 'Peer isolation residual — slices import each other freely.', {
|
|
231
231
|
kind: 'skill',
|
|
232
|
-
ref: '/ark-
|
|
232
|
+
ref: '/ark-autopilot',
|
|
233
233
|
summary: 'Peer isolation fixes are judgment-class — one cluster at a time.',
|
|
234
234
|
});
|
|
235
235
|
continue;
|
|
@@ -237,12 +237,12 @@ function mapViolations(byId, violations) {
|
|
|
237
237
|
if (upper === 'FORBIDDEN_GLOBAL' || upper.startsWith('FORBIDDEN_')) {
|
|
238
238
|
attach('dip', 'Forbidden globals / effect surfaces break dependency inversion.', {
|
|
239
239
|
kind: 'skill',
|
|
240
|
-
ref: '/ark-
|
|
240
|
+
ref: '/ark-autopilot',
|
|
241
241
|
summary: 'Inject a port instead of the forbidden global.',
|
|
242
242
|
});
|
|
243
243
|
attach('testability', 'Forbidden ambient effects reduce pure-domain testability.', {
|
|
244
244
|
kind: 'skill',
|
|
245
|
-
ref: '/ark-
|
|
245
|
+
ref: '/ark-autopilot',
|
|
246
246
|
summary: 'Replace ambient effects with injectable ports.',
|
|
247
247
|
});
|
|
248
248
|
continue;
|
|
@@ -250,12 +250,12 @@ function mapViolations(byId, violations) {
|
|
|
250
250
|
if (upper === 'CAPABILITY_VIOLATION') {
|
|
251
251
|
attach('dip', 'Denied capability use — invert through an allowed adapter/port.', {
|
|
252
252
|
kind: 'skill',
|
|
253
|
-
ref: '/ark-
|
|
253
|
+
ref: '/ark-autopilot',
|
|
254
254
|
summary: 'Capability walls require port injection (judgment, not mechanical-safe).',
|
|
255
255
|
});
|
|
256
256
|
attach('testability', 'Capability violations couple domain code to I/O — harder to unit-test.', {
|
|
257
257
|
kind: 'skill',
|
|
258
|
-
ref: '/ark-
|
|
258
|
+
ref: '/ark-autopilot',
|
|
259
259
|
summary: 'Keep pure layers free of denied capabilities.',
|
|
260
260
|
});
|
|
261
261
|
continue;
|
|
@@ -263,7 +263,7 @@ function mapViolations(byId, violations) {
|
|
|
263
263
|
if (upper.startsWith('ARKRULE_') || upper === 'INVARIANT_UNCOVERED') {
|
|
264
264
|
attach('encapsulation', 'ArkRules structure / invariant residual inside a layer.', {
|
|
265
265
|
kind: 'skill',
|
|
266
|
-
ref: '/ark-
|
|
266
|
+
ref: '/ark-autopilot',
|
|
267
267
|
summary: 'Label [ArkRules]; structure fixes are judgment — never invent mechanical-safe.',
|
|
268
268
|
});
|
|
269
269
|
attach('domain', 'Intra-layer domain structure or invariant coverage residual.', {
|
|
@@ -282,7 +282,7 @@ function mapCountsAndFlags(byId, facts) {
|
|
|
282
282
|
pushEvidence(lens, 'cycles', `count:${cycleCount}`);
|
|
283
283
|
markResidual(lens, 'Import cycles couple modules tightly.', {
|
|
284
284
|
kind: 'skill',
|
|
285
|
-
ref: '/ark-
|
|
285
|
+
ref: '/ark-autopilot',
|
|
286
286
|
summary: 'Break cycles with a judgment extraction — one pilot.',
|
|
287
287
|
});
|
|
288
288
|
}
|
|
@@ -296,7 +296,7 @@ function mapCountsAndFlags(byId, facts) {
|
|
|
296
296
|
pushEvidence(lens, 'peerIsolation', `count:${peer}`);
|
|
297
297
|
markResidual(lens, 'Peer isolation residual remains.', {
|
|
298
298
|
kind: 'skill',
|
|
299
|
-
ref: '/ark-
|
|
299
|
+
ref: '/ark-autopilot',
|
|
300
300
|
summary: 'Peer isolation is judgment-class residual.',
|
|
301
301
|
});
|
|
302
302
|
}
|
|
@@ -314,7 +314,7 @@ function mapCountsAndFlags(byId, facts) {
|
|
|
314
314
|
markResidual(srp, 'Mirrored clusters suggest split-by-concern residual (architecture SRP).', {
|
|
315
315
|
kind: 'skill',
|
|
316
316
|
ref: '/ark-autopilot',
|
|
317
|
-
summary: 'One reshape/extraction pilot
|
|
317
|
+
summary: 'One reshape/extraction pilot this turn.',
|
|
318
318
|
});
|
|
319
319
|
}
|
|
320
320
|
const pureN = Number(facts.pureOrCapabilityResidual) || 0;
|
|
@@ -327,7 +327,7 @@ function mapCountsAndFlags(byId, facts) {
|
|
|
327
327
|
pushEvidence(dip, 'forbiddenGlobals', `residual:${fgN}`);
|
|
328
328
|
markResidual(dip, 'Pure / capability / forbidden residual weakens dependency inversion.', {
|
|
329
329
|
kind: 'skill',
|
|
330
|
-
ref: '/ark-
|
|
330
|
+
ref: '/ark-autopilot',
|
|
331
331
|
summary: 'Inject ports; keep pure layers free of effects.',
|
|
332
332
|
});
|
|
333
333
|
const test = byId.get('testability');
|
|
@@ -337,7 +337,7 @@ function mapCountsAndFlags(byId, facts) {
|
|
|
337
337
|
pushEvidence(test, 'forbiddenGlobals', `residual:${fgN}`);
|
|
338
338
|
markResidual(test, 'Impure domain or capability residual reduces testability.', {
|
|
339
339
|
kind: 'skill',
|
|
340
|
-
ref: '/ark-
|
|
340
|
+
ref: '/ark-autopilot',
|
|
341
341
|
summary: 'Prefer ports over concrete I/O in pure/domain modules.',
|
|
342
342
|
});
|
|
343
343
|
}
|
|
@@ -347,7 +347,7 @@ function mapCountsAndFlags(byId, facts) {
|
|
|
347
347
|
pushEvidence(enc, 'arkRules', `structureResidual:${arkN}`);
|
|
348
348
|
markResidual(enc, 'ArkRules structure residual — encapsulation inside the layer.', {
|
|
349
349
|
kind: 'skill',
|
|
350
|
-
ref: '/ark-
|
|
350
|
+
ref: '/ark-autopilot',
|
|
351
351
|
summary: 'Fix structure sensors under [ArkRules] without inventing mechanical-safe.',
|
|
352
352
|
});
|
|
353
353
|
const domain = byId.get('domain');
|
|
@@ -368,7 +368,7 @@ function mapCountsAndFlags(byId, facts) {
|
|
|
368
368
|
markResidual(m, 'Design-weak: checked edges may be clean, but design residual remains — not finished.', {
|
|
369
369
|
kind: 'skill',
|
|
370
370
|
ref: '/ark-explore',
|
|
371
|
-
summary: 'Shape door: explore shape-focus → dual-plan B → one pilot
|
|
371
|
+
summary: 'Shape door: explore shape-focus → dual-plan B → one /ark-autopilot pilot.',
|
|
372
372
|
});
|
|
373
373
|
}
|
|
374
374
|
if (facts.dirtyBaselineRisk === true || (Number(facts.baselineStale) || 0) > 0) {
|
|
@@ -409,7 +409,7 @@ function mapCountsAndFlags(byId, facts) {
|
|
|
409
409
|
pushEvidence(mod, 'coverage', `emptyLayers:${emptyL}`);
|
|
410
410
|
markResidual(mod, 'Placement / modularity residual — ungoverned dirs or empty layer globs.', {
|
|
411
411
|
kind: 'skill',
|
|
412
|
-
ref: '/ark-
|
|
412
|
+
ref: '/ark-adopt',
|
|
413
413
|
summary: 'Classify ungoverned paths; fix empty layer patterns.',
|
|
414
414
|
});
|
|
415
415
|
}
|
|
@@ -80,6 +80,7 @@ import {
|
|
|
80
80
|
RUNNER_BEFORE_ARK,
|
|
81
81
|
} from './mcp-adoption.mjs';
|
|
82
82
|
import { inspectCodexInstallActivation, printCodexActivationHandoff, reportPartialInstall } from './install-activation.mjs';
|
|
83
|
+
import { installRequestedAgentHomes } from './agent-homes.mjs';
|
|
83
84
|
import {
|
|
84
85
|
hasHardWriteHook,
|
|
85
86
|
validateHardWriteRequest,
|
|
@@ -647,6 +648,17 @@ export function runInstallAgentGates(args) {
|
|
|
647
648
|
}
|
|
648
649
|
}
|
|
649
650
|
|
|
651
|
+
installRequestedAgentHomes({
|
|
652
|
+
root,
|
|
653
|
+
skills,
|
|
654
|
+
version,
|
|
655
|
+
force: args.force,
|
|
656
|
+
claudeHome: args.claudeHome,
|
|
657
|
+
grokHome: args.grokHome,
|
|
658
|
+
agentHomes: args.agentHomes,
|
|
659
|
+
json: args.json,
|
|
660
|
+
});
|
|
661
|
+
|
|
650
662
|
// Optional legacy/home fallback. Normal Codex installs use the project-scoped
|
|
651
663
|
// .codex/config.toml above, avoiding cross-project primary binding conflicts.
|
|
652
664
|
//
|
package/bin/lib/pilot-loop.mjs
CHANGED
|
@@ -115,7 +115,7 @@ export function extractionCardFromBet(bet, preferredFiles) {
|
|
|
115
115
|
reDoctor: 'ark-check --doctor --json',
|
|
116
116
|
rePlan: 'ark-check --plan --json',
|
|
117
117
|
next:
|
|
118
|
-
'/ark-
|
|
118
|
+
'/ark-autopilot (one cluster / one B pilot) | re-doctor after pilot',
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
121
|
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* Plan B stays never mechanical-safe.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { POST_GREEN_HUMAN } from './product-copy.mjs';
|
|
10
|
+
|
|
9
11
|
/** Stable product id for JSON / tests. */
|
|
10
12
|
export const POST_GREEN_PATH_ID = 'clarify-for-ai';
|
|
11
13
|
|
|
@@ -14,14 +16,13 @@ export const POST_GREEN_PRIMARY_SKILL = '/ark-explore';
|
|
|
14
16
|
|
|
15
17
|
/**
|
|
16
18
|
* Canonical human / agent next-action string (single door).
|
|
17
|
-
* Chained: explore shape-focus then autopilot
|
|
19
|
+
* Chained: explore shape-focus then autopilot applies one B pilot.
|
|
18
20
|
*/
|
|
19
|
-
export const POST_GREEN_PRIMARY_ACTION =
|
|
20
|
-
'Shape residual (design-weak): edges are clean, design is not finished. Map with /ark-explore shape-focus → dual-plan B; apply B only via /ark-autopilot with your OK. Empty plan A is not done; pattern bets are never mechanical-safe.';
|
|
21
|
+
export const POST_GREEN_PRIMARY_ACTION = POST_GREEN_HUMAN;
|
|
21
22
|
|
|
22
23
|
/** Short label for tables / metrics. */
|
|
23
24
|
export const POST_GREEN_PRIMARY_SHORT =
|
|
24
|
-
'/ark-explore shape-focus → /ark-autopilot (apply B
|
|
25
|
+
'/ark-explore shape-focus → /ark-autopilot (apply one B pilot) # Shape residual';
|
|
25
26
|
|
|
26
27
|
/** Placement coaching while design residual remains (new code only). */
|
|
27
28
|
export const POST_GREEN_PLACEMENT_COACHING =
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from './resolved-candidate-facts.mjs';
|
|
14
14
|
import { effectiveAnalysisConfig } from './analysis-policy.mjs';
|
|
15
15
|
import { isScanExcludedRelative } from '../ark-shared.mjs';
|
|
16
|
+
import { classifyChangeSet, evaluateTeamGate } from './team-parliament.mjs';
|
|
16
17
|
|
|
17
18
|
function candidatePath(value) {
|
|
18
19
|
if (typeof value !== 'string' || value.trim() === '') {
|
|
@@ -102,6 +103,14 @@ export function prepareChangeFromRoot({
|
|
|
102
103
|
const normalizedChanges = normalizeChangeSet(changes);
|
|
103
104
|
const normalizedOverlayChanges =
|
|
104
105
|
overlayChanges === undefined ? normalizedChanges : normalizeChangeSet(overlayChanges);
|
|
106
|
+
const lawGate = evaluateTeamGate({
|
|
107
|
+
changeSet: classifyChangeSet(normalizedChanges.map((change) => change.path)),
|
|
108
|
+
contractSession:
|
|
109
|
+
process.env.ARK_CONTRACT_SESSION === '1' || process.env.ARK_CONTRACT_SESSION === 'true',
|
|
110
|
+
});
|
|
111
|
+
if (lawGate.deny && lawGate.reasonId === 'mixed-law-and-product') {
|
|
112
|
+
throw new Error(lawGate.message);
|
|
113
|
+
}
|
|
105
114
|
const effectiveConfig = effectiveAnalysisConfig(config, manifest);
|
|
106
115
|
for (const change of normalizedChanges) {
|
|
107
116
|
assertInsideProject(root, change.path);
|