arkgate 4.8.23 → 4.8.25

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 (201) hide show
  1. package/CHANGELOG.md +153 -0
  2. package/README.md +7 -5
  3. package/bin/ark-check-runtime.mjs +41 -91
  4. package/bin/ark-dashboard.mjs +56 -53
  5. package/bin/ark-layer-match.mjs +707 -121
  6. package/bin/ark-mcp-runtime.mjs +448 -169
  7. package/bin/ark-mcp.mjs +68 -4
  8. package/bin/ark-shared.mjs +42 -22
  9. package/bin/ark.mjs +65 -97
  10. package/bin/lib/adapter-contract-types.mjs +5 -0
  11. package/bin/lib/adapter-contract.mjs +15 -0
  12. package/bin/lib/adoption-stance.mjs +2 -2
  13. package/bin/lib/adr-path.mjs +3 -3
  14. package/bin/lib/agent-gates.mjs +0 -61
  15. package/bin/lib/agent-homes.mjs +12 -21
  16. package/bin/lib/agent-projection-command.mjs +9 -5
  17. package/bin/lib/ambient-state.mjs +9 -30
  18. package/bin/lib/analysis-engine.mjs +15 -11
  19. package/bin/lib/architecture-scan.mjs +61 -15
  20. package/bin/lib/ark-order-doctor.mjs +4 -29
  21. package/bin/lib/ark-order-facts.mjs +319 -17
  22. package/bin/lib/ark-order-sensors.mjs +28 -101
  23. package/bin/lib/ark-order-types.mjs +3 -2
  24. package/bin/lib/ark-run-doctor.mjs +17 -28
  25. package/bin/lib/ark-run-facts.mjs +461 -48
  26. package/bin/lib/ark-run-sensors.mjs +6 -4
  27. package/bin/lib/arkgate-pins.mjs +91 -0
  28. package/bin/lib/arkrule-file-hints.mjs +2 -47
  29. package/bin/lib/arkrules-contract.mjs +124 -133
  30. package/bin/lib/arkrules-sensors.mjs +55 -252
  31. package/bin/lib/check-args.mjs +85 -1
  32. package/bin/lib/ci-and-commands.mjs +24 -78
  33. package/bin/lib/ci-merge-boundary.mjs +1 -1
  34. package/bin/lib/class-source-scan.mjs +479 -0
  35. package/bin/lib/clone-detection.mjs +474 -0
  36. package/bin/lib/codex-home.mjs +5 -3
  37. package/bin/lib/config-contract-slices.mjs +739 -0
  38. package/bin/lib/config-contract.mjs +42 -607
  39. package/bin/lib/config-extras.mjs +16 -22
  40. package/bin/lib/config-warnings.mjs +32 -0
  41. package/bin/lib/contract-smells.mjs +33 -15
  42. package/bin/lib/core-ratchet.mjs +0 -2
  43. package/bin/lib/dashboard-cli.mjs +62 -0
  44. package/bin/lib/deep-module-coach.mjs +2 -4
  45. package/bin/lib/deploy-path.mjs +13 -38
  46. package/bin/lib/design-delta.mjs +1 -1
  47. package/bin/lib/design-smells.mjs +2 -2
  48. package/bin/lib/diagnostic-catalog.mjs +23 -9
  49. package/bin/lib/doctor-advisories.mjs +78 -8
  50. package/bin/lib/doctor-green-cite.mjs +1 -1
  51. package/bin/lib/doctor-human.mjs +1 -1
  52. package/bin/lib/doctor-next-actions.mjs +19 -4
  53. package/bin/lib/doctor-plan.mjs +9 -13
  54. package/bin/lib/duplication-io.mjs +572 -0
  55. package/bin/lib/effective-contract-load.mjs +282 -83
  56. package/bin/lib/enforcement-profiles.mjs +40 -4
  57. package/bin/lib/entry-points-io.mjs +424 -0
  58. package/bin/lib/extra-merge-teeth.mjs +36 -0
  59. package/bin/lib/field-install.mjs +38 -14
  60. package/bin/lib/first-run-help.mjs +10 -5
  61. package/bin/lib/flat-parent-importers.mjs +139 -0
  62. package/bin/lib/flat-parent-pilot.mjs +40 -100
  63. package/bin/lib/gate-files.mjs +58 -90
  64. package/bin/lib/git-change-scope.mjs +109 -0
  65. package/bin/lib/github-enforcement.mjs +166 -29
  66. package/bin/lib/golden-pattern.mjs +1 -1
  67. package/bin/lib/hook-templates.mjs +17 -15
  68. package/bin/lib/host-support-matrix.mjs +3 -1
  69. package/bin/lib/html-report-advisories.mjs +4 -0
  70. package/bin/lib/html-report-depth.mjs +2 -2
  71. package/bin/lib/html-report.mjs +2 -2
  72. package/bin/lib/import-graph-projection.mjs +112 -0
  73. package/bin/lib/import-resolve.mjs +13 -1
  74. package/bin/lib/improvement-compass-doctor.mjs +1 -24
  75. package/bin/lib/install-migrate.mjs +50 -73
  76. package/bin/lib/invariant-coverage-io.mjs +4 -3
  77. package/bin/lib/invariant-coverage.mjs +85 -11
  78. package/bin/lib/invariant-probe-cli.mjs +347 -0
  79. package/bin/lib/invariant-probe-io.mjs +165 -0
  80. package/bin/lib/invariant-probe-runner.mjs +268 -0
  81. package/bin/lib/invariant-probe-sites.mjs +174 -0
  82. package/bin/lib/invariant-probe-workspace.mjs +183 -0
  83. package/bin/lib/invariant-probe.mjs +461 -0
  84. package/bin/lib/invariant-tests-path.mjs +16 -22
  85. package/bin/lib/literal-path-drift.mjs +4 -2
  86. package/bin/lib/managed-upgrade.mjs +2 -8
  87. package/bin/lib/mcp-adoption.mjs +13 -6
  88. package/bin/lib/mcp-hook-payload.mjs +403 -63
  89. package/bin/lib/orphan-modules-io.mjs +458 -0
  90. package/bin/lib/orphan-modules.mjs +251 -0
  91. package/bin/lib/outside-importers.mjs +197 -0
  92. package/bin/lib/package-manager.mjs +60 -2
  93. package/bin/lib/parse-health.mjs +1 -1
  94. package/bin/lib/passthrough-commands.mjs +83 -0
  95. package/bin/lib/persistence-write-hint.mjs +106 -0
  96. package/bin/lib/pin-dependent-gates.mjs +117 -0
  97. package/bin/lib/policy-delta-io.mjs +219 -14
  98. package/bin/lib/post-green-path.mjs +1 -1
  99. package/bin/lib/prepare-change.mjs +115 -7
  100. package/bin/lib/prepare-write.mjs +2 -2
  101. package/bin/lib/presets.mjs +59 -73
  102. package/bin/lib/product-copy.mjs +1 -6
  103. package/bin/lib/project-root.mjs +31 -62
  104. package/bin/lib/remediation.mjs +23 -4
  105. package/bin/lib/resolved-candidate-facts.mjs +405 -117
  106. package/bin/lib/rules-inventory.mjs +22 -3
  107. package/bin/lib/rules-under-contract.mjs +247 -9
  108. package/bin/lib/scan-files.mjs +36 -4
  109. package/bin/lib/schema-validation.mjs +130 -0
  110. package/bin/lib/sensor-promote-cli.mjs +2 -11
  111. package/bin/lib/sensor-promote-io.mjs +33 -22
  112. package/bin/lib/sensor-promotion.mjs +1 -1
  113. package/bin/lib/skill-catalog-apply.mjs +1 -1
  114. package/bin/lib/skill-install.mjs +42 -38
  115. package/bin/lib/snippet-analysis.mjs +140 -6
  116. package/bin/lib/source-policy.mjs +34 -0
  117. package/bin/lib/start-install-recovery.mjs +72 -2
  118. package/bin/lib/start-preview.mjs +31 -1
  119. package/bin/lib/states-transitions-presence.mjs +3 -3
  120. package/bin/lib/status-command.mjs +35 -22
  121. package/bin/lib/status-manifest.mjs +50 -30
  122. package/bin/lib/status-transition-catalog.mjs +0 -6
  123. package/bin/lib/suggestions.mjs +3 -3
  124. package/bin/lib/team-parliament-io.mjs +76 -88
  125. package/bin/lib/team-parliament.mjs +21 -10
  126. package/bin/lib/upgrade-behavior-changes.mjs +2 -2
  127. package/bin/lib/upgrade-command.mjs +2 -24
  128. package/bin/lib/upgrade-outcome.mjs +1 -1
  129. package/bin/lib/upstream-report.mjs +21 -10
  130. package/bin/lib/violations.mjs +10 -9
  131. package/bin/lib/weakest-link.mjs +13 -0
  132. package/bin/lib/write-path-capabilities.mjs +6 -4
  133. package/dist/chunk-A5EIY3HI.cjs +5 -0
  134. package/dist/chunk-AHACEKSV.js +16 -0
  135. package/dist/chunk-SVSQVGFJ.js +5 -0
  136. package/dist/chunk-UN5EH4QV.cjs +16 -0
  137. package/dist/{configTypes-Bbf_MFGe.d.ts → configTypes-GPvvdleM.d.cts} +30 -4
  138. package/dist/configTypes-GPvvdleM.d.ts +257 -0
  139. package/dist/eslint/index.cjs +18 -5
  140. package/dist/eslint/index.d.cts +368 -0
  141. package/dist/eslint/index.d.ts +139 -156
  142. package/dist/eslint/index.js +11 -5
  143. package/dist/index.cjs +36 -35
  144. package/dist/index.d.cts +5547 -0
  145. package/dist/index.d.ts +3283 -198
  146. package/dist/index.js +36 -35
  147. package/dist/inspectorListen-6DQVJ4DC.js +1 -0
  148. package/dist/inspectorListen-KGMIQ2TF.cjs +1 -0
  149. package/dist/nestjs/index.cjs +1 -18
  150. package/dist/nestjs/index.d.cts +22 -0
  151. package/dist/nestjs/index.d.ts +1 -3
  152. package/dist/nestjs/index.js +1 -18
  153. package/dist/order/index.cjs +1 -1
  154. package/dist/order/index.d.cts +155 -0
  155. package/dist/order/index.d.ts +15 -3
  156. package/dist/order/index.js +1 -1
  157. package/dist/runtime/index.cjs +13 -30
  158. package/dist/{diagnosticCatalog-35mrwAHe.d.ts → runtime/index.d.cts} +828 -276
  159. package/dist/runtime/index.d.ts +2741 -133
  160. package/dist/runtime/index.js +13 -30
  161. package/dist/{types-Bdj32QwA.d.ts → types-CnauMQQL.d.cts} +607 -163
  162. package/dist/types-CnauMQQL.d.ts +1800 -0
  163. package/docs/README.md +3 -1
  164. package/docs/agent-guide.md +217 -16
  165. package/docs/ai-gates.md +174 -26
  166. package/docs/arkorder.md +32 -15
  167. package/docs/brownfield-adoption.md +5 -1
  168. package/docs/configuration.md +230 -31
  169. package/docs/develop.md +2 -1
  170. package/docs/diagnostics.md +212 -19
  171. package/docs/package-surface.md +35 -9
  172. package/docs/product-voice.md +7 -1
  173. package/package.json +69 -19
  174. package/schemas/ark.analysis-result.schema.json +15 -0
  175. package/schemas/ark.config.schema.json +77 -12
  176. package/schemas/ark.invariant-probe.schema.json +368 -0
  177. package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
  178. package/schemas/ark.status-manifest.schema.json +22 -0
  179. package/server.json +3 -9
  180. package/templates/agent-skills/README.md +1 -1
  181. package/templates/agent-skills/ark-adopt/SKILL.md +5 -1
  182. package/templates/agent-skills/ark-contract/SKILL.md +17 -0
  183. package/templates/agent-skills/ark-coverage/SKILL.md +2 -0
  184. package/templates/agent-skills/ark-explore/SKILL.md +7 -0
  185. package/templates/agent-skills/ark-fix/SKILL.md +20 -0
  186. package/templates/agent-skills/ark-order/SKILL.md +4 -2
  187. package/templates/agent-skills/ark-place/SKILL.md +8 -0
  188. package/templates/agent-skills/ark-runtime/SKILL.md +6 -1
  189. package/templates/arkrules/PersistenceAdapters.json +1 -1
  190. package/templates/arkrules/PresentationAdapters.json +1 -1
  191. package/templates/skills/ark-adopt.md +5 -1
  192. package/templates/skills/ark-contract.md +17 -0
  193. package/templates/skills/ark-coverage.md +2 -0
  194. package/templates/skills/ark-explore.md +7 -0
  195. package/templates/skills/ark-fix.md +20 -0
  196. package/templates/skills/ark-order.md +4 -2
  197. package/templates/skills/ark-place.md +8 -0
  198. package/templates/skills/ark-runtime.md +6 -1
  199. package/bin/lib/ark-order-error.mjs +0 -18
  200. package/bin/lib/ark-order-invariants.mjs +0 -346
  201. package/dist/types-Ik_OakcI.d.ts +0 -183
@@ -19,6 +19,15 @@ import {
19
19
  } from './reshape-decisions.mjs';
20
20
  import { printParseHealthSection, summarizeParseHealth } from './parse-health.mjs';
21
21
  import { detectGraphBlindSpots, printGraphBlindSection } from './graph-blind.mjs';
22
+ import {
23
+ computeOrphanModules,
24
+ printOrphanModulesCompactLine,
25
+ printOrphanModulesSection,
26
+ } from './orphan-modules-io.mjs';
27
+ import {
28
+ computeCrossWallDuplication,
29
+ printCrossWallDuplicationSection,
30
+ } from './duplication-io.mjs';
22
31
  import {
23
32
  formatArkRulesDoctorLines,
24
33
  summarizeRulesUnderContract,
@@ -38,7 +47,12 @@ import {
38
47
  } from './ark-order-doctor.mjs';
39
48
  import { composeMergePlanesHonesty } from './extra-merge-teeth.mjs';
40
49
  import { collectPrototypeShortcutsResidual } from './prototype-shortcuts.mjs';
41
- import { sliceAliasReport, sliceCountReport, sliceIdentityCollisions } from '../ark-layer-match.mjs';
50
+ import {
51
+ crossParentViaSharedHubs,
52
+ sliceAliasReport,
53
+ sliceCountReport,
54
+ sliceIdentityCollisions,
55
+ } from '../ark-layer-match.mjs';
42
56
 
43
57
  export function attachExtraDoctorSections(rulesUnderContract, config, classification, findings) {
44
58
  const arkRulesMerge = {
@@ -57,6 +71,7 @@ export function attachExtraDoctorSections(rulesUnderContract, config, classifica
57
71
  findings,
58
72
  classification,
59
73
  arkRules: arkRulesMerge,
74
+ layerFlow: { layers: config?.layers, rules: config?.rules },
60
75
  });
61
76
  const arkOrder = summarizeArkOrderSection({
62
77
  arkOrder: config?.arkOrder,
@@ -137,6 +152,7 @@ export function printCompactExtraDoctorLines(advisories, io) {
137
152
  io.line(io.warn, flatMoves[0].evidence);
138
153
  if (flatMoves.length > 1) io.line(' ', `${flatMoves.length - 1} more flat-parent suggestion(s) in --doctor --all`);
139
154
  }
155
+ printOrphanModulesCompactLine(advisories?.orphanModules, io);
140
156
  const noDomain = advisories?.noDomainFrontend;
141
157
  if (noDomain?.ask) {
142
158
  console.log('');
@@ -231,8 +247,13 @@ function classificationFromCoverage(cov) {
231
247
  };
232
248
  }
233
249
 
234
- /** `activeViolations` must already exclude frozen baseline keys (report residual parity). */
235
- export function computeDoctorAdvisories(root, config, cov, rules, files, ts, parseHealth, facts, activeViolations) {
250
+ /**
251
+ * `activeViolations` must already exclude frozen baseline keys (report residual parity).
252
+ * `facts.importGraph` is the scan's importer index; `view.details` (Details / report) adds
253
+ * the unused-exports tier and copies across a wall. `view.changed` (a changed-files scope)
254
+ * keeps copies across a wall `not-run`.
255
+ */
256
+ export function computeDoctorAdvisories(root, config, cov, rules, files, ts, parseHealth, facts, activeViolations, view = {}) {
236
257
  const physicalCohesion = computePhysicalCohesion(root, files);
237
258
  const decisionMemory = computeReshapeDecisionMemory(root, files);
238
259
  physicalCohesion.reshapeDecisions = decisionMemory.summary;
@@ -289,6 +310,7 @@ export function computeDoctorAdvisories(root, config, cov, rules, files, ts, par
289
310
  });
290
311
  const sliceIdentityHits = sliceIdentityCollisions(rules ?? config?.rules);
291
312
  const slices = sliceCountReport(activeViolations);
313
+ const sharedWalkHubs = crossParentViaSharedHubs(activeViolations);
292
314
  const aliasFiles = [];
293
315
  for (const entry of Array.isArray(files) ? files : []) {
294
316
  const rel = aliasScanPath(root, entry);
@@ -300,9 +322,12 @@ export function computeDoctorAdvisories(root, config, cov, rules, files, ts, par
300
322
  files: aliasFiles,
301
323
  rules: rules ?? config?.rules,
302
324
  layers: config?.layers,
325
+ facts,
326
+ ts,
303
327
  });
304
328
  return {
305
329
  ...(slices ? { slices } : {}),
330
+ ...(sharedWalkHubs ? { sharedWalkHubs } : {}),
306
331
  ...(sliceAliases ? { sliceAliases } : {}),
307
332
  ...(flatParentPilot ? { flatParentPilot } : {}),
308
333
  ...(prototypeShortcuts ? { prototypeShortcuts } : {}),
@@ -315,10 +340,32 @@ export function computeDoctorAdvisories(root, config, cov, rules, files, ts, par
315
340
  parseHealth: parseHealth ?? summarizeParseHealth(),
316
341
  // Y09 direction: advisory graph-blind spots (template-interpolation); never hard verdict.
317
342
  graphBlindSpots: detectGraphBlindSpots(ts, root, files),
343
+ // ADR 0037: files nothing imports (projection of the resolved import edges; notAScore).
344
+ orphanModules: computeOrphanModules({
345
+ root,
346
+ config: { ...config, rules: rules ?? config?.rules },
347
+ ts,
348
+ importGraph: facts?.importGraph,
349
+ details: view.details === true,
350
+ }),
351
+ // ADR 0038: copies across a wall (doctor-time token fingerprints; Details / report only).
352
+ crossWallDuplication: computeCrossWallDuplication({
353
+ root,
354
+ config,
355
+ rules: rules ?? config?.rules,
356
+ ts,
357
+ files: aliasFiles,
358
+ details: view.details === true,
359
+ changed: view.changed === true,
360
+ }),
318
361
  // AR12 — Rules under contract (honest counts; real test I/O, never empty-fileContents stub).
319
362
  // P1M: pass classification so extraMergeTeeth cannot arm at 0% governed.
320
363
  stewardNudge: collectStewardNudge(root, config),
321
364
  rulesUnderContract,
365
+ // AR15: doctor.rulesMigration — inventoried / under contract / frozen (not a score).
366
+ ...(rulesUnderContract?.rulesMigration
367
+ ? { rulesMigration: rulesUnderContract.rulesMigration }
368
+ : {}),
322
369
  arkRun,
323
370
  arkOrder,
324
371
  };
@@ -328,13 +375,34 @@ export function printDoctorAdvisories(advisories, io) {
328
375
  printFlatParentPilot(advisories?.flatParentPilot, io);
329
376
  const sliceAliases = advisories?.sliceAliases;
330
377
  if (sliceAliases && sliceAliases.notAScore === true && Array.isArray(sliceAliases.moves)) {
378
+ if (sliceAliases.moves.length > 0) {
379
+ console.log('');
380
+ console.log(io.color.bold('Slice aliases (owed move)'));
381
+ io.line(io.warn, sliceAliases.debt);
382
+ for (const move of sliceAliases.moves) {
383
+ io.line(io.warn, `${move.from} → ${move.to}. Move these files to ${move.destination}. They are not finished.`);
384
+ if (move.advisory) io.line(io.warn, move.advisory);
385
+ for (const file of move.files ?? []) io.line(' ', file);
386
+ }
387
+ }
388
+ if (Array.isArray(sliceAliases.pinned) && sliceAliases.pinned.length > 0) {
389
+ console.log('');
390
+ console.log(io.color.bold('Pinned slice aliases'));
391
+ for (const row of sliceAliases.pinned) {
392
+ const label = typeof row.reason === 'string' && row.reason.length > 0 ? ` (${row.reason})` : '';
393
+ io.line(' ', `${row.from} → ${row.to}${label} stays. It is not an owed move.`);
394
+ for (const file of row.files ?? []) io.line(' ', file);
395
+ }
396
+ }
397
+ }
398
+ const hubs = advisories?.sharedWalkHubs;
399
+ if (hubs && hubs.notAScore === true && Array.isArray(hubs.hubs) && hubs.hubs.length > 0) {
331
400
  console.log('');
332
- console.log(io.color.bold('Slice aliases (owed move)'));
333
- io.line(io.warn, sliceAliases.debt);
334
- for (const move of sliceAliases.moves) {
335
- io.line(io.warn, `${move.from} → ${move.to}. Move these files to ${move.destination}. They are not finished.`);
336
- for (const file of move.files ?? []) io.line(' ', file);
401
+ console.log(io.color.bold('Shared walk hubs (not a score)'));
402
+ for (const hub of hubs.hubs) {
403
+ io.line(io.warn, `${hub.file} sits on ${hub.count} of ${hubs.total} CROSS_PARENT_VIA_SHARED paths.`);
337
404
  }
405
+ io.line(' ', io.color.dim(`Next: ${hubs.nextAction}`));
338
406
  }
339
407
  const sliceIdentity = advisories?.sliceIdentity;
340
408
  if (Array.isArray(sliceIdentity?.collisions) && sliceIdentity.collisions.length > 0) {
@@ -354,6 +422,8 @@ export function printDoctorAdvisories(advisories, io) {
354
422
  printReshapeDecisionsSection(advisories.physicalCohesion?.reshapeDecisions, io);
355
423
  printParseHealthSection(advisories.parseHealth, io);
356
424
  printGraphBlindSection(advisories.graphBlindSpots, io);
425
+ printOrphanModulesSection(advisories.orphanModules, io);
426
+ printCrossWallDuplicationSection(advisories.crossWallDuplication, io);
357
427
  const nudge = advisories.stewardNudge;
358
428
  if ((nudge?.needsStewards || nudge?.drift || nudge?.emptyStewardsPastGrace) && nudge.ask) {
359
429
  console.log('');
@@ -9,7 +9,7 @@ import { REQUIRED_GATE_WORKFLOW } from './gate-files.mjs';
9
9
  import { CI_MERGE_BOUNDARY_REL } from './ci-merge-boundary.mjs';
10
10
 
11
11
  export const HEALTHY_CLAIM = 'Healthy — nothing to do.';
12
- export const HEALTHY_KEEP = ' Keep write path + CI.';
12
+ const HEALTHY_KEEP = ' Keep write path + CI.';
13
13
  export const UNCITE_SUFFIX =
14
14
  'not green until a file, config key, or test is named';
15
15
 
@@ -169,7 +169,7 @@ export function printDoctorCompactHuman(view) {
169
169
  adrPresence: view.adrPresence,
170
170
  statesTransitions: view.statesTransitions, statusTransitionCatalog: view.statusTransitionCatalog, noDomainFrontend: view.noDomainFrontend, invariantTestsPath: view.invariantTestsPath, invariantCoverageRoots: view.invariantCoverageRoots,
171
171
  },
172
- { line, warn }
172
+ { line, warn, color }
173
173
  );
174
174
 
175
175
  if (violations.length === 0) {
@@ -13,7 +13,7 @@ import { REQUIRED_GATE_WORKFLOW } from './gate-files.mjs';
13
13
  export const PACKAGE_UNRESOLVED_NEXT_ACTION =
14
14
  'Install arkgate in this project first (npx --package=arkgate, or pnpm add -D arkgate -w / yarn add -D arkgate -W at a workspace root), then re-run --doctor';
15
15
 
16
- export function packageUnresolvedNextAction(ctx) {
16
+ function packageUnresolvedNextAction(ctx) {
17
17
  if (ctx?.packageInstalled !== false || ctx?.selfHost === true) return null;
18
18
  const code = ctx?.packageVersionTruth?.code;
19
19
  if (code === 'PACKAGE_PIN_SELF_HOST' || code === 'PACKAGE_PIN_ABSENT') return null;
@@ -24,6 +24,19 @@ export function packageUnresolvedNextAction(ctx) {
24
24
  return null;
25
25
  }
26
26
 
27
+ /**
28
+ * PACKAGE_PIN_ABSENT leads when a generated gate file (hook, MCP entry, CI workflow, script)
29
+ * runs Ark through the local runner: without the pin that command 404s or runs a stale global.
30
+ */
31
+ export function packagePinAbsentPrimaryAction(ctx) {
32
+ if (ctx?.selfHost === true) return null;
33
+ const truth = ctx?.packageVersionTruth;
34
+ if (truth?.code !== 'PACKAGE_PIN_ABSENT') return null;
35
+ const files = Array.isArray(truth.dependentGateFiles) ? truth.dependentGateFiles : [];
36
+ if (files.length === 0) return null;
37
+ return `Add arkgate to package.json devDependencies and install — ${files.join(', ')} run Ark through the local runner, which does not resolve without the pin (PACKAGE_PIN_ABSENT)`;
38
+ }
39
+
27
40
  /** Compact doctor JSON #1 — same rank as collectDoctorNextActions when package is missing. */
28
41
  export function preferredDoctorPrimaryNextAction({
29
42
  adopted,
@@ -35,6 +48,7 @@ export function preferredDoctorPrimaryNextAction({
35
48
  } = {}) {
36
49
  return (
37
50
  packageUnresolvedNextAction({ packageInstalled, selfHost, packageVersionTruth }) ||
51
+ packagePinAbsentPrimaryAction({ selfHost, packageVersionTruth }) ||
38
52
  (adopted === 'not-adopted' ? NOT_ADOPTED_NEXT_ACTION : postGreenPath?.action ?? dualTruthNext ?? null)
39
53
  );
40
54
  }
@@ -87,7 +101,7 @@ function domainSampleIsGlobLeak(row, cov) {
87
101
  }
88
102
 
89
103
  /** Overlapping roots from matching Domain wildcards, else sample path prefixes. */
90
- export function overlappingRootsFromCoverage(cov) {
104
+ function overlappingRootsFromCoverage(cov) {
91
105
  const samples = Array.isArray(cov?.dualMembership?.samples) ? cov.dualMembership.samples : [];
92
106
  const domainPatterns = layerPatterns(cov, 'DomainModel');
93
107
  const fromPatterns = [];
@@ -149,7 +163,8 @@ export function collectDoctorNextActions(ctx) {
149
163
  if (notAdopted || ctx.adopted === ADOPTED_NOT || ctx.adopted == null) {
150
164
  actions.push(ctx.notAdoptedNextAction || NOT_ADOPTED_NEXT_ACTION);
151
165
  }
152
- const resolvePkg = packageUnresolvedNextAction(ctx);
166
+ const pinAbsentPrimary = packagePinAbsentPrimaryAction(ctx);
167
+ const resolvePkg = packageUnresolvedNextAction(ctx) || pinAbsentPrimary;
153
168
  if (resolvePkg) actions.push(resolvePkg);
154
169
  const nudge = ctx.stewardNudge;
155
170
  if (
@@ -220,7 +235,7 @@ export function collectDoctorNextActions(ctx) {
220
235
  ctx.dualTruthNext ||
221
236
  'bump package.json arkgate pin to match this CLI (or install without --no-install)'
222
237
  );
223
- } else if (ctx.packageVersionTruth?.code === 'PACKAGE_PIN_ABSENT') {
238
+ } else if (ctx.packageVersionTruth?.code === 'PACKAGE_PIN_ABSENT' && !pinAbsentPrimary) {
224
239
  actions.push(
225
240
  ctx.dualTruthNext ||
226
241
  'Add arkgate to package.json and install so CI/npx resolve this CLI (PACKAGE_PIN_ABSENT)'
@@ -10,8 +10,8 @@ import {
10
10
  shouldShowNewHereNudge,
11
11
  } from '../ark-shared.mjs';
12
12
  import * as arkShared from '../ark-shared.mjs';
13
- import { summarizeRulesUnderContract } from './rules-under-contract.mjs';
14
13
  import { describePackageVersionDualTruth } from './field-install.mjs';
14
+ import { withPinDependentGateFiles } from './pin-dependent-gates.mjs';
15
15
  import { detectAgentHomeGaps } from './agent-homes.mjs';
16
16
  import { collectDoctorNextActions, preferredDoctorPrimaryNextAction } from './doctor-next-actions.mjs';
17
17
  import { printDoctorCompactHuman, printDoctorDetailsHuman } from './doctor-human.mjs';
@@ -21,10 +21,9 @@ import { collectStatesTransitionsResidual } from './states-transitions-presence.
21
21
  import { collectStatusTransitionCatalogResidual } from './status-transition-catalog.mjs';
22
22
  import { collectNoDomainFrontendResidual } from './no-domain-frontend.mjs';
23
23
  import { collectInvariantCoverageResiduals } from './invariant-tests-path.mjs';
24
- import { anyChildWallAdvisory, anySliceAlias } from '../ark-layer-match.mjs';
24
+ import { anyChildWallAdvisory, applyAdvisorySiblingRatchet, sliceAliasOwesMove } from '../ark-layer-match.mjs';
25
25
  export { printAdrPresenceHint };
26
26
  export { printDoctorCompactHuman, printDoctorDetailsHuman };
27
- export { summarizeRulesUnderContract };
28
27
  /** Optional S3 dual-match classifier when ark-shared exports it (soft dep for S5 landing). */
29
28
  const matchingLayersForRelativePath =
30
29
  typeof arkShared.matchingLayersForRelativePath === 'function'
@@ -533,7 +532,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
533
532
  const skillGaps = detectSkillGaps(root);
534
533
  const agentHomeGaps = detectAgentHomeGaps(root);
535
534
  // Dual-truth: CLI version vs package.json pin (field residual after upgrade --no-install).
536
- const packageVersionTruth = describePackageVersionDualTruth(root);
535
+ const packageVersionTruth = withPinDependentGateFiles(root, describePackageVersionDualTruth(root));
537
536
  const staleRunners = staleRunnerGateFiles(root);
538
537
  const adoption = collectAdoptionGaps(root, config, cov);
539
538
  // Prefer writePath from adoption (same detector); recompute only if missing (tests/stubs).
@@ -557,12 +556,9 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
557
556
  ? [...baseline.keys].filter((key) => !currentKeys.has(key)).length
558
557
  : 0;
559
558
  const activeCount = violations.length - suppressed;
560
- // productHonesty: blocking = failsStrict !== false only (type-only placement debt excluded).
561
- const blockingActive = violations.filter((v, index) => {
562
- if (v.failsStrict === false) return false;
563
- if (!baseline.exists) return true;
564
- return !baseline.keys.has(occurrenceKeys[index]);
565
- }).length;
559
+ // productHonesty: blocking = failsStrict !== false only, after the same sibling ratchet as --baseline.
560
+ const judged = baseline.exists ? applyAdvisorySiblingRatchet(violations, occurrenceKeys, baseline.keys, { rules, layers: config?.layers }) : violations;
561
+ const blockingActive = judged.filter((v, i) => v.failsStrict !== false && !(baseline.exists && baseline.keys.has(occurrenceKeys[i]))).length;
566
562
  const emptyScopeEarly = cov.emptyScope === true || cov.governed.totalFiles === 0;
567
563
  const presentationRowEarly = cov.layers.find((r) => r.name === 'PresentationAdapters');
568
564
  const totalFilesEarly = cov.governed.totalFiles || 0;
@@ -612,7 +608,8 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
612
608
  options.ts,
613
609
  options.parseHealth,
614
610
  options.facts ?? options.architectureFacts,
615
- activeViolations
611
+ activeViolations,
612
+ { details: options.all === true, changed: options.changed === true }
616
613
  );
617
614
  // After physical cohesion so a reshape pilot is a candidate (#309).
618
615
  const pilotLoop = summarizePilotLoop(
@@ -694,7 +691,7 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
694
691
  github: githubForBoundary,
695
692
  adoptionStance: stanceFile,
696
693
  stewardNudge: doctorAdvisories.stewardNudge,
697
- physicalCohesionResidual: physicalCohesionResidualRemains(doctorAdvisories.physicalCohesion), childWallAdvisory: anyChildWallAdvisory(config?.rules), sliceAliasDebt: anySliceAlias(config?.rules),
694
+ physicalCohesionResidual: physicalCohesionResidualRemains(doctorAdvisories.physicalCohesion), childWallAdvisory: anyChildWallAdvisory(config?.rules), sliceAliasDebt: sliceAliasOwesMove(config?.rules),
698
695
  });
699
696
 
700
697
  // Improvement compass: projection only — never feeds ok/valid/goal.met.
@@ -976,4 +973,3 @@ export function runDoctor(root, config, files, rules, violations, asJson, option
976
973
  printDoctorCompactHuman(humanView);
977
974
  if (options.all) printDoctorDetailsHuman(humanView);
978
975
  }
979
-