arkgate 4.6.0 → 4.6.2

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 (78) hide show
  1. package/CHANGELOG.md +54 -1
  2. package/README.md +11 -5
  3. package/bin/ark-check-runtime.mjs +115 -128
  4. package/bin/ark-mcp-runtime.mjs +86 -48
  5. package/bin/ark.mjs +21 -78
  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-next-actions.mjs +92 -0
  14. package/bin/lib/doctor-plan.mjs +61 -71
  15. package/bin/lib/field-install.mjs +1 -1
  16. package/bin/lib/first-run-help.mjs +221 -0
  17. package/bin/lib/html-report-advisories.mjs +20 -0
  18. package/bin/lib/improvement-compass-map.mjs +20 -20
  19. package/bin/lib/pilot-loop.mjs +1 -1
  20. package/bin/lib/post-green-path.mjs +2 -2
  21. package/bin/lib/prepare-change.mjs +9 -0
  22. package/bin/lib/product-copy.mjs +1 -1
  23. package/bin/lib/start-preview.mjs +17 -10
  24. package/bin/lib/status-command.mjs +19 -0
  25. package/bin/lib/status-manifest.mjs +23 -0
  26. package/bin/lib/team-parliament-io.mjs +338 -0
  27. package/bin/lib/team-parliament.mjs +383 -0
  28. package/bin/lib/upgrade-whats-new.mjs +16 -0
  29. package/bin/lib/violations.mjs +8 -4
  30. package/dist/{configTypes-CC0FEXoF.d.ts → configTypes-B8uIcLaG.d.ts} +5 -0
  31. package/dist/eslint/index.cjs +2 -2
  32. package/dist/eslint/index.d.ts +1 -1
  33. package/dist/eslint/index.js +2 -2
  34. package/dist/index.cjs +8 -8
  35. package/dist/index.d.ts +66 -2
  36. package/dist/index.js +8 -8
  37. package/docs/README.md +5 -5
  38. package/docs/agent-guide.md +21 -17
  39. package/docs/configuration.md +36 -2
  40. package/docs/develop.md +13 -1
  41. package/docs/enthusiast/README.md +1 -1
  42. package/docs/enthusiast/how-to-agent-gates.md +3 -3
  43. package/docs/enthusiast/how-to-pick-shape.md +2 -2
  44. package/docs/enthusiast/tutorial-first-project.md +4 -3
  45. package/docs/package-surface.md +5 -3
  46. package/docs/product-voice.md +48 -10
  47. package/docs/use.md +10 -5
  48. package/package.json +2 -2
  49. package/schemas/ark.config.schema.json +9 -0
  50. package/schemas/ark.status-manifest.schema.json +51 -0
  51. package/server.json +3 -3
  52. package/templates/agent-skills/README.md +1 -1
  53. package/templates/agent-skills/ark-adopt/SKILL.md +59 -23
  54. package/templates/agent-skills/ark-architect/SKILL.md +24 -145
  55. package/templates/agent-skills/ark-autopilot/SKILL.md +49 -32
  56. package/templates/agent-skills/ark-contract/SKILL.md +21 -105
  57. package/templates/agent-skills/ark-coverage/SKILL.md +7 -3
  58. package/templates/agent-skills/ark-explain/SKILL.md +8 -4
  59. package/templates/agent-skills/ark-explore/SKILL.md +38 -21
  60. package/templates/agent-skills/ark-fix/SKILL.md +34 -157
  61. package/templates/agent-skills/ark-loop/SKILL.md +31 -153
  62. package/templates/agent-skills/ark-place/SKILL.md +35 -14
  63. package/templates/agent-skills/ark-runtime/SKILL.md +3 -3
  64. package/templates/agent-skills/ark-think/SKILL.md +6 -2
  65. package/templates/agent-skills/ark-upgrade/SKILL.md +21 -10
  66. package/templates/skills/ark-adopt.md +59 -23
  67. package/templates/skills/ark-architect.md +24 -145
  68. package/templates/skills/ark-autopilot.md +49 -32
  69. package/templates/skills/ark-contract.md +21 -105
  70. package/templates/skills/ark-coverage.md +7 -3
  71. package/templates/skills/ark-explain.md +8 -4
  72. package/templates/skills/ark-explore.md +38 -21
  73. package/templates/skills/ark-fix.md +34 -157
  74. package/templates/skills/ark-loop.md +31 -153
  75. package/templates/skills/ark-place.md +35 -14
  76. package/templates/skills/ark-runtime.md +3 -3
  77. package/templates/skills/ark-think.md +6 -2
  78. package/templates/skills/ark-upgrade.md +21 -10
