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
@@ -61,40 +61,43 @@ hardening guide remains repository-hosted rather than duplicated in the gate tar
61
61
  | **Ambient state (pure layers)** | `ark-check --doctor --json` → `doctor.ambientState`; optional `.ark/ambient-state-acks.json` | Additive, **advisory only and opt-in**: only layers declared `pure: true` are scanned; the MVP shape is module-scope `let`/`var`. Findings carry `file`/`line`/`name`/`kind` (sorted, capped with honest `truncated` count). Acknowledgments live in the bounded sidecar (`{ acks: [{ file, name, reason }] }`, ≤64 KB / ≤200 entries); `acknowledged` counts applied acks; malformed file suppresses nothing. When TypeScript is unavailable the sensor reports `available: false` instead of guessing. **No strict mode exists** — A5: strictness requires a completed corpus and an explicit later decision. |
62
62
  | **Parse health + analysis completeness** | `ark-check --doctor --json` → `doctor.parseHealth` + `doctor.completeness`; check JSON → `completeness`; report section `data-advisory="parseHealth"` | The resolved candidate facts contribute only `parseDiagnosticCount` per governed file (no raw diagnostics, second parser pass, or `tsc`). Z04's correctness path ignores legacy v9 caches and parses the complete candidate on every invocation; Z07 owns any future identity-keyed warm snapshot. Doctor remains diagnostic: parse health adds no architecture violation and does not change `designFitness` or `patternBets`. Verdict surfaces consume the evidence fail-closed: affected governed files mean `partial`, plan `goal.met: false`, normal JSON `valid:false`/`ok:false`, and strict merge exit `1`; the non-strict process exit remains advisory for compatibility. No usable host means `unavailable`, plan false, and CLI exit `2`. JSON reports `scannedFiles`, `affectedFiles`, `diagnosticCount`, deterministic top-12 `{ file, diagnosticCount }` entries, and honest `truncated`/`overflow`; missing/unsafe evidence never becomes a clean claim. |
63
63
  | **Physical cohesion + reshape pilot** | `ark-check --doctor --json` → `doctor.physicalCohesion` (`reshapePilot`, `reshapeDecisions`); optional `.ark/reshape-decisions.json`; report section `data-advisory="physicalCohesion"` | Additive, **advisory only** — `notAScore`; never feeds the verdict, `designFitness`, or `patternBets`. Signal is **concentration, not volume**: concept clusters per anchor directory (deterministic path/name tokenization; framework filenames like `route.ts` take the topmost meaningful path segment — ADR 0010 D2). Fixed corpus-calibrated thresholds (`maxCluster ≥ 40` OR ≥2 anchors ≥ 20, ADR 0010 D3); findings ranked and capped (top 5, honest `truncated`). Anchors under `app/`/`pages/` are `fixedByConvention` and never move (D7). `reshapePilot` is **proposed, never applied** (`neverMechanicalSafe`): one Q04-style pilot card at a time targeting the smallest convention-free anchor, with `moveSample`/`movesTotal`, `successSignal`, `killSwitch`, `doNot[]`; each `to` is `concept/` under the source anchor and is kept only when layer and slice id are unchanged — if every candidate fails, `nextPilot` is null; real moves run only through the write gate + atomic preflight via `/ark-loop`; merges are `/ark-architect` judgment cards, never a codemod (D6). **Y01 verdict memory:** bounded sidecar `{ schemaVersion?: "1", decisions: [{ concept, anchors, verdict: "accepted"|"deferred"|"rejected", reason, reviewBy? }] }` (≤64 KiB / ≤200 unique targets). Identity is concept + complete sorted anchor set, never counts/change-map evidence. Current rejected/deferred records suppress pilot pressure only; accepted keeps the existing path. Expired/malformed/stale/invalid records suppress nothing; lifecycle and decisions render in doctor/report. Explicit only — golden-pattern prose never infers a verdict. |
64
+ | **Files nothing imports** | `ark-check --doctor --json` → `doctor.orphanModules` (nested `unusedExports`); human status details (`--doctor --all`) section **Files nothing imports (not a score)** and at most one dim count line in compact status; HTML report `data-advisory="orphanModules"`; optional `.ark/entry-points.json` | Additive schema `1.0`, **advisory only** — always `notAScore: true`; never flips `valid`, strict-merge, `goal.met` or a compass lens; never runs in the write hook, MCP write tools, ESLint, `--changed` or `--strict-merge`. Projects the resolved import edges the check already built (ADR 0037): a governed file with zero incoming project edges (type-only imports and `export … from` count) that no entry source covers. Closed entry sources, each labelled in `entryEvidence.bySource`: `package-json` (`main`/`module`/`types`/`browser`/`exports`/`bin` per workspace; built paths map back through a bundler entry map in a `*.config.*` file, tsconfig `outDir`/`rootDir`, then `dist\|build\|lib\|out` → `src`; an unmapped path forces `partial`), `package-scripts` (package scripts and CI workflow run steps), `framework` (Next route files, `pages/**`, `middleware`/`proxy`/`instrumentation`; Vite `index.html`; Nest `main`; Vercel root `api/**`; Storybook stories), `ark-config` (arkRun / arkOrder roots, `stopAt`), `config-file` (config and setup files, and any source file a config file names), `ambient` (`declare global` / `declare module`), `sidecar`. Files only tests import are counted in `testOnly`; files only non-governed source or a generated copy uses count as `outsideOnly`. `status` is `complete` \| `partial` \| `deferred` \| `unavailable`; under `partial` each item carries `certainty` `no-importer` \| `maybe-dynamic` \| `maybe-unresolved` and `honesty[]` names what could not be followed. Lists capped at 20 with honest `truncated`; with no framework recognised and more than a quarter of governed files listed, the list is cut to 5 and the headline says entry points were not recognised. Outside pass bounded to 5000 files / 256 KB each. `unusedExports` (`UNUSED_EXPORT`) is `deferred` in compact status and filled only by `--doctor --all` and `--report`. Sidecar `{ "schemaVersion": "1", "entryPoints": [{ "glob", "reason", "reviewBy?" }] }` (≤64 KiB / ≤200 entries); an expired `reviewBy` stops applying and annotates the file; a malformed file suppresses nothing and a line says so. Not a config key and not in `policyHash`. Domain pure + CLI gen mirror (`orphanModules.ts` / `bin/lib/orphan-modules.mjs`); not a root package export. |
65
+ | **Copies across a wall** | `ark-check --doctor --all --json` → `doctor.crossWallDuplication`; human status details (`--doctor --all`) section **Copies across a wall (not a score)**; HTML report `data-advisory="crossWallDuplication"` | Additive schema `1.0`, **advisory only** — always `notAScore: true`; never flips `valid`, strict-merge, `goal.met` or a compass lens; never runs in the write hook, MCP, ESLint, compact status, `--changed` or `--strict-merge`. Without `--all` (and under `--changed`) the key says `status: "not-run"` with `next: "arkgate-check --doctor --all"`. Token fingerprints taken at status time (ADR 0038) from the TypeScript already loaded — one `createSourceFile` per governed file, a JSX-safe node walk (identifiers and literals collapse, imports and `export … from` skipped); they never enter facts, `factsHash` or the verdict. Tests, `.d.ts`, default generated globs, files whose first five lines say `@generated` / `GENERATED FILE` / `DO NOT EDIT` / `generated from|by`, and files over 256 KB are never read for it. A family is listed only when a pair crosses a boundary the gate's own classifier (`findDeniedEdgeDecision`, both directions) draws: `crossing` `cross-slice` \| `cross-parent` \| `cross-sibling` \| `cross-layer-walled` (`CROSS_WALL_DUPLICATE`) or `cross-layer` (`CROSS_LAYER_DUPLICATE`). Same-slice pairs (`totals.pairsSameSliceUnexamined`), unclassifiable (`fail-closed`) pairs (`pairsUnclassifiable`) and files outside every layer (`pairsUnplaced`) are counted, never listed. Each family carries `members[]` (path, layer, `startLine`/`endLine`, tokens), `pairs[]`, `names` (`same` of `total` identifiers matching), a plain `destination` (`shared-root` \| `universe-common` \| `lower-layer` \| `shared-layer` \| `either-layer` \| `ask-place`; never applied) and `line`. Fixed constants in `method` (20-token grams, 31-gram winnowing window, ≥50 tokens, ≥5 lines, ≥60% names, bucket cap 16); not configurable. Caps: `graphScanLimit(n)` files, 1 000 000 fingerprints, 50 000 seed pairs, 1000 compared file pairs; any cap → `status: "partial"` with `honesty[]` counts. Families capped at 8 with honest `truncated`. No config key. Domain pure + CLI gen mirror (`cloneDetection.ts` / `bin/lib/clone-detection.mjs`); not a root package export. |
64
66
  | **Capability walls, every adapter** | CLI scan, pure IR engine, atomic preflight, `ark-mcp --hook` / MCP gate (`capabilityWalls`), ESLint `ark/no-denied-capabilities` | The same opt-in deny set enforces across every surface: hook/MCP and CLI cover ambient + import evidence (symbol-aware); the pure engine, preflight, and ESLint cover the import dimension (documented envelope). Dual depth everywhere: plain port hint (`FIX_HINTS`/`suggestion`) + stable JSON (`ruleId`, `capability`, `fixClass: inject-port`, deterministic `nextAction`). |
