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.
Files changed (83) hide show
  1. package/CHANGELOG.md +48 -1
  2. package/README.md +18 -12
  3. package/bin/ark-check-runtime.mjs +107 -8
  4. package/bin/ark-mcp-runtime.mjs +17 -1
  5. package/bin/ark.mjs +5 -3
  6. package/bin/lib/agent-homes.mjs +296 -0
  7. package/bin/lib/agent-projection.mjs +2 -2
  8. package/bin/lib/analysis-engine.mjs +3 -3
  9. package/bin/lib/ci-and-commands.mjs +16 -16
  10. package/bin/lib/config-contract.mjs +2 -0
  11. package/bin/lib/contract-smells.mjs +5 -5
  12. package/bin/lib/design-smells.mjs +4 -8
  13. package/bin/lib/doctor-advisories.mjs +9 -0
  14. package/bin/lib/doctor-plan.mjs +39 -15
  15. package/bin/lib/field-install.mjs +1 -1
  16. package/bin/lib/golden-pattern.mjs +1 -1
  17. package/bin/lib/host-support-matrix.mjs +4 -4
  18. package/bin/lib/html-report-advisories.mjs +20 -0
  19. package/bin/lib/html-report-depth.mjs +7 -8
  20. package/bin/lib/html-report.mjs +2 -1
  21. package/bin/lib/improvement-compass-map.mjs +20 -20
  22. package/bin/lib/install-migrate.mjs +12 -0
  23. package/bin/lib/pilot-loop.mjs +1 -1
  24. package/bin/lib/post-green-path.mjs +5 -4
  25. package/bin/lib/prepare-change.mjs +9 -0
  26. package/bin/lib/product-copy.mjs +32 -0
  27. package/bin/lib/skill-write.mjs +1 -1
  28. package/bin/lib/status-command.mjs +14 -0
  29. package/bin/lib/status-manifest.mjs +17 -0
  30. package/bin/lib/team-parliament-io.mjs +338 -0
  31. package/bin/lib/team-parliament.mjs +383 -0
  32. package/bin/lib/upgrade-whats-new.mjs +32 -0
  33. package/bin/lib/violations.mjs +8 -4
  34. package/dist/{configTypes-CC0FEXoF.d.ts → configTypes-B8uIcLaG.d.ts} +5 -0
  35. package/dist/eslint/index.cjs +2 -2
  36. package/dist/eslint/index.d.ts +1 -1
  37. package/dist/eslint/index.js +2 -2
  38. package/dist/index.cjs +8 -8
  39. package/dist/index.d.ts +61 -2
  40. package/dist/index.js +8 -8
  41. package/docs/README.md +5 -4
  42. package/docs/agent-guide.md +31 -24
  43. package/docs/ai-gates.md +19 -0
  44. package/docs/configuration.md +36 -2
  45. package/docs/develop.md +16 -4
  46. package/docs/enthusiast/README.md +1 -1
  47. package/docs/enthusiast/how-to-agent-gates.md +3 -3
  48. package/docs/enthusiast/how-to-pick-shape.md +2 -2
  49. package/docs/enthusiast/tutorial-first-project.md +4 -3
  50. package/docs/package-surface.md +5 -3
  51. package/docs/product-voice.md +126 -82
  52. package/docs/use.md +13 -6
  53. package/package.json +1 -1
  54. package/schemas/ark.config.schema.json +9 -0
  55. package/schemas/ark.status-manifest.schema.json +51 -0
  56. package/server.json +2 -2
  57. package/templates/agent-skills/README.md +1 -1
  58. package/templates/agent-skills/ark-adopt/SKILL.md +46 -19
  59. package/templates/agent-skills/ark-architect/SKILL.md +24 -145
  60. package/templates/agent-skills/ark-autopilot/SKILL.md +40 -30
  61. package/templates/agent-skills/ark-contract/SKILL.md +21 -105
  62. package/templates/agent-skills/ark-coverage/SKILL.md +3 -0
  63. package/templates/agent-skills/ark-explain/SKILL.md +7 -4
  64. package/templates/agent-skills/ark-explore/SKILL.md +29 -21
  65. package/templates/agent-skills/ark-fix/SKILL.md +34 -157
  66. package/templates/agent-skills/ark-loop/SKILL.md +31 -153
  67. package/templates/agent-skills/ark-place/SKILL.md +25 -13
  68. package/templates/agent-skills/ark-runtime/SKILL.md +3 -3
  69. package/templates/agent-skills/ark-think/SKILL.md +5 -2
  70. package/templates/agent-skills/ark-upgrade/SKILL.md +13 -8
  71. package/templates/skills/ark-adopt.md +46 -19
  72. package/templates/skills/ark-architect.md +24 -145
  73. package/templates/skills/ark-autopilot.md +40 -30
  74. package/templates/skills/ark-contract.md +21 -105
  75. package/templates/skills/ark-coverage.md +3 -0
  76. package/templates/skills/ark-explain.md +7 -4
  77. package/templates/skills/ark-explore.md +29 -21
  78. package/templates/skills/ark-fix.md +34 -157
  79. package/templates/skills/ark-loop.md +31 -153
  80. package/templates/skills/ark-place.md +25 -13
  81. package/templates/skills/ark-runtime.md +3 -3
  82. package/templates/skills/ark-think.md +5 -2
  83. 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-contract), or record the deliberate loop in ${CONTRACT_SMELL_ACKS_PATH} with a reason.`;
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-contract), or acknowledge with a reason in ${CONTRACT_SMELL_ACKS_PATH}.`;
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-contract), or acknowledge with a reason in ${CONTRACT_SMELL_ACKS_PATH}.`;
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-contract.';
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-contract; never weaken the gate to silence a smell.';
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-contract (or a policy pack) so every populated layer participates in enforcement.',
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
- ? `${mode ? `${mode} · ` : ''}design-weak edges clean; Shape residual remains (see designSmells / plan B)`
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 edge debt'
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
  }
@@ -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-contract (adds the layer patterns to ark.config.json).');
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 edge violations — contract edges are clean, but design smells remain (design-weak). Shape residual is plan B only; not healthy finished.';
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; the contract is not yet the control plane. You do not pick this light. Next: ark start (preview), then ark start --apply; re-check with --doctor.',
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
- 'contract and tree still disagree, or debt is open. Write path does not fully protect you yet. You do not pick this light. Next: do doctor top action #1 (often /ark-adopt, /ark-contract, or /ark-autopilot).',
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 edges. You arrived here; you never turn Enforce on. Next: keep the host write path and CI check; only NEW violations should fail.',
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
- ? 'checked edges are honest; design smells remain. Green is not elegant design. You do not pick this light. Next: one Shape door — /ark-explore shape-focus → dual-plan B; apply B only with /ark-autopilot and your OK. Empty plan A is not done.'
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; patternBets never mechanical-safe'));
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-weak or replace the gate.'
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-contract)');
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 edgesedges match the contract; design residual remains (${modeTitle}). Not healthy finished.`);
1046
+ line(warn, `None on checked importsimport 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-fix`
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 or /ark-contract — reclassify persistence/auth dirs out of Application before claiming ENFORCE.',
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-weak or replace the gate.';
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: hard preToolUse is supported for Write/StrReplace when .cursor/hooks.json is installed + trusted; without runtime-observed hook evidence, hard is unverified. ${mergeBoundary}.`;
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: write path is advisory / best-effort at write (not Claude/Grok/Cursor hard). ${mergeBoundary}.`;
230
+ return `Codex: edits are warning only (not blocked) at write time. ${mergeBoundary}.`;
231
231
  }