@@ -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.';
@@ -147,21 +147,16 @@ export function renderStartPreview(preview, options = {}) {
147
147
  } else {
148
148
  console.log('Ark start preview — no files were changed.');
149
149
  }
150
+ if (!applying) {
151
+ console.log('Apply this plan with: arkgate start --apply');
152
+ }
150
153
  if (preview.analysis) {
151
154
  console.log(`Your project looks like: ${preview.analysis.label} (${preview.analysis.archetype}, confidence ${preview.analysis.confidence}).`);
152
155
  }
153
- console.log(`Projected governed coverage: ${preview.projectedCoverage.percent ?? 'unknown'}% (${preview.projectedCoverage.classifiedFiles}/${preview.projectedCoverage.totalFiles} files)`);
154
- const budget = preview.setupBudget;
155
- const arkrulesNote =
156
- budget.arkrulesFiles > 0 ? ` (+${budget.arkrulesFiles} arkrules)` : '';
157
- const gateCount = budget.gateFiles ?? budget.files;
158
- console.log(
159
- `Compact setup budget: ${gateCount}/${budget.maxFiles} gate files${arkrulesNote}, ${budget.bytes}/${budget.maxBytes} bytes${budget.ok ? '' : ' (exceeded)'}.`
160
- );
161
156
  console.log(applying ? 'Files create/edit/delete:' : 'Files to create/edit/delete:');
162
157
  if (preview.changes.length === 0) console.log(' (none)');
163
158
  for (const change of preview.changes) {
164
- console.log(` ${change.action.padEnd(6)} ${change.path} ${change.afterHash ?? '(deleted)'}`);
159
+ console.log(` ${change.action.padEnd(6)} ${change.path}`);
165
160
  }
166
161
  if (!applying) {
167
162
  console.log('Commands in the approved setup plan:');
@@ -179,8 +174,20 @@ export function renderStartPreview(preview, options = {}) {
179
174
  console.log('Unresolved decisions:');
180
175
  for (const decision of preview.unresolvedDecisions) console.log(` ${decision}`);
181
176
  }
177
+ console.log('Details (optional):');
178
+ console.log(`Projected governed coverage: ${preview.projectedCoverage.percent ?? 'unknown'}% (${preview.projectedCoverage.classifiedFiles}/${preview.projectedCoverage.totalFiles} files)`);
179
+ const budget = preview.setupBudget;
180
+ const arkrulesNote =
181
+ budget.arkrulesFiles > 0 ? ` (+${budget.arkrulesFiles} arkrules)` : '';
182
+ const gateCount = budget.gateFiles ?? budget.files;
183
+ console.log(
184
+ `Compact setup budget: ${gateCount}/${budget.maxFiles} gate files${arkrulesNote}, ${budget.bytes}/${budget.maxBytes} bytes${budget.ok ? '' : ' (exceeded)'}.`
185
+ );
186
+ for (const change of preview.changes) {
187
+ console.log(` ${change.action.padEnd(6)} ${change.path} ${change.afterHash ?? '(deleted)'}`);
188
+ }
182
189
  if (!applying) {
183
- console.log('Review complete file contents with --json. Apply this plan with: ark start --apply');
190
+ console.log('Review complete file contents with --json.');
184
191
  }
185
192
  }
186
193
 
@@ -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');
@@ -381,7 +382,23 @@ export function collectStatusFacts(options = {}) {
381
382
  : arkRulesLoaded
382
383
  ? 0
383
384
  : null,
385
+ leftoverDesignWork:
386
+ options.leftoverDesignWork === true ||
387
+ latest?.leftoverDesignWork === true ||
388
+ latest?.designFitness?.designWeak === true ||
389
+ latest?.doctor?.designFitness?.designWeak === true,
384
390
  improvementCompass,
391
+ vsBase: (() => {
392
+ const vsRef = typeof options.vs === 'string' ? options.vs.trim() : '';
393
+ if (!vsRef) return null;
394
+ const baseRef = discoverTeamBaseRef(resolvedRoot, vsRef);
395
+ if (!baseRef) return null;
396
+ return collectVsBaseFacts({
397
+ root: resolvedRoot,
398
+ baseRef,
399
+ configRel: path.basename(configPath),
400
+ });
401
+ })(),
385
402
  };
386
403
  }
387
404
 
@@ -423,6 +440,7 @@ export function runStatusCommand(args = {}) {
423
440
  expectedProjectId: args.expectedProjectId,
424
441
  host: args.host,
425
442
  arkgateVersion: args.arkgateVersion,
443
+ vs: args.vs,
426
444
  });
427
445
 
428
446
  if (asJson || args.json) {
@@ -471,6 +489,7 @@ export function runStatusCommand(args = {}) {
471
489
  ' · not a score'
472
490
  );
473
491
  }
492
+ if (manifest.vsBase?.line) write(` ${manifest.vsBase.line}`);
474
493
  write(` next: [${manifest.nextAction.id}] ${manifest.nextAction.summary}`);
475
494
  }
476
495
 
@@ -230,6 +230,12 @@ export function resolveStatusNextAction(facts, binding, activation, lastCheck, r
230
230
  summary: 'Local write is advisory for this host — keep a required GitHub status on arkgate-check --strict-merge as the hard merge boundary.',
231
231
  };
232
232
  }
233
+ if (facts.leftoverDesignWork === true) {
234
+ return {
235
+ id: 'map-leftover-design',
236
+ summary: 'Leftover design work remains. Map with /ark-explore, then apply one small refactor with /ark-autopilot. Green imports are not done.',
237
+ };
238
+ }
233
239
  if (rules.arkRulesLoaded && (rules.frozenResidual ?? 0) > 0) {
234
240
  return {
235
241
  id: 'review-arkrules-residual',
@@ -311,6 +317,9 @@ export function buildStatusManifest(facts) {
311
317
  const compass = normalizeStatusImprovementCompass(facts.improvementCompass);
312
318
  if (compass)
313
319
  status.improvementCompass = compass;
320
+ if (facts.vsBase && typeof facts.vsBase.baseRef === 'string' && facts.vsBase.baseRef.length > 0) {
321
+ status.vsBase = facts.vsBase;
322
+ }
314
323
  return status;
315
324
  }
316
325
  const STATUS_COMPASS_MODE_SET = new Set(STATUS_COMPASS_MODES);
@@ -576,5 +585,19 @@ export const ARK_STATUS_MANIFEST_SCHEMA = {
576
585
  contractHash: { type: 'string', minLength: 1 },
577
586
  },
578
587
  },
588
+ vsBase: {
589
+ type: 'object',
590
+ description: 'Checkout vs a git base ref: pin, contract identity, baseline grow. Advisory honesty only — never a gate input.',
591
+ additionalProperties: false,
592
+ required: ['baseRef', 'line', 'pinLocal', 'pinBase', 'contractEqual', 'baselineGrew'],
593
+ properties: {
594
+ baseRef: { type: 'string', minLength: 1 },
595
+ line: { type: 'string', minLength: 1 },
596
+ pinLocal: { anyOf: [{ type: 'string', minLength: 1 }, { type: 'null' }] },
597
+ pinBase: { anyOf: [{ type: 'string', minLength: 1 }, { type: 'null' }] },
598
+ contractEqual: { type: 'boolean' },
599
+ baselineGrew: { type: 'boolean' },
600
+ },
601
+ },
579
602
  },
580
603
  };