65
67
  | **Hook-path budgets (maintainer)** | Repository-only evidence: `npm run bench:hook-path`; `eval/performance/hook-budgets.v1.json`; CI job "Hook-path end-to-end budgets" | Measures the COMPLETE pre-tool paths as fresh child processes (hook cold/warm, doctor cold) at 1k/10k. D5 method locked: ceilings are Linux-baseline p95 + fixed headroom, set once per cycle, never ratcheted; scenarios without a recorded baseline stay in RECORDING mode and cannot fail CI. |
66
68
  | **Governance weight** | `ark-check --doctor --json` → `doctor.contractHealth.governanceWeight` | Additive, **advisory only** — raw facts (`declaredLayers`, `populatedLayers`, `governedFiles`, `rules`, `deniedEdges`, `allowedEdges`, `filesPerLayer`, `rulesPerLayer`) plus a fixed comparative band `weight: heavy | typical | light | unknown` and its fixed `note`. Fixed deterministic thresholds: **heavy** = fewer than 25 governed files per declared layer AND (6+ layers OR 4+ well-formed rules per layer); **light** = at most 2 layers over 150+ governed files; **unknown** = no layers or no governed files; everything else is **typical** (banding uses raw ratios; the reported ratios are rounded for display). `notAScore: true` is explicit: never a composite score, ranking, or gate input; the heavy note asks to justify NEW layers/rules and never suggests deleting working ones. Human doctor prints a line only for `heavy`/`light`. |
67
69
  | **Report parity and snapshot evidence (4.2)** | `ark-check --report` → advisory sections (`data-advisory="contractHealth\|ambientState\|parseHealth\|arkRun"`, nested `governanceWeight`) + layer wall badges; `.ark/reports/*.json` | The report is a rendering of doctor truth. **Standing rule:** every doctor advisory ships with its report section — enforced by the `reportParity` guard, which enumerates the doctor's advisory keys and fails on any missing section. Snapshots add best-effort Git `HEAD`/branch/dirty provenance without a shell; unavailable Git is explicit. Evolution renders the Ark score delta only when both snapshots name the same ArkGate version, while retaining raw facts across versions. Thin `arkRun` on `latest.json` is `notAScore` residual honesty for `ark status`. |
68
70
  | **MCP project identity (4.2)** | `ark_identity`; `arkgate/schema/project-identity` or `arkgate/schema/ark.project-identity.schema.json`; root API constants/helpers/types | Schema `1.0`. `projectId` hashes canonical root + config path and stays stable across contract edits/restarts; runtime id/start time are separate. Every project-bound tool result and error carries `projectIdentity`, `binding` (`matched` / `unverified` / `mismatch`), and `authoritative`. Canonical out-of-root config/file evidence fails before project data. |
69
- | **MCP tools and compatibility resource** | `arkgate-mcp`; `ark_manifest`; `ark_status`; `ark://manifest` | Tool names and primary argument shapes are stable within a major. Every tool accepts additive `project.expectedRoot` / optional `expectedProjectId`. The initial handshake requires the exact project root; a contained descendant is authoritative only together with the matching project id. Legacy tool calls remain callable but `unverified` and non-authoritative. `ark_manifest` is the authoritative contract surface after binding. **`ark_status`** returns the status manifest envelope (parity with `ark status --json`). Standard `resources/read` cannot portably carry the expectation, so `ark://manifest` remains compatibility-only and always unverified/non-authoritative. The server never retargets from input. |
70
- | **`ark.config.json`** | Layer globs, optional `layers[].description`, optional `layers[].trustBoundary`, optional `layers[].owners`, optional `requireLayerOwners`, rules, include/exclude, forbiddenGlobals, intent prefixes, `peerIsolation`, `dynamicImportAllowlist`, `safety` thresholds; optional **`coverage`** controls (`testGlobs`, `maxFiles`); optional **`arkRules`** map (schema `1.1+`); optional **`arkRun`** extra (schema `1.2+`); optional **`arkOrder`** extra (schema `1.3+`) | Versioned by `schemaVersion`; unknown fields fail closed and migrations preserve the previous supported major. Absence of `coverage`, `arkRules`, `arkRun`, or `arkOrder` is byte-for-byte silent on Layers / ArkRules verdicts. Enforced extra teeth share the CLI / MCP / hook / preflight / CI verdict and arm only when the layer plane is classified (same ArkRules floor). |
71
+ | **MCP tools and compatibility resource** | `arkgate-mcp`; `ark_manifest`; `ark_status`; `ark://manifest` | Tool names and primary argument shapes are stable within a major. Every tool accepts additive `project.expectedRoot` / optional `expectedProjectId`. The initial handshake requires the exact project root; a contained descendant is authoritative only together with the matching project id. Legacy tool calls remain callable but `unverified` and non-authoritative. `ark_manifest` is the authoritative contract surface after binding. **`ark_status`** returns the status manifest envelope (parity with `ark status --json`). Standard `resources/read` cannot portably carry the expectation, so `ark://manifest` remains compatibility-only and always unverified/non-authoritative. The server never retargets from input. Every published `outputSchema` has top-level `type: "object"` and the returned `structuredContent` validates against it (MCP SDK clients check both). A contract edit after startup makes project tools return `CONTRACT_STALE` until restart. `arkgate-mcp --help` / `--version` print and exit. |
72
+ | **`ark.config.json`** | Layer globs, optional `layers[].description`, optional `layers[].trustBoundary`, optional `layers[].owners`, optional `requireLayerOwners`, rules, include/exclude, forbiddenGlobals, intent prefixes, `peerIsolation`, `dynamicImportAllowlist`, `safety` thresholds; optional **`coverage`** controls (`testGlobs`, `maxFiles`); optional **`arkRules`** map of one path or a list of paths (schema `1.1+`), plus optional `childSlices.arkRulesFile` and `sliceAliases.pinned`; optional **`arkRun`** extra (schema `1.2+`); optional **`arkOrder`** extra (schema `1.3+`) | Versioned by `schemaVersion`; unknown fields fail closed and migrations preserve the previous supported major. Absence of `coverage`, `arkRules`, `arkRun`, or `arkOrder` is byte-for-byte silent on Layers / ArkRules verdicts. Enforced extra teeth share the CLI / MCP / hook / preflight / CI verdict and arm only when the layer plane is classified (same ArkRules floor). |
71
73
  | **Layer caption (`layers[].description`, 4.8.7)** | Optional string on each layer. Projected onto `ark_place` / prepare-write / MCP place JSON, doctor JSON + human, coverage JSON, and the HTML Purpose column when present. `/ark-adopt` writes it from the product map or glossary; `/ark-place` prints it next to layer name + globs. | Existing optional field — **no `schemaVersion` bump**, no new key, no 14th skill. Copy is **app context** (a product sentence such as `Purchase requests — from asked to received.`), not architecture jargon. Stripped from `policyHash` like `stewards`; caption-only edits are neutral. Absence is silent: never a residual, never a score, never `--strict-config` fail, never flips `valid`. Compact starters may omit. |
72
74
  | **Layer trust (`layers[].trustBoundary`)** | Optional closed tag on each layer: `public` \| `auth` \| `admin` \| `internal`. Projected onto `ark_place` / prepare-write / MCP place JSON, doctor JSON + human, coverage JSON, and the HTML report when present. `/ark-adopt` writes it when the product map names that door; `/ark-place` prints `trust: public`. | Additive optional field — **no `schemaVersion` bump**, no new skill. Stripped from `policyHash` like `stewards` and `layers[].description`; tag-only edits are neutral. Absence is silent: never a residual, never a score, never `--strict-config` fail, never flips `valid`. Invalid values fail the schema. Compact starters may omit. Not host/CI TLS. Not an import-rule matrix. |
73
75
  | **Layer owners (`layers[].owners` + `requireLayerOwners`)** | Optional GitHub handles or emails on each layer. Projected onto place / prepare-write / MCP / doctor / coverage / HTML when present. `requireLayerOwners: true` asks for owners on every live (non-reserved) layer: doctor names the first missing house, `--strict-config` refuses, and the write gate denies a write into that house. | Additive optional fields — **no `schemaVersion` bump**, no new skill. Owners are stripped from `policyHash` like captions; owner-only edits are neutral. The require flag stays in the hash. Absence of owners is silent unless the require flag is on. Invalid identity (display name) fails the schema. Compact starters may omit. Not import-rule teeth. No `/ark-owners`. |