232
232
  if (host === 'opencode') {
233
- return `OpenCode: write path is advisory / best-effort (MCP + optional plugin; not Claude/Grok/Antigravity/Cursor hard). ${mergeBoundary}.`;
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}: hard PreToolUse is supported for listed ops when installed + trusted; without runtime-observed hook evidence, hard is unverified. ${mergeBoundary}.`;
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 === 'enforce'
387
- ? 'ENFORCE · design-weak'
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
- ? 'Contract edges are clean, but lived design residual remains. This does not fail PASS — it blocks “healthy finished” until Shape work lands.'
392
- : 'Design smells exist alongside open edge debt. Fix edges first; treat smells as Shape residual after green.';
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 mechanical-safe · never multi-pilot batch
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-weak residual detected</span>
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;
@@ -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="Edges can be green while lived design residual remains (Shape). Not a FAIL.">design-weak</span>`
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 user OK.',
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-fix',
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 with user OK — never multi-pilot batch.',
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-contract',
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-contract',
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-fix',
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-loop',
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-fix',
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-fix',
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-fix',
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-fix',
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-fix',
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-fix',
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-loop',
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 with user OK.',
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-fix',
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-fix',
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-fix',
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 with OK.',
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-contract',
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
  //
@@ -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-fix (one cluster) | /ark-autopilot (user ok on B) | re-doctor after pilot',
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 only to apply B with user OK.
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 with OK) # Shape residual';
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);