arkgate 4.6.0 → 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 (75) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/README.md +9 -5
  3. package/bin/ark-check-runtime.mjs +98 -7
  4. package/bin/ark-mcp-runtime.mjs +17 -1
  5. package/bin/ark.mjs +5 -3
  6. package/bin/lib/agent-projection.mjs +1 -1
  7. package/bin/lib/analysis-engine.mjs +3 -3
  8. package/bin/lib/ci-and-commands.mjs +11 -11
  9. package/bin/lib/config-contract.mjs +2 -0
  10. package/bin/lib/contract-smells.mjs +5 -5
  11. package/bin/lib/design-smells.mjs +1 -1
  12. package/bin/lib/doctor-advisories.mjs +9 -0
  13. package/bin/lib/doctor-plan.mjs +4 -4
  14. package/bin/lib/field-install.mjs +1 -1
  15. package/bin/lib/html-report-advisories.mjs +20 -0
  16. package/bin/lib/improvement-compass-map.mjs +20 -20
  17. package/bin/lib/pilot-loop.mjs +1 -1
  18. package/bin/lib/post-green-path.mjs +2 -2
  19. package/bin/lib/prepare-change.mjs +9 -0
  20. package/bin/lib/product-copy.mjs +1 -1
  21. package/bin/lib/status-command.mjs +14 -0
  22. package/bin/lib/status-manifest.mjs +17 -0
  23. package/bin/lib/team-parliament-io.mjs +338 -0
  24. package/bin/lib/team-parliament.mjs +383 -0
  25. package/bin/lib/upgrade-whats-new.mjs +16 -0
  26. package/bin/lib/violations.mjs +8 -4
  27. package/dist/{configTypes-CC0FEXoF.d.ts → configTypes-B8uIcLaG.d.ts} +5 -0
  28. package/dist/eslint/index.cjs +2 -2
  29. package/dist/eslint/index.d.ts +1 -1
  30. package/dist/eslint/index.js +2 -2
  31. package/dist/index.cjs +19 -19
  32. package/dist/index.d.ts +61 -2
  33. package/dist/index.js +22 -22
  34. package/docs/README.md +5 -5
  35. package/docs/agent-guide.md +21 -17
  36. package/docs/configuration.md +36 -2
  37. package/docs/develop.md +13 -1
  38. package/docs/enthusiast/README.md +1 -1
  39. package/docs/enthusiast/how-to-agent-gates.md +3 -3
  40. package/docs/enthusiast/how-to-pick-shape.md +2 -2
  41. package/docs/enthusiast/tutorial-first-project.md +4 -3
  42. package/docs/package-surface.md +5 -3
  43. package/docs/product-voice.md +48 -10
  44. package/docs/use.md +6 -1
  45. package/package.json +2 -2
  46. package/schemas/ark.config.schema.json +9 -0
  47. package/schemas/ark.status-manifest.schema.json +51 -0
  48. package/server.json +3 -3
  49. package/templates/agent-skills/README.md +1 -1
  50. package/templates/agent-skills/ark-adopt/SKILL.md +46 -19
  51. package/templates/agent-skills/ark-architect/SKILL.md +24 -145
  52. package/templates/agent-skills/ark-autopilot/SKILL.md +40 -30
  53. package/templates/agent-skills/ark-contract/SKILL.md +21 -105
  54. package/templates/agent-skills/ark-coverage/SKILL.md +3 -0
  55. package/templates/agent-skills/ark-explain/SKILL.md +7 -4
  56. package/templates/agent-skills/ark-explore/SKILL.md +25 -17
  57. package/templates/agent-skills/ark-fix/SKILL.md +34 -157
  58. package/templates/agent-skills/ark-loop/SKILL.md +31 -153
  59. package/templates/agent-skills/ark-place/SKILL.md +25 -13
  60. package/templates/agent-skills/ark-runtime/SKILL.md +3 -3
  61. package/templates/agent-skills/ark-think/SKILL.md +5 -2
  62. package/templates/agent-skills/ark-upgrade/SKILL.md +11 -7
  63. package/templates/skills/ark-adopt.md +46 -19
  64. package/templates/skills/ark-architect.md +24 -145
  65. package/templates/skills/ark-autopilot.md +40 -30
  66. package/templates/skills/ark-contract.md +21 -105
  67. package/templates/skills/ark-coverage.md +3 -0
  68. package/templates/skills/ark-explain.md +7 -4
  69. package/templates/skills/ark-explore.md +25 -17
  70. package/templates/skills/ark-fix.md +34 -157
  71. package/templates/skills/ark-loop.md +31 -153
  72. package/templates/skills/ark-place.md +25 -13
  73. package/templates/skills/ark-runtime.md +3 -3
  74. package/templates/skills/ark-think.md +5 -2
  75. package/templates/skills/ark-upgrade.md +11 -7