74
- | **ArkRules inventory / under-contract (4.0; layer context 4.2)** | `ark-check --rules-inventory [--json]`; doctor `rulesUnderContract`; MCP `ark_rules_inventory` | Additive. Honest counts (inventoried / under-contract / frozen) — **never a score**. When configured layer evidence exists it overrides filename role guesses: a Domain file named `handler` is not a controller candidate. Test/fixture/seed/migration/exclusion surfaces plus narrow development-identity, PostgreSQL OID, and technical I/O constants are silent. Without layer evidence, backward-compatible path/content heuristics remain. Structure/invariant diagnostics use adapter `1.4` provenance. |
76
+ | **ArkRules inventory / under-contract (4.0; layer context 4.2)** | `ark-check --rules-inventory [--json]`; doctor `rulesUnderContract` + `rulesMigration`; MCP `ark_rules_inventory` | Additive. Honest counts (inventoried / under-contract / frozen) — **never a score**. Structure candidates count as under contract by declared (layer, sensor); frozen counts `ARKRULE_*` / `INVARIANT_*` baseline keys. CLI and MCP payloads share one builder (same keys, incl. `coverageEvidence`). When configured layer evidence exists it overrides filename role guesses: a Domain file named `handler` is not a controller candidate. Test/fixture/seed/migration/exclusion surfaces plus narrow development-identity, PostgreSQL OID, and technical I/O constants are silent. Without layer evidence, backward-compatible path/content heuristics remain. Structure/invariant diagnostics use adapter `1.4` provenance. |
75
77
  | **`arkgate/schema/project-identity`** or **`arkgate/schema/ark.project-identity.schema.json`** | MCP canonical project, contract, runtime, expectation, and binding envelope | Schema `1.0`. Initial `expectedRoot` must be the exact project root. A contained descendant can match only when `expectedProjectId` is also present and correct; id-only matching stays non-authoritative. Mismatch codes are `PROJECT_ROOT_MISMATCH`, `PROJECT_ID_MISMATCH`, and `INVALID_PROJECT_EXPECTATION`. |
76
78
  | **Package pin dual-truth (4.0)** | doctor JSON `packageVersionTruth`; upgrade JSON/human note when pin behind CLI | Additive, advisory. Surfaces after `upgrade --no-install` when managed CLI is ahead of package.json. |
77
79
  | **Managed upgrade self-service honesty (4.5 / DF05)** | `ark upgrade [--json]` → `selfService` (+ human “Self-service honesty” lines) | Additive, advisory. Answers without a maintainer: write-path activation labels per selected host (`hard`\|`advisory`\|`unavailable`) and customized content-identity preserve (`customizedPaths` / `customizedContentPreserved`). Soft hosts never hard; upgrade never invents `hardWriteActive` from disk alone. Always `notAScore: true`. Not a gate input; not part of `planDigest`. |
78
80
  | **Product honesty readiness split (4.1.1)** | doctor JSON `productHonesty` | Additive. `unfinished` / `headline` / `primaryNextAction` / `reasonIds` remain; EH adds `contractReadiness` (`ready`\|`partial`\|`not-ready`), `localWriteBoundary` (`advisory`\|`hard`\|`unverified`\|`unknown`), `architectureReasonIds`, `environmentResidualIds` / `environmentResiduals`. Soft-write hosts stay in evidence without alone forcing global **Not finished**. `notAScore: true` always. A physical-cohesion residual that reshape decisions have not suppressed adds `physical-cohesion-residual`, which clears `elegant` and `finished` and sets the headline to residual remaining; the gate verdict, `designFitness`, `designWeak`, and `patternBets` stay unchanged. When that residual still proposes `reshapePilot.nextPilot`, `pilotLoop` is active with that one extraction card. |
79
81
  | **Policy transition analysis (3.1.0)** | `analyzePolicyDelta(...)`; MCP `ark_policy_delta`; CLI `--policy-base` / `--policy-base-ref` / `--policy-ack`; check JSON `policyDelta` | Additive schema `1.0`. Classifications and finding ids are deterministic. Weakening/judgment requires an acknowledgement bound to both policy hashes and the exact blocking finding set. A new layer or new allow edge is `judgment-required`. On the CLI/MCP policy-ack plane, `adrPath` must name a short note under `docs/adr/` or `docs/decisions/` (`policyDelta.adrNote`). Domain hash match stays I/O-free. |
80
- | **Team parliament (law vs feature)** | Optional `stewards` on `ark.config.json` (GitHub handle or email); CLI `--local` / `--changed` / `--against` / `--base` / `--contract-diff` / `--contract-session` / `--persona` / `--author`; check JSON `teamParliament`; `ark status --vs`; write-gate mixed-batch deny | Additive. Law files must not mix with product source. Loosen / baseline-grow are steward-only when `stewards` is set. `--against` ratchets vs the base-ref baseline. `--changed` scans touched sources plus their import closure. `--local` (or `ARK_CHECK_LOCAL=1`) is the same cheap path and stays per `--root`; it cannot combine with `--strict-merge`. Check JSON may add `local`, `scope: "changed"`, `analysisRoot`. `stewards`, `layers[].description`, `layers[].trustBoundary`, and `layers[].owners` are excluded from policy hash. `requireLayerOwners` stays in the hash. Identity is handle or email, not git `user.name`. No org plane. |
82
+ | **Team parliament (law vs feature)** | Optional `stewards` on `ark.config.json` (GitHub handle or email); CLI `--local` / `--changed` / `--against` / `--base` / `--contract-diff` / `--contract-session` / `--persona` / `--author`; check JSON `teamParliament`; `ark status --vs`; write-gate mixed-batch deny | Additive. Law files must not mix with product source. Loosen / baseline-grow are steward-only when `stewards` is set. `--against` ratchets vs the base-ref baseline. `--changed` scans touched sources plus their import closure. `--local` (or `ARK_CHECK_LOCAL=1`) is the same cheap path and stays per `--root`; it cannot combine with `--strict-merge` or `--update-baseline`. Changed paths are relative to `--root` (monorepo packages included). A missing base ref or failed git listing is exit 2 (`changed-needs-base` / `local-needs-base` / `contract-diff-needs-base` for `--contract-diff` and `--persona steward` / `changed-paths-unavailable`), never a pass. `--report` applies the committed baseline implicitly except under `--strict` / `--strict-merge` / `--contract-diff` / `--against`. Check JSON may add `local`, `scope: "changed"`, `analysisRoot`. `stewards`, `layers[].description`, `layers[].trustBoundary`, and `layers[].owners` are excluded from policy hash. `requireLayerOwners` stays in the hash. Identity is handle or email, not git `user.name`. No org plane. |
81
83
  | **Atomic change preflight (3.1.0)** | `preflightChange(...)`; CLI `ark preflight --changes <file> --json`; MCP `ark_prepare_change` | Additive schema `1.0`. One complete governed production-source `{path,content}` / `{path,delete:true}` batch; read-only; returns operation, content/tree/policy/compiler fingerprints and stable graph findings. MCP availability alone is advisory. |
82
84
  | **Architecture change map (3.1.0)** | `arkgate/schema/change-map` or `arkgate/schema/ark.change-map.schema.json`; CLI `ark preflight --change-map <file>`; MCP `ark_prepare_change.changeMap` | Optional strict schema `1.0`. Canonical planned paths + operations + resolved Ark layers + dependencies between planned files. Preflight returns `changeMapHash`; absence is normal and adds no project file. Structural intent only, never behavioral completion. |
83
85
  | **Structural convergence (3.1.0)** | `analyzeArchitectureConvergence(...)`; map-enabled `preflightChange(...)`; existing CLI/MCP preflight adapters | Additive `convergence` result with stable `satisfied`, `missing`, `contradictory`, and `unplanned` findings. Uses the supplied/current project tree as base and the explicit complete change set as candidate; no implicit Git or LLM input. `readOnly: true`; `behavioralCompletion: "not-evaluated"`. Structural mismatch makes preflight invalid. |
84
86
  | **Enforcement ladder + fixed journey (3.1.0)** | `doctor.writePath.enforcementLadder`; hook repair `enforcement`; `npm run eval:change-integrity` | Additive schema `1.0` separates supported/installed/active/bypassable state and evidence. Hard is operation-scoped only for a supported covered hook; MCP is advisory; required CI status stays unverified locally. Fixed no-context fixture proves CLI/MCP/hook/final parity, one casual denial, acceptance behavior, and strict Ark. |
85
87
  | **Enforcement state** | `doctor.writePath.enforcementState`; schema/type | Schema `1.1`: runtime observation, operation coverage, and operation-scoped `hard`. Only fresh covered active-host evidence permits `hard:true`; unverified assets and MCP remain non-hard. |
86
88
  | **Design delta** | `--strict-merge` / `--strict` (created paths when a Git base exists); `--fail-on-new-smells --base-ref <ref>` (full ratchet); hook/MCP; schema/types | Schema `1.0`: identities, touched paths, optional `enforcementScope`. Default merge blocks **created** `domain-logic-in-ui` files vs merge-base; missing base skips (does not exit 2). Full new+worsened on touched paths remains opt-in `--fail-on-new-smells` (missing base fails closed). Historical residual and worsened-in-existing-file stay green on `--strict-merge`. Global doctor smells stay advisory. |