@@ -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
  }
@@ -208,7 +208,7 @@ export function runCoverage(root, config, files, rules, asJson) {
208
208
  }
209
209
  }
210
210
  console.log('');
211
- 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).');
212
212
  }
213
213
  if (layersWithoutRules.length > 0) {
214
214
  console.log('');
@@ -846,7 +846,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
846
846
  suggest:
847
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.',
848
848
  adapt:
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, /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).',
850
850
  enforce:
851
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.',
852
852
  };
@@ -973,7 +973,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
973
973
  }
974
974
  if (cov.suggestions.length > 0) {
975
975
  line(warn, `${cov.suggestions.length} ungoverned director(y/ies) — proposals: ${arkCommand(root, 'ark-check', '--coverage')}`);
976
- actions.push('classify the ungoverned directories (/ark-contract)');
976
+ actions.push('classify the ungoverned directories (/ark-adopt)');
977
977
  }
978
978
  if (cov.emptyLayers.length > 0) line(warn, `Empty layers (pattern matches nothing): ${cov.emptyLayers.join(', ')}`);
979
979
  if (cov.layersWithoutRules.length > 0) line(warn, `Layers with no rule edge: ${cov.layersWithoutRules.join(', ')}`);
@@ -1060,7 +1060,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
1060
1060
  }
1061
1061
  if (activeCount > 0) {
1062
1062
  actions.push(
1063
- `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`
1064
1064
  );
1065
1065
  }
1066
1066
  }
@@ -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
 
@@ -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),
@@ -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
  }
@@ -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
 
@@ -16,13 +16,13 @@ export const POST_GREEN_PRIMARY_SKILL = '/ark-explore';
16
16
 
17
17
  /**
18
18
  * Canonical human / agent next-action string (single door).
19
- * Chained: explore shape-focus then autopilot only to apply B with user OK.
19
+ * Chained: explore shape-focus then autopilot applies one B pilot.
20
20
  */
21
21
  export const POST_GREEN_PRIMARY_ACTION = POST_GREEN_HUMAN;
22
22
 
23
23
  /** Short label for tables / metrics. */
24
24
  export const POST_GREEN_PRIMARY_SHORT =
25
- '/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';
26
26
 
27
27
  /** Placement coaching while design residual remains (new code only). */
28
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);
@@ -23,7 +23,7 @@ export function operatingModeTitle(mode, leftoverDesign) {
23
23
  export const LEFTOVER_DESIGN_BADGE = LEFTOVER_DESIGN_LABEL;
24
24
 
25
25
  export const POST_GREEN_HUMAN =
26
- 'Imports check out, but the design is still messy. Map leftover work with /ark-explore shape-focus, then apply one small refactor via /ark-autopilot with your OK. A clean import check is not done; pattern bets are never auto-applied.';
26
+ 'Imports check out, but the design is still messy. Map leftover work with /ark-explore shape-focus, then apply one small refactor via /ark-autopilot. A clean import check is not done; pattern bets are never auto-applied.';
27
27
 
28
28
  export const POST_GREEN_LEDE =
29
29
  'Import rules are clean, but leftover design work remains. That does not fail the check — it only means “done” is still wrong until you tidy shape.';
@@ -25,6 +25,7 @@ import { detectActiveAgentHost } from './skill-install.mjs';
25
25
  import { readBaseline } from './violations.mjs';
26
26
  import { reportsDir, readJsonSafe } from './html-report.mjs';
27
27
  import { summarizeRulesUnderContract } from './rules-under-contract.mjs';
28
+ import { collectVsBaseFacts, discoverTeamBaseRef } from './team-parliament-io.mjs';
28
29
 
29
30
  function sha256Hex(value) {
30
31
  return createHash('sha256').update(value, 'utf8').digest('hex');
@@ -382,6 +383,17 @@ export function collectStatusFacts(options = {}) {
382
383
  ? 0
383
384
  : null,
384
385
  improvementCompass,
386
+ vsBase: (() => {
387
+ const vsRef = typeof options.vs === 'string' ? options.vs.trim() : '';
388
+ if (!vsRef) return null;
389
+ const baseRef = discoverTeamBaseRef(resolvedRoot, vsRef);
390
+ if (!baseRef) return null;
391
+ return collectVsBaseFacts({
392
+ root: resolvedRoot,
393
+ baseRef,
394
+ configRel: path.basename(configPath),
395
+ });
396
+ })(),
385
397
  };
386
398
  }
387
399
 
@@ -423,6 +435,7 @@ export function runStatusCommand(args = {}) {
423
435
  expectedProjectId: args.expectedProjectId,
424
436
  host: args.host,
425
437
  arkgateVersion: args.arkgateVersion,
438
+ vs: args.vs,
426
439
  });
427
440
 
428
441
  if (asJson || args.json) {
@@ -471,6 +484,7 @@ export function runStatusCommand(args = {}) {
471
484
  ' · not a score'
472
485
  );
473
486
  }
487
+ if (manifest.vsBase?.line) write(` ${manifest.vsBase.line}`);
474
488
  write(` next: [${manifest.nextAction.id}] ${manifest.nextAction.summary}`);
475
489
  }
476
490
 
@@ -311,6 +311,9 @@ export function buildStatusManifest(facts) {
311
311
  const compass = normalizeStatusImprovementCompass(facts.improvementCompass);
312
312
  if (compass)
313
313
  status.improvementCompass = compass;
314
+ if (facts.vsBase && typeof facts.vsBase.baseRef === 'string' && facts.vsBase.baseRef.length > 0) {
315
+ status.vsBase = facts.vsBase;
316
+ }
314
317
  return status;
315
318
  }
316
319
  const STATUS_COMPASS_MODE_SET = new Set(STATUS_COMPASS_MODES);
@@ -576,5 +579,19 @@ export const ARK_STATUS_MANIFEST_SCHEMA = {
576
579
  contractHash: { type: 'string', minLength: 1 },
577
580
  },
578
581
  },
582
+ vsBase: {
583
+ type: 'object',
584
+ description: 'Checkout vs a git base ref: pin, contract identity, baseline grow. Advisory honesty only — never a gate input.',
585
+ additionalProperties: false,
586
+ required: ['baseRef', 'line', 'pinLocal', 'pinBase', 'contractEqual', 'baselineGrew'],
587
+ properties: {
588
+ baseRef: { type: 'string', minLength: 1 },
589
+ line: { type: 'string', minLength: 1 },
590
+ pinLocal: { anyOf: [{ type: 'string', minLength: 1 }, { type: 'null' }] },
591
+ pinBase: { anyOf: [{ type: 'string', minLength: 1 }, { type: 'null' }] },
592
+ contractEqual: { type: 'boolean' },
593
+ baselineGrew: { type: 'boolean' },
594
+ },
595
+ },
579
596
  },
580
597
  };