87
- | **`arkgate/schema/analysis-result`** or **`arkgate/schema/ark.analysis-result.schema.json`** | Public CLI/MCP/hook diagnostic envelope (`schemaVersion`, `mode`, `valid`, `completeness`, `completenessReasons`, `diagnostics`, resolved identities) | Schema **`1.5`** adds optional stable finding refs on diagnostics: `findingRef` (`fnv1a-` + hex), `targetKey` (baseline-compatible freeze identity), `docsCodePath` (`docs/diagnostics.md#RULE_ID`). Factory-emitted diagnostics always include them; consumer-owned 1.0–1.4 values remain valid without them. `1.4` added optional `evidence.arkruleId` / `evidence.arkruleSource` for ArkRules; `1.3` distinguished `resolved-candidate-facts` from `lexical-compatibility`; partial/unavailable analysis is always non-green, and resolved complete/partial results require policy/resolver/facts/tree identities. `1.2` added completeness and remains accepted alongside consumer-owned 1.0/1.1 values. |
89
+ | **`arkgate/schema/analysis-result`** or **`arkgate/schema/ark.analysis-result.schema.json`** | Public CLI/MCP/hook diagnostic envelope (`schemaVersion`, `mode`, `valid`, `completeness`, `completenessReasons`, `diagnostics`, resolved identities) | Schema **`1.5`** adds optional stable finding refs on diagnostics: `findingRef` (`fnv1a-` + hex), `targetKey` (baseline-compatible freeze identity), `docsCodePath` (`docs/diagnostics.md#RULE_ID`). Factory-emitted diagnostics always include them; consumer-owned 1.0–1.4 values remain valid without them. Optional `evidence.reasonId` (`CROSS_PARENT_SLICE` / `CROSS_SIBLING_SLICE` / `CROSS_PARENT_VIA_SHARED`) with `evidence.universeFrom` / `universeTo` is additive within `1.5` and outside the baseline key. `1.4` added optional `evidence.arkruleId` / `evidence.arkruleSource` for ArkRules; `1.3` distinguished `resolved-candidate-facts` from `lexical-compatibility`; partial/unavailable analysis is always non-green, and resolved complete/partial results require policy/resolver/facts/tree identities. `1.2` added completeness and remains accepted alongside consumer-owned 1.0/1.1 values. |
88
90
  | **Stable finding refs (4.3)** | Root API `adapterFindingTargetKey` / `adapterFindingRefFromTargetKey` / `toAdapterDiagnostic` / `createAdapterResult`; CLI/MCP/repair envelopes via analysis-result diagnostics | Multi-turn re-address without fuzzy message match. `targetKey` **is** the baseline (occurrence) key so freeze identity is never orphaned; `findingRef` is a compact FNV-1a of that key. Line/message drift does not change the ref. Multi-turn fixture: `tests/fixtures/finding-refs/multi-turn-stability.json`. |
89
91
  | **Diagnostic code catalog** | Root API `DIAGNOSTIC_CATALOG` / `getDiagnosticCatalogEntry` / `diagnosticDocsPath`; docs [diagnostics.md](diagnostics.md) (`#RULE_ID` anchors) | Closed vocabulary of public `ruleId`s with why/fix anchors. Cataloguing only — no new rule semantics. Remediation parity is test-guarded. ArkRun `ARKRUN_*` codes (RN05) share dual-depth `nextAction` / `enthusiastHint`; declaration-list adds are mechanical-safe only when the call-site literal already exists. Docs ship in the npm tarball. |
90
92
  | **Status manifest** | CLI `ark status [--json] [--vs <ref>]`; MCP `ark_status`; `arkgate/schema/status-manifest`; root API `buildStatusManifest` / `ARK_STATUS_MANIFEST_SCHEMA` / `projectStatusImprovementCompass` | Schema `1.0`. One session/project snapshot: identity binding, honest write-path activation (`hard`\|`advisory`\|`unavailable`), last-check summary, rules residual counts, primary next action, **`improvementCompass`** with honesty **`mode`** `full`\|`subset`\|`unavailable` (residual ids only; always `notAScore: true`; optional `reasonCode`/`reason`/`factsSource`/`contractHash`), optional **`vsBase`** (pin / contract / baseline grow vs a git ref; advisory only), and additive **`arkRun`** (`notAScore`; `present` / `mode` / `extraMergeTeeth` / residual count — null residual is unknown, not green). **Not a score.** Residual never changes gate verdicts. Never prompts (`CI=1` forces JSON). Optional `--expected-root` / `--expected-project-id` (MCP `project`) for matched vs stale identity. |
91
93
  | **Agent contract projection** | CLI `ark agents-md [--write] [--check] [--stdout] [--json]`; install/upgrade AGENTS templates; root API `buildAgentProjectionBlock` / `mergeAgentProjectionDocument` | Schema `1.0` (projection markers). Version-stamped managed block (`arkgateVersion` + contract summary + diagnostic short list). **Non-authoritative** — not a gate input; enforcement is ark-check / hooks / CI. Content-identity merge preserves customized regions outside markers. Drift: `--check` vs package version. |
92
94
  | **Agent Skills packaging** | `templates/agent-skills/<name>/SKILL.md` (+ package README); root API `ARK_SKILL_NAMES` / `ARK_FIRST_CLASS_SKILL_NAMES` / `validateAgentSkillsPackage` / `validateSkillProductCapacity`; `npm run check:agent-skills` | Schema `1.0` (package contract). Closed catalog (`ARK_SKILL_NAMES`): first-class doors plus one-release stubs, 1:1 with flat `templates/skills/*.md`. First-class `/ark-order` (issue #216 / ADR 0036). `check:agent-skills` fails closed when a product plane or the north star drops out of skill bodies or living hubs. Add a name only with a live ROADMAP item. |
93
95
  | **`arkgate/schema/arkrules`** or **`arkgate/schema/ark.arkrules.schema.json`** | Per-layer structure sensors + invariant catalog (ADR 0012) | Schema `1.0`. Opt-in via root `arkRules` map (`ark.config` schema `1.1`). |
96
+ | **Invariant probe** | CLI `arkgate-check --probe-invariants[=<id>] [--write] [--json] [--runner vitest\|jest\|node]` → JSON `probeInvariants`; optional committed `.ark/invariant-probe.json`; **`arkgate/schema/invariant-probe`** or **`arkgate/schema/ark.invariant-probe.schema.json`**; coverage evidence `probe` (type `InvariantProbeSummary`); promotion blocker `probe-survived`; doctor `rulesUnderContract.probe` and `--rules-inventory` `coverageEvidence.probe` when the file exists; catalog `INVARIANT_PROBE_SURVIVED` | Schema `1.0`, always `notAScore: true`. **Owner-invoked only** ([ADR 0039](adr/0039-invariant-mutation-probe.md)): runs the project's own covering tests (vitest, jest or `node --test`) in a temporary copy; never in the write hook, MCP, ESLint, the GitHub Action or `--strict-merge` (flag pairings refused). Closed operator set `ip-ops@1`, at most 3 changes per invariant after a green baseline and two wiring checks; rows `killed` \| `survived` \| `not-reached` \| `inconclusive` \| `unprobeable`, each bound to SHA-256 hashes of the symbol file, the covering tests and the invariant (without `mode`). The artifact can **only subtract**: a fresh `survived` / `not-reached` row refuses promotion on `--promote` and the policy delta; stale, killed, inconclusive, unprobeable, malformed or absent changes nothing. Exit 0 / 1 survived or not reached / 2 could not run (`reasonCode`). Best-effort environment allowlist — not a sandbox. Not a mutation score. No config key. |
94
97
  | **`arkgate/schema/resolved-candidate-facts`** or **`arkgate/schema/ark.resolved-candidate-facts.schema.json`** | Versioned parity-capable input for `analyzeResolvedProject` / `preflightResolvedChange` | Schema `1.2` is additive: optional `classShapes` (1.1) plus ArkRun `arkRunKernelCalls` / `arkRunManagedNews` / `arkRunCompositionRootHits` / `arkRunDeclarations` (RN03–RN04) and ArkOrder `arkOrderPlaneCalls` / `arkOrderGenericUpdates` / `arkOrderRootHits` (OR05). `1.0`/`1.1` payloads remain loadable. Tooling owns filesystem/compiler resolution; Domain/Kernel validate and evaluate supplied facts without importing those effects. Facts name resolver/compiler inputs, governed files, dependency evidence, completeness reasons, candidate tree/facts hashes, and (when present) ArkRun/ArkOrder call-site evidence. Tier-1 sensors emit `ARKRUN_*` / `ARKORDER_*` diagnostics from those facts: advisory never flips `valid`; enforced blocks. Extra absence is silent. |
95
98
  | **Config JSON Schema** | `arkgate/schema` or `arkgate/schema/ark.config.schema.json` | Stable package resource subpaths for editor completion and contract tooling. |
96
99
  | **Agent skills** | `/ark-*` templates; install via `--install-agent-gates` (often `--skills-only` on top of compact) **or** Agent Skills ecosystem path | **Day zero** is the compact router from `ark start` / `start --apply` + doctor — not the full skill pack. Closed catalog (`ARK_SKILL_NAMES`): first-class doors (including `/ark-order`) plus one-release stubs. Guided expert path remains `/ark-autopilot` after pack install. **4.0:** first-class doors except experimental `/ark-runtime` / `/ark-order` integrate **layers + ArkRules** and must label residual `[Layer]` vs `[ArkRules]`. **4.2:** repo catalogs are content-idempotent; the optional shared Codex home catalog is monotonic across 4.2.0+ installers. Pre-4.2 writers are outside that protocol and must be upgraded first. A durable pending-catalog journal preserves the floor across an interrupted install and is cleared only by its owning same/newer recovery. **4.3:** Agent Skills–compatible layout at `templates/agent-skills/<name>/SKILL.md` (1:1 with flat `templates/skills/*.md`); install via `npx skills add ./node_modules/arkgate/templates/agent-skills` (or the GitHub tree). **#216:** first-class `/ark-order`; ACS05 freeze opened (ADR 0036). Domain `ARK_SKILL_NAMES` + `validateAgentSkillsPackage` + `validateSkillProductCapacity`; drift `npm run check:agent-skills` (layout **and** 100% product coverage). Skills never enforce. |
97
- | **ESLint subpath** | `arkgate/eslint` | Config-driven layer/import/purity rules plus ArkRun import/`new` envelope (`ark/no-arkrun-kernel-in-domain`, `ark/no-arkrun-direct-new`, `ark/no-arkrun-transport-bypass`) when `arkRun` is on, and ArkOrder envelope (`ark/no-arkorder-kernel-in-domain`, `ark/no-arkorder-generic-update`) when `arkOrder` is on; loads consumer `ark.config.json`. Absence of an extra is silent. Missing-root / missing-plane and undeclared-* stay CLI/MCP. |
100
+ | **ESLint subpath** | `arkgate/eslint` | Config-driven layer/import/purity rules plus ArkRun import/`new` envelope (`ark/no-arkrun-kernel-in-domain`, `ark/no-arkrun-direct-new`, `ark/no-arkrun-transport-bypass`) when `arkRun` is on, ArkOrder envelope (`ark/no-arkorder-kernel-in-domain`, `ark/no-arkorder-generic-update`) when `arkOrder` is on, and ArkRules structure sensors (`ark/arkrules-structure`) when `arkRules` is on; loads consumer `ark.config.json`. Non-blocking findings (ark-check warnings) report on the warn-level `ark/architecture-advisory` (when that rule is not enabled they fall back to the blocking rule id, tagged advisory). `require('arkgate/eslint')` and the ESM default are the same plugin object (`meta`, `rules`, `configs`). Absence of an extra is silent. Missing-root / missing-plane, undeclared-*, and ArkRules invariant coverage stay CLI/MCP. |
98
101
  | **GitHub Action** | `pedroknigge/arkgate` (see `action.yml`) | The `uses:` tag/SHA selects the checker source; `version` remains an optional exact npm compatibility override. |
99
102
  | **Package metadata** | `arkgate/package.json` | Stable resource subpath for tooling that needs the installed manifest. |
100
103
 
@@ -141,6 +144,8 @@ are grouped below.
141
144
  | Design delta contract | `ARK_DESIGN_DELTA_SCHEMA_VERSION` |
142
145
  | Analysis IR + resolved facts | `ANALYSIS_IR_SCHEMA_VERSION`, `RESOLVED_CANDIDATE_FACTS_SCHEMA_VERSION`, `RESOLVED_CANDIDATE_FACTS_SCHEMA`, `createResolvedCandidateFacts`, `loadResolvedCandidateFacts`, `resolvedFactsEvidenceRequirementsHash`, `deterministicHash`, `stableSerialize` |
143
146
  | Config contract | `ARK_CONFIG_SCHEMA`, `ARK_CONFIG_SCHEMA_VERSION`, `loadArkConfigContract`, `parseArkConfigJson` |
147
+ | ArkRules contract | `ARK_RULES_SCHEMA`, `ARK_RULES_SCHEMA_VERSION`, `ARK_RULE_SENSORS`, `ArkRulesValidationError`, `buildEffectiveArkRules`, `emptyEffectiveArkRules`, `loadArkRulesContract`, `parseArkRulesJson` (`parseArkRulesJson` / `loadArkRulesContract` throw `ArkRulesValidationError` with `.issues: ArkRulesIssue[]`) |
148
+ | Effective contract | `EffectiveContractError`, `effectiveContractPolicyPayload`, `resolveEffectiveContract` |
144
149
 
145
150
  The type-only root exports are also semver-supported:
146
151
 
@@ -178,6 +183,7 @@ The type-only root exports are also semver-supported:
178
183
  `AnalysisFile`, `AnalysisImportEdge`, `AnalysisCapabilityUse`, `AnalysisEvidence`,
179
184
  `AnalysisViolation`, `AnalysisIr`.
180
185
  - Config contract: `ArkConfig`, `ArkConfigLoadResult`.
186
+ - ArkRules contract: `ArkRulesFile`, `ArkRulesIssue`, `EffectiveArkRules`.
181
187
 
182
188
  Runtime-kernel factories, `CAPABILITY_IDS`, `collectCapabilityUses`, and Domain capability mapping
183
189
  helpers are deliberately absent from this root. Use `arkgate/runtime` for the
@@ -195,9 +201,9 @@ claims. Static architecture enforcement does not depend on them.
195
201
 
196
202
  | Surface | Import path | Notes |
197
203
  |---------|-------------|--------|
198
- | **ArkRun kernel** | **`arkgate/runtime`** | Public brand **ArkRun**. Same npm package `arkgate` (ADR 0031). Factory `createStrictArkKernel` (each call is an isolated instance; no process-wide `getKernel()` singleton). Root export does **not** include the factory. Optional extra `arkRun` on schema `1.2+`. Event bus, intents, policies, sagas, event buffer, projections, and strict helpers. Managed components declare `uses` / `reactsTo` / `raises` / `sends` on `register()`; `getDependencyInformationPackage()` is a JSON snapshot of ids, lifetime, and declarations and never includes factories, live instances, or input DTOs (ADR 0023). `requestGraph()` slices that snapshot into **process** or **technical** graphs with optional `nodeIds`, `degreesOfSeparation`, and include/exclude query; `formatArkRunGraphMermaid()` (also `graph.mermaid`) is a helper string, never a score. `send()` is the transport port (local / localBlocking / broker); missing broker falls back to in-process local delivery, `ephemeral` defaults true, and **no cloud SDKs ship** in the package (ADR 0024). Opt-in `startInspector()` / `startArkRunInspector()` binds **`127.0.0.1` only**, refuses `NODE_ENV=production`, lazy-loads HTTP, and serves JSON snapshots, SSE, `/graph` slices of the information package, plus queue monitors **`GET /outbox`** and **`GET /workflows`** (full counts + sanitized samples capped at 32; no full event payloads; missing ports are unavailable; no public / authless bind). Snapshot `hardening.durability` classifies the explicit outbox/audit/workflow store ports; default `InMemory*` stores stay visibly `memory`, never durable. Dual bins **`ark-dashboard`** / **`arkgate-dashboard`** poll those JSON facts (ANSI TUI in `bin/` only — presentation is not in the kernel); `ark dashboard` / `arkgate dashboard` dispatch to the same executable. **Shadow / replay / compare** (`shadowInformationPackage`, `compareInformationPackages`, `replayInformationPackages`) are in-memory helpers on that snapshot — not durable, not a second bus (ADR 0033). **Decision tape** `decisionTape` `{ xiHash, event, residual }` via `appendDecisionTape` (ADR 0034). Built-in stores are **InMemory reference only**. Branding ArkRun is not a production-durability claim. **`@arkgate/runtime` is deprecated** leftover 0.x (`experimental` dist-tag). |
199
- | **NestJS adapter** | **`arkgate/nestjs`** | Experimental optional peer `@nestjs/common` for the ArkRun kernel. Same npm package. `@arkgate/runtime/nestjs` is deprecated. |
200
- | **ArkOrder plane** | **`arkgate/order`** | Public brand **ArkOrder**. Same npm package `arkgate` (ADR 0030) — not `@arkgate/order`. Factory `createOrderPlane`. Valved verbs: `release` / `project` / `ingest` / `proposeRelease` / `apply` / `refreshSigma` / `restore`. No `update`. First freeze is `release()`; later ξ change is `apply` (`ARKORDER_UNVALVED_RELEASE`). `restore(release)` installs a previously frozen `Release` (hash remains identity; not durable). `hashOf` / `hashReleasePayload` verify a stored hash without a second freeze. Default Kernel clock is `Date.now()`. Optional `arkOrder.appliesTo` globs; `maxXiKeys` caps one `release()`, not `xiKeys.length`. Haken: few slow keys; ingest residual `absorb | escalate_up | hold` + closed `reasonCode`; empty blast fails closed. Capacity pack as data (`kind` / `sigmaKey` / `payloadKey` / `op`). Factory options (not config keys): `informationBudget.cannotObserve`, `sigmaMaxAgeMs`, `store` (`ReleaseStore` / `createMemoryReleaseStore`), `catalogDigest`. Thin travel: `ingestTravelAction` absorb→`send` / escalate_up human→`raises`. `IngestEscalate.target` includes `human`. Root `arkgate` export does **not** include the factory. Optional extra `arkOrder` on schema `1.3`. In-memory; not durable; does not close K01. Does not replace ArkRun. Runtime half (shadow/replay/compare + `decisionTape` / `appendDecisionTape`) is ArkRun (ADR 0033 / 0034). Billing gallery is git-only: [examples/arkorder-billing](https://github.com/pedroknigge/arkgate/tree/main/examples/arkorder-billing) (not in the npm tarball). Canonical: [ArkOrder](arkorder.md). |
204
+ | **ArkRun kernel** | **`arkgate/runtime`** | Public brand **ArkRun**. Same npm package `arkgate` (ADR 0031). Factory `createStrictArkKernel` (each call is an isolated instance; no process-wide `getKernel()` singleton). Root export does **not** include the factory. Optional extra `arkRun` on schema `1.2+`. Event bus, intents, policies, sagas, event buffer, projections, and strict helpers. Managed components declare `uses` / `reactsTo` / `raises` / `sends` on `register()`; `getDependencyInformationPackage()` is a JSON snapshot of ids, lifetime, and declarations and never includes factories, live instances, or input DTOs (ADR 0023). `requestGraph()` slices that snapshot into **process** or **technical** graphs with optional `nodeIds`, `degreesOfSeparation`, and include/exclude query; `formatArkRunGraphMermaid()` (also `graph.mermaid`) is a helper string, never a score. `send()` is the transport port (local / localBlocking / broker); missing broker falls back to in-process local delivery, `ephemeral` defaults true, and **no cloud SDKs ship** in the package (ADR 0024). Opt-in `startInspector()` / `startArkRunInspector()` binds **`127.0.0.1` only**, refuses `NODE_ENV=production`, lazy-loads HTTP, and serves JSON snapshots, SSE, `/graph` slices of the information package, plus queue monitors **`GET /outbox`** and **`GET /workflows`** (full counts + sanitized samples capped at 32; no full event payloads; missing ports are unavailable; no public / authless bind). Snapshot `hardening.durability` classifies the explicit outbox/audit/workflow store ports; default `InMemory*` stores stay visibly `memory`, never durable. Dual bins **`ark-dashboard`** / **`arkgate-dashboard`** poll those JSON facts (ANSI TUI in `bin/` only — presentation is not in the kernel); `ark dashboard` / `arkgate dashboard` dispatch to the same executable. **Shadow / replay / compare** (`shadowInformationPackage`, `compareInformationPackages`, `replayInformationPackages`) are in-memory helpers on that snapshot — not durable, not a second bus (ADR 0033). **Decision tape** `decisionTape` `{ xiHash, event, residual }` via `appendDecisionTape` (ADR 0034). Built-in stores are **InMemory reference only**; the default event buffer is capped at `maxHistorySize` and settled after local delivery, and broker handoff outcomes (including failures, as `event.handoffFailed`) are recorded. `createStrictArkKernelFromConfig` gives canonical layer names (what `ark init` writes) their built-in intent prefixes; a custom-named deny-rule layer without `intentPrefixes` is recorded as a `layer.observedFlowUnresolvable` audit record, and throws `ArkKernelConfigError` only when `enforceObservedLayerFlow: 'hard'` is passed explicitly. Branding ArkRun is not a production-durability claim. **`@arkgate/runtime` is deprecated** leftover 0.x (`experimental` dist-tag). |
205
+ | **NestJS adapter** | **`arkgate/nestjs`** | Experimental optional peer `@nestjs/common` for the ArkRun kernel. Same npm package. `arkgate/runtime` and `arkgate/nestjs` load **one shared kernel chunk**, so errors thrown by an `ArkModule` kernel pass `instanceof` against the classes exported from `arkgate/runtime`. `ArkModule.forRoot()` / `forRootAsync()` count as an ArkRun composition-root factory; a Domain-role import of `arkgate/nestjs` is `ARKRUN_KERNEL_IN_DOMAIN`. `@arkgate/runtime/nestjs` is deprecated. |
206
+ | **ArkOrder plane** | **`arkgate/order`** | Public brand **ArkOrder**. Same npm package `arkgate` (ADR 0030) — not `@arkgate/order`. Factory `createOrderPlane`. Valved verbs: `release` / `project` / `ingest` / `proposeRelease` / `apply` / `refreshSigma` / `restore`. No `update`. First freeze is `release()`; later ξ change is `apply` (`ARKORDER_UNVALVED_RELEASE`). `apply` rejects a proposal not computed against the current Release (`ARKORDER_STALE_PROPOSAL`). `restore(release)` reinstalls a validated `Release` (frozen or JSON-deserialized; hash remains identity; not durable) onto an empty plane or at the same ξ at the current or next version (no rollback, no jump; a freeze time after the plane clock fails closed); `ReleaseStore.load()` output is validated the same way (hashes are a consistency check, not a signature), and the plane advances only after `save()` returns. `hashOf` / `hashReleasePayload` verify a stored hash without a second freeze. Default Kernel clock is `Date.now()`. Optional `arkOrder.appliesTo` globs; `maxXiKeys` caps one `release()`, not `xiKeys.length`. Haken: few slow keys; ingest residual `absorb | escalate_up | hold` + closed `reasonCode`; empty blast fails closed. Capacity pack as data (`kind` / `sigmaKey` / `payloadKey` / `op`). Factory options (not config keys): `informationBudget.cannotObserve`, `sigmaMaxAgeMs`, `store` (`ReleaseStore` / `createMemoryReleaseStore`), `catalogDigest`. Thin travel: `ingestTravelAction` absorb→`send` / escalate_up human→`raises`. `IngestEscalate.target` includes `human`. Root `arkgate` export does **not** include the factory. Optional extra `arkOrder` on schema `1.3`. In-memory; not durable; does not close K01. Does not replace ArkRun. Runtime half (shadow/replay/compare + `decisionTape` / `appendDecisionTape`) is ArkRun (ADR 0033 / 0034). Billing gallery is git-only: [examples/arkorder-billing](https://github.com/pedroknigge/arkgate/tree/main/examples/arkorder-billing) (not in the npm tarball). Canonical: [ArkOrder](arkorder.md). |
201
207
 
202
208
  ---
203
209
 
@@ -212,6 +218,26 @@ import { createOrderPlane, hashOf } from 'arkgate/order';
212
218
 
213
219
  One install: `npm install arkgate`. `@arkgate/runtime` is deprecated.
214
220
 
221
+ **Types for ESM and CommonJS consumers.** Every code entry (`arkgate`, `arkgate/eslint`,
222
+ `arkgate/order`, `arkgate/runtime`, `arkgate/nestjs`) maps `import` to `index.js` +
223
+ `index.d.ts` and `require` to `index.cjs` + `index.d.cts`, and the package ships both
224
+ declaration sets. A CommonJS TypeScript project (`.cts`, or `module` / `moduleResolution`
225
+ `node16` without `"type": "module"`, the usual NestJS shape) type-checks `require` /
226
+ `import x = require(...)` against the CJS declarations. `typesVersions` maps the subpaths for
227
+ legacy `moduleResolution: node10`. Through 4.8.23 the tarball dropped `*.d.cts`, so those
228
+ consumers got TS1471 / TS1479 / TS2307 although runtime `require()` worked.
229
+
230
+ ### MCP server via npx and the MCP Registry
231
+
232
+ `npx arkgate@<version> <args>` always runs the bin named after the package, `arkgate`. So
233
+ `arkgate mcp [--root <dir>] [--config <path>] [--hook]` starts the same stdio MCP server as
234
+ `arkgate-mcp`, and forwards every flag to it. The MCP Registry descriptor (`server.json`) launches
235
+ `npx arkgate@<version> mcp --root .` with no `--config`, so it starts in a fresh project too
236
+ (the server reads `ark.config.json` when present; an explicit `--config` to a missing file exits
237
+ 1). `arkgate arkgate-mcp` and `arkgate ark-mcp` are accepted as well. Registry entries published
238
+ through 4.8.23 pin arkgate versions that have no `mcp` route, so they keep failing until the
239
+ descriptor is republished for a version that has it.
240
+
215
241
  ---
216
242
 
217
243
  ## Installing from git
@@ -80,7 +80,7 @@ On the seven lane openings — `docs/use.md`, `docs/develop.md`, `docs/README.md
80
80
  `README.md` is the first-contact module. Its first screen is four beats: what
81
81
  (**Contener · Guiar · Ordenar**), why (design the boundary before you implement;
82
82
  a green check is **needs a refactor** when leftover mess remains), one command
83
- (`npx arkgate@4.8.23 start`), and one honest red result (fail-open named; required
83
+ (`npx arkgate@4.8.25 start`), and one honest red result (fail-open named; required
84
84
  CI is the hard line). The four paragraphs, the not-that line, the host matrix,
85
85
  and the release index sit below `## Appendix`. The not-that line does not lead
86
86
  the README.
@@ -134,6 +134,9 @@ Three beats when a line teaches:
134
134
  | ArkRun is experimental. In-memory. Data is gone on restart. | The kernel is production-ready |
135
135
  | The agent PATCHed the billing plan as if it were a seat count. The write didn’t land. Next: `proposeRelease` then `apply`, not `update`. | Freeze ξ. Four verbs. Haken slaving. |
136
136
  | Status: one light, one next step. | Become an architect in 60 seconds |
137
+ | Nothing imports src/lib/legacy-pricing.ts, and no entry point covers it. Next: delete it through the write gate. | Dead code detected! Auto-removing unused files |
138
+ | This code is copied between features/billing and features/invoices. The wall stops the import, not the copy. Next: move it to shared/ with `/ark-place`. | 12.4% duplication! Refactor score: C |
139
+ | INV-REFUND-WINDOW: the tests still pass when the refund guard is negated. The test names the rule but does not pin it. Next: add a case that fails when the window is crossed. | Mutation score 67%! 2 of 3 mutants killed |
137
140
 
138
141
  **Brands, then the common word:** ArkGate, ArkRules, ArkRun, ArkOrder. Gloss once.
139
142
 
@@ -212,6 +215,9 @@ a product PR — that is an owner `--contract-session` (config change).
212
215
  - ArkRun as Postgres, an outbox, or Temporal
213
216
  - ArkOrder as durable storage, a second npm package, or a replacement for ArkRun
214
217
  - New skill *names* without a live queue item
218
+ - “Dead code” for a file nothing imports — say what was seen (nothing imports it), and say when a dynamic import may still load it
219
+ - A duplication percentage or score for copies — name the two places and the wall between them, and where the shared code could live
220
+ - A mutation score or percentage for the invariant probe — say which change went unnoticed, where, and which test to add
215
221
  - “the house stays up” / “training wheels” / “mimo” on first contact
216
222
 
217
223
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkgate",
3
- "version": "4.8.23",
3
+ "version": "4.8.25",
4
4
  "description": "When the agent writes a bad import, the write doesn’t land. The same check fails the pull request.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -8,29 +8,54 @@
8
8
  "types": "./dist/index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
- "types": "./dist/index.d.ts",
12
- "import": "./dist/index.js",
13
- "require": "./dist/index.cjs"
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
14
19
  },
15
20
  "./eslint": {
16
- "types": "./dist/eslint/index.d.ts",
17
- "import": "./dist/eslint/index.js",
18
- "require": "./dist/eslint/index.cjs"
21
+ "import": {
22
+ "types": "./dist/eslint/index.d.ts",
23
+ "default": "./dist/eslint/index.js"
24
+ },
25
+ "require": {
26
+ "types": "./dist/eslint/index.d.cts",
27
+ "default": "./dist/eslint/index.cjs"
28
+ }
19
29
  },
20
30
  "./order": {
21
- "types": "./dist/order/index.d.ts",
22
- "import": "./dist/order/index.js",
23
- "require": "./dist/order/index.cjs"
31
+ "import": {
32
+ "types": "./dist/order/index.d.ts",
33
+ "default": "./dist/order/index.js"
34
+ },
35
+ "require": {
36
+ "types": "./dist/order/index.d.cts",
37
+ "default": "./dist/order/index.cjs"
38
+ }
24
39
  },
25
40
  "./runtime": {
26
- "types": "./dist/runtime/index.d.ts",
27
- "import": "./dist/runtime/index.js",
28
- "require": "./dist/runtime/index.cjs"
41
+ "import": {
42
+ "types": "./dist/runtime/index.d.ts",
43
+ "default": "./dist/runtime/index.js"
44
+ },
45
+ "require": {
46
+ "types": "./dist/runtime/index.d.cts",
47
+ "default": "./dist/runtime/index.cjs"
48
+ }
29
49
  },
30
50
  "./nestjs": {
31
- "types": "./dist/nestjs/index.d.ts",
32
- "import": "./dist/nestjs/index.js",
33
- "require": "./dist/nestjs/index.cjs"
51
+ "import": {
52
+ "types": "./dist/nestjs/index.d.ts",
53
+ "default": "./dist/nestjs/index.js"
54
+ },
55
+ "require": {
56
+ "types": "./dist/nestjs/index.d.cts",
57
+ "default": "./dist/nestjs/index.cjs"
58
+ }
34
59
  },
35
60
  "./schema": "./schemas/ark.config.schema.json",
36
61
  "./schema/ark.config.schema.json": "./schemas/ark.config.schema.json",
@@ -50,8 +75,26 @@
50
75
  "./schema/ark.arkrules.schema.json": "./schemas/ark.arkrules.schema.json",
51
76
  "./schema/status-manifest": "./schemas/ark.status-manifest.schema.json",
52
77
  "./schema/ark.status-manifest.schema.json": "./schemas/ark.status-manifest.schema.json",
78
+ "./schema/invariant-probe": "./schemas/ark.invariant-probe.schema.json",
79
+ "./schema/ark.invariant-probe.schema.json": "./schemas/ark.invariant-probe.schema.json",
53
80
  "./package.json": "./package.json"
54
81
  },
82
+ "typesVersions": {
83
+ "*": {
84
+ "eslint": [
85
+ "./dist/eslint/index.d.ts"
86
+ ],
87
+ "order": [
88
+ "./dist/order/index.d.ts"
89
+ ],
90
+ "runtime": [
91
+ "./dist/runtime/index.d.ts"
92
+ ],
93
+ "nestjs": [
94
+ "./dist/nestjs/index.d.ts"
95
+ ]
96
+ }
97
+ },
55
98
  "bin": {
56
99
  "arkgate": "bin/ark.mjs",
57
100
  "arkgate-check": "bin/ark-check.mjs",
@@ -74,7 +117,6 @@
74
117
  "!bin/lib/safety-diagnostics.mjs",
75
118
  "!bin/lib/ts-resolve.mjs",
76
119
  "dist",
77
- "!dist/**/*.d.cts",
78
120
  "schemas",
79
121
  "!schemas/*.source.json",
80
122
  "docs/README.md",
@@ -125,6 +167,8 @@
125
167
  "security:audit": "npm audit --omit=dev --audit-level=high",
126
168
  "check:architecture": "node bin/ark-check.mjs --root . --config ark.config.json --strict",
127
169
  "check:js": "node scripts/check-js-syntax.mjs",
170
+ "check:knip": "knip",
171
+ "check:duplication": "jscpd --config .jscpd.json",
128
172
  "generate:layer-match": "node scripts/generate-layer-match.mjs",
129
173
  "check:layer-match": "node scripts/generate-layer-match.mjs --check",
130
174
  "generate:cli-pure": "node scripts/generate-cli-pure.mjs",
@@ -168,7 +212,8 @@
168
212
  "clean": "rm -rf dist",
169
213
  "release:npm": "node scripts/release-npm.mjs",
170
214
  "prepublishOnly": "npm run build",
171
- "bench:hook-path": "node scripts/hook-path-bench.mjs"
215
+ "bench:hook-path": "node scripts/hook-path-bench.mjs",
216
+ "bench:memory": "node scripts/memory-bench.mjs"
172
217
  },
173
218
  "dependencies": {
174
219
  "typescript-ark-host": "npm:typescript@6.0.3"
@@ -193,7 +238,7 @@
193
238
  "browserslist": "4.28.8",
194
239
  "nanoid": "3.3.18",
195
240
  "brace-expansion@2": "2.1.4",
196
- "brace-expansion@5": "5.0.9",
241
+ "brace-expansion@5": "5.0.12",
197
242
  "tsup": {
198
243
  "esbuild": "0.28.1"
199
244
  },
@@ -203,12 +248,17 @@
203
248
  },
204
249
  "devDependencies": {
205
250
  "@nestjs/common": "^11.1.27",
251
+ "@stryker-mutator/api": "9.6.1",
206
252
  "@stryker-mutator/core": "^9.6.1",
207
253
  "@stryker-mutator/vitest-runner": "^9.6.1",
208
254
  "@types/node": "^20.14.0",
209
255
  "@vitest/coverage-v8": "^4.1.11",
256
+ "acorn": "8.17.0",
257
+ "ajv": "8.18.0",
210
258
  "esbuild": "0.28.1",
211
259
  "fast-check": "^4.9.0",
260
+ "knip": "6.39.0",
261
+ "jscpd": "5.4.0",
212
262
  "reflect-metadata": "^0.2.2",
213
263
  "rxjs": "^7.8.2",
214
264
  "tsup": "^8.1.0",
@@ -246,6 +246,21 @@
246
246
  "arkruleSource": {
247
247
  "type": "string",
248
248
  "minLength": 1
249
+ },
250
+ "reasonId": {
251
+ "enum": [
252
+ "CROSS_PARENT_SLICE",
253
+ "CROSS_SIBLING_SLICE",
254
+ "CROSS_PARENT_VIA_SHARED"
255
+ ]
256
+ },
257
+ "universeFrom": {
258
+ "type": "string",
259
+ "minLength": 1
260
+ },
261
+ "universeTo": {
262
+ "type": "string",
263
+ "minLength": 1
249
264
  }
250
265
  }
251
266
  },
@@ -631,8 +631,22 @@
631
631
  "arkRules": {
632
632
  "type": "object",
633
633
  "additionalProperties": {
634
- "type": "string",
635
- "minLength": 1
634
+ "description": "A project-relative ArkRules path, or a list of paths merged into one catalog for this layer. A string keeps today's behaviour. An older build that types this value as a string rejects an array at config load.",
635
+ "oneOf": [
636
+ {
637
+ "type": "string",
638
+ "minLength": 1
639
+ },
640
+ {
641
+ "type": "array",
642
+ "minItems": 1,
643
+ "uniqueItems": true,
644
+ "items": {
645
+ "type": "string",
646
+ "minLength": 1
647
+ }
648
+ }
649
+ ]
636
650
  },
637
651
  "default": {}
638
652
  },
@@ -804,7 +818,7 @@
804
818
  "path",
805
819
  "stars"
806
820
  ],
807
- "description": "How a starred sliceFolders prefix is named. Absent and path keep today's ids (every literal and star binding). stars keeps the last literal plus the star bindings, so parallel trees share one feature id. Bare names are unchanged."
821
+ "description": "How a starred sliceFolders prefix is named. Absent and path keep today's ids (every literal and star binding). stars keeps the last literal plus every star binding (a star before the last literal is kept too), so parallel trees share one feature id. Bare names are unchanged."
808
822
  },
809
823
  "sharedRoots": {
810
824
  "type": "array",
@@ -838,12 +852,41 @@
838
852
  }
839
853
  },
840
854
  "sharedImportsSlice": {
841
- "type": "string",
842
- "enum": [
843
- "deny",
844
- "deny-cross-parent"
845
- ],
846
- "description": "deny blocks every shared-root import of a slice. deny-cross-parent leaves that hop as a warning and, in ark-check and CI only, reports a slice that reaches another universe through a shared root. The write hook and ESLint see one edge and do not block it. arkgate 4.8.22 and older reject deny-cross-parent."
855
+ "description": "deny blocks every shared-root import of a slice. deny-cross-parent leaves that hop as a warning and, in ark-check and CI only, reports a slice that reaches another universe through a shared root. The write hook and ESLint see one edge and do not block it. The object form { mode: \"deny-cross-parent\", stopAt } names composition roots (bootstrap, DI registrations) the whole-graph walk never starts at or passes through; a stop file must still sit under sharedRoots, and stopAt does not silence the direct SHARED_IMPORTS_SLICE warning. arkgate 4.8.22 and older reject deny-cross-parent. arkgate 4.8.23 and older reject the object form (must be one of deny, deny-cross-parent).",
856
+ "oneOf": [
857
+ {
858
+ "type": "string",
859
+ "enum": [
860
+ "deny",
861
+ "deny-cross-parent"
862
+ ]
863
+ },
864
+ {
865
+ "type": "object",
866
+ "additionalProperties": false,
867
+ "required": [
868
+ "mode",
869
+ "stopAt"
870
+ ],
871
+ "properties": {
872
+ "mode": {
873
+ "type": "string",
874
+ "enum": [
875
+ "deny-cross-parent"
876
+ ]
877
+ },
878
+ "stopAt": {
879
+ "type": "array",
880
+ "items": {
881
+ "type": "string",
882
+ "minLength": 1
883
+ },
884
+ "uniqueItems": true,
885
+ "minItems": 1
886
+ }
887
+ }
888
+ }
889
+ ]
847
890
  },
848
891
  "childSlices": {
849
892
  "type": "object",
@@ -851,7 +894,7 @@
851
894
  "required": [
852
895
  "sliceFolders"
853
896
  ],
854
- "description": "Optional inner wall under this rule. Absent keeps today's universe wall. sliceFolders names children. commonFolders and flat files are universe common. siblings is \"deny\" (default), \"advisory\", or { default, enforce }. parentMayImportChild defaults to false. allowedCrossSlice may use a whole-segment *. It clears only a sibling crossing. Cross-parent has no advisory knob. arkgate 4.8.22 and older reject this key. A build that still rejects unknown childSlices fields rejects allowedCrossSlice at config load. A build that still types siblings as a string enum rejects the object at config load.",
897
+ "description": "Optional inner wall under this rule. Runs only on a rule with peerIsolation: true and allowed: false; elsewhere it is inert and ark-check warns CONFIG_CHILD_SLICES_INERT (config load still accepts it, as 4.8.23 did). Absent keeps today's universe wall. sliceFolders names children. Flat files are universe common, and so is a commonFolders directory directly under the universe (a folder of that name inside a child belongs to that child). siblings is \"deny\" (default), \"advisory\", or { default, enforce, ratchet }. parentMayImportChild defaults to false. message is the text for inner-wall findings. allowedCrossSlice may use a whole-segment *. It clears only a sibling crossing. Cross-parent has no advisory knob. arkgate 4.8.22 and older reject this key. A build that still rejects unknown childSlices fields rejects allowedCrossSlice at config load. A build that still types siblings as a string enum rejects the object at config load.",
855
898
  "properties": {
856
899
  "sliceFolders": {
857
900
  "type": "array",
@@ -879,7 +922,7 @@
879
922
  "minItems": 1
880
923
  },
881
924
  "siblings": {
882
- "description": "\"deny\" or \"advisory\", or { default, enforce } so listed subtrees are errors while the default stays advisory. A string-enum build rejects the object at config load (must be one of deny, advisory).",
925
+ "description": "\"deny\" or \"advisory\", or { default, enforce, ratchet } so listed subtrees are errors while the default stays advisory. ratchet absent: a new advisory crossing fails only when the baseline in use already records an advisory crossing of this rule. ratchet true: any unrecorded advisory crossing fails when a baseline is in use. ratchet false: measure only, never fails. A string-enum build rejects the object at config load (must be one of deny, advisory). arkgate 4.8.23 and older reject ratchet (unknown field).",
883
926
  "oneOf": [
884
927
  {
885
928
  "type": "string",
@@ -909,6 +952,9 @@
909
952
  "minLength": 1
910
953
  },
911
954
  "uniqueItems": true
955
+ },
956
+ "ratchet": {
957
+ "type": "boolean"
912
958
  }
913
959
  }
914
960
  }
@@ -917,10 +963,20 @@
917
963
  "parentMayImportChild": {
918
964
  "type": "boolean"
919
965
  },
966
+ "message": {
967
+ "type": "string",
968
+ "minLength": 1,
969
+ "description": "Text for inner-wall findings (CROSS_SIBLING_SLICE and universe common importing a child). The rule message stays the universe-wall text (CROSS_PARENT_SLICE and fail-closed denies). Absent: ArkGate default text, never the rule message. arkgate 4.8.23 and older reject this field (unknown field)."
970
+ },
971
+ "arkRulesFile": {
972
+ "type": "string",
973
+ "minLength": 1,
974
+ "description": "Filename read at each child slice root this wall already resolved. The only token is <Layer>, replaced with a declared layer name (arkrules.<Layer>.json). No slash and no wildcard. A discovered file that omits appliesTo is scoped to that slice directory. Explicit appliesTo outside the slice fails config load (ARKRULE_SCOPE_ESCAPES_SLICE). The effective id is <childId>#<localId>. A build that rejects unknown childSlices fields fails at config load (unknown field)."
975
+ },
920
976
  "sliceAliases": {
921
977
  "type": "array",
922
978
  "minItems": 1,
923
- "description": "Maps a source path glob onto a child slice id (universe id plus one child segment) so files outside the slice trees take that universe and child for both walls. A bare name, a universe id alone, an unknown universe, and a wildcard in to are rejected. The glob may not overlap a slice folder, and two aliases may not match the same file. Doctor lists each alias as an owed move. A build that rejects unknown childSlices fields fails at config load (unknown field). arkgate 4.8.22 and older reject childSlices.",
979
+ "description": "Maps a source path glob onto a child slice id (universe id plus one child segment) so files outside the slice trees take that universe and child for both walls. A path without a wildcard also covers everything under it, as sharedRoots does. A bare name, a universe id alone, a target outside every universe shape, and a wildcard in to are rejected. Under stars identity a target written against the 4.8.23 stars id (star bindings before the last literal dropped, e.g. api/v1/x for modules/*/api/*) still loads when it maps to one universe shape, and ark-check warns CONFIG_SLICE_LEGACY_STARS_ID with the new id; it is rejected only when it maps to more than one shape. Config load checks only the shape; doctor reports a target universe no file belongs to. The glob may not overlap a slice folder, and two aliases may not match the same file. Doctor lists each unpinned alias as an owed move. pinned true lists the alias separately and does not count it as debt. reason is a doctor label and does not pin by itself. A build that rejects unknown childSlices fields fails at config load (unknown field). arkgate 4.8.22 and older reject childSlices.",
924
980
  "items": {
925
981
  "type": "object",
926
982
  "additionalProperties": false,
@@ -936,6 +992,15 @@
936
992
  "to": {
937
993
  "type": "string",
938
994
  "minLength": 1
995
+ },
996
+ "pinned": {
997
+ "type": "boolean",
998
+ "description": "True when a framework forces the path to stay. The alias is not an owed move and does not clear other honesty debt."
999
+ },
1000
+ "reason": {
1001
+ "type": "string",
1002
+ "minLength": 1,
1003
+ "description": "Doctor label, such as framework-route. A label alone does not pin."
939
1004
  }
940
1005
  }
941
1006
  }