arkgate 3.9.2 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/README.md +16 -4
  3. package/bin/ark-check-runtime.mjs +75 -3
  4. package/bin/ark-mcp-runtime.mjs +94 -0
  5. package/bin/lib/adapter-contract.mjs +14 -1
  6. package/bin/lib/analysis-engine.mjs +8 -8
  7. package/bin/lib/architecture-scan.mjs +35 -2
  8. package/bin/lib/arkrule-file-hints.mjs +71 -0
  9. package/bin/lib/arkrules-contract.mjs +382 -0
  10. package/bin/lib/arkrules-sensors.mjs +411 -0
  11. package/bin/lib/config-contract.mjs +85 -6
  12. package/bin/lib/doctor-advisories.mjs +14 -1
  13. package/bin/lib/doctor-plan.mjs +21 -0
  14. package/bin/lib/effective-contract-load.mjs +116 -0
  15. package/bin/lib/field-install.mjs +104 -0
  16. package/bin/lib/graph-blind.mjs +20 -1
  17. package/bin/lib/html-report-advisories.mjs +12 -5
  18. package/bin/lib/install-migrate.mjs +20 -2
  19. package/bin/lib/invariant-coverage-io.mjs +157 -0
  20. package/bin/lib/invariant-coverage.mjs +127 -0
  21. package/bin/lib/managed-upgrade.mjs +1 -1
  22. package/bin/lib/policy-delta-io.mjs +33 -0
  23. package/bin/lib/presets.mjs +241 -1
  24. package/bin/lib/remediation.mjs +28 -0
  25. package/bin/lib/resolved-candidate-facts.mjs +14 -1
  26. package/bin/lib/rules-inventory.mjs +144 -0
  27. package/bin/lib/rules-under-contract.mjs +320 -0
  28. package/bin/lib/start-preview.mjs +24 -7
  29. package/bin/lib/upgrade-command.mjs +373 -16
  30. package/dist/{configTypes-DAPvBqK6.d.ts → configTypes-CC0FEXoF.d.ts} +16 -3
  31. package/dist/eslint/index.cjs +2 -2
  32. package/dist/eslint/index.d.ts +1 -1
  33. package/dist/eslint/index.js +2 -2
  34. package/dist/index.cjs +14 -7
  35. package/dist/index.d.ts +615 -20
  36. package/dist/index.js +13 -6
  37. package/docs/README.md +5 -3
  38. package/docs/agent-guide.md +7 -3
  39. package/docs/ai-gates.md +6 -1
  40. package/docs/brownfield-adoption.md +22 -0
  41. package/docs/configuration.md +53 -4
  42. package/docs/develop.md +8 -2
  43. package/docs/enthusiast/README.md +11 -0
  44. package/docs/package-surface.md +13 -10
  45. package/docs/product-voice.md +11 -2
  46. package/docs/use.md +11 -0
  47. package/package.json +4 -17
  48. package/schemas/ark.analysis-result.schema.json +9 -1
  49. package/schemas/ark.arkrules.schema.json +141 -0
  50. package/schemas/ark.config.schema.json +10 -2
  51. package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
  52. package/server.json +2 -2
  53. package/templates/arkrules/ApplicationOrchestration.json +14 -0
  54. package/templates/arkrules/DomainModel.json +32 -0
  55. package/templates/arkrules/PersistenceAdapters.json +14 -0
  56. package/templates/arkrules/PresentationAdapters.json +14 -0
  57. package/templates/skills/ark-adopt.md +28 -1
  58. package/templates/skills/ark-architect.md +23 -0
  59. package/templates/skills/ark-autopilot.md +27 -1
  60. package/templates/skills/ark-contract.md +27 -1
  61. package/templates/skills/ark-coverage.md +23 -0
  62. package/templates/skills/ark-explain.md +39 -3
  63. package/templates/skills/ark-explore.md +26 -1
  64. package/templates/skills/ark-fix.md +23 -0
  65. package/templates/skills/ark-loop.md +23 -0
  66. package/templates/skills/ark-place.md +26 -0
  67. package/templates/skills/ark-runtime.md +4 -0
  68. package/templates/skills/ark-think.md +24 -1
  69. package/templates/skills/ark-upgrade.md +80 -11
  70. package/compat/nestjs.cjs +0 -2
  71. package/compat/nestjs.d.ts +0 -2
  72. package/compat/nestjs.js +0 -1
  73. package/compat/runtime.cjs +0 -2
  74. package/compat/runtime.d.ts +0 -2
  75. package/compat/runtime.js +0 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,131 @@ in the immutable pre-2.0 archive linked below.
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 4.0.1 — 2026-07-24
9
+
10
+ **Patch** over 4.0.0. Field footgun fix for stale global CLI on upgrade, ArkRules HTML/doctor
11
+ catalog honesty, dual-plane product wording, and internal coverage attribution.
12
+ **No required config migration.** Does not weaken write gate or CI. Does not close Z09 / RB-11.
13
+
14
+ ### Improved
15
+
16
+ - **HTML / doctor ArkRules showcase:** `rulesUnderContract` is no longer counts-only.
17
+ When `arkRules` is active, the report lists **per-layer** structure/invariant totals,
18
+ **structure sensors** (id · sensor · mode · description), **uncovered** invariants,
19
+ and a **covered sample** — so `/ark-explain` has real [ArkRules] content to walk.
20
+ Skill text updated to open `arkrules/*` and name real IDs.
21
+ - **HTML advisory cards:** doctor advisory blocks (contract health, ambient, cohesion,
22
+ parse health, graph blind spots, ArkRules) render with `class="section card"` so they
23
+ match the white panel styling of Layers / Violations / Enforcement (were bare
24
+ `<section data-advisory>` on the page background).
25
+ - **Dual-plane honesty in product surfaces:** configuration / use / product-voice /
26
+ `/ark-explain` and the ArkRules HTML strip state that structure sensors are heuristics,
27
+ invariants are catalog + coverage evidence (not a business runtime), and the two planes
28
+ never merge into one architecture score.
29
+ - **Coverage floors (internal):** branch threshold 83.0 → **82.5** after Fase 0 surface
30
+ growth (measured ~83% clean candidate); unit tests for upgrade refuse/HTML catalog use
31
+ ESM imports so V8 attributes hits (createRequire was under-counting).
32
+
33
+ ### Fixed
34
+
35
+ - **Stale global CLI vs project `arkgate` (upgrade footgun):** `ark upgrade` /
36
+ `ark update` now **fail closed** when the running CLI package root is outside
37
+ the project's `node_modules/arkgate` **and** the running version is older than
38
+ the installed project package. Recovery prefers the package-manager runner
39
+ (`npx` / `pnpm exec` / `yarn` + `arkgate`) so hoisted monorepos without a
40
+ nested shallow path still work; shallow
41
+ `node node_modules/arkgate/bin/ark.mjs` is install-root secondary advice.
42
+ With `--json`, refuse also emits a machine-readable
43
+ `{ refused, reason, message, cliVersion, projectVersion, nextCommand }` on
44
+ stdout (exit 2). Does not block project-local CLI, newer globals, or projects
45
+ with no local install yet. Field context: global Homebrew **2.x** mutative
46
+ upgrade next to 3.8+/4.0 projects (see
47
+ [4.0.0 release notes](docs/releases/4.0.0.md#field-footgun--global-arkgate-2x-on-path)).
48
+ - **`/ark-upgrade` skill:** procedure step 1 resolves the **project-local** CLI
49
+ first, probes for managed upgrade (`--plan-digest`), and aborts when only an
50
+ old PATH binary is available.
51
+ - **Doctor `rulesUnderContract` catalog caps:** structure sensors and uncovered
52
+ invariants are truncated in doctor JSON (with `structureTruncated` /
53
+ `uncoveredTruncated`) the same way as the covered sample — HTML announces
54
+ overflow instead of implying the full catalog is always embedded.
55
+
56
+ ## 4.0.0 — 2026-07-24
57
+
58
+ **Major** over 3.9.2. **Breaking:** deprecated root subpaths `arkgate/runtime` and `arkgate/nestjs`
59
+ are removed (use `@arkgate/runtime`). **ArkRules (opt-in):** intra-layer structural sensors,
60
+ invariant catalogs, coverage evidence, brownfield rules inventory, and modular `arkrules/*.json`
61
+ on the same enforcement plane (CLI, MCP, PreToolUse, CI, doctor). Absence of `arkRules` changes no
62
+ inter-layer verdict. **Not field-cohort proven:** Z09/RB-11 retained adoption remains open; AR16
63
+ case-study docs are scaffolding, not a closed field gate.
64
+
65
+ ### Breaking (AR04)
66
+
67
+ - **Removed** deprecated root package subpaths `arkgate/runtime` and `arkgate/nestjs`
68
+ (and the `compat/` forwarders). Import `@arkgate/runtime` / `@arkgate/runtime/nestjs`
69
+ instead. See [migration note](docs/migrate-from-ark-runtime-kernel.md#arkgate-4--ar04--root-runtime-forwarders-removed).
70
+
71
+ ### Added — ArkRules foundations + sensors (AR01–AR08)
72
+
73
+ - **AR01 — ArkRules foundations (ADR 0012):** optional `arkRules` map on `ark.config.json`
74
+ (`schemaVersion` `1.0→1.1` additive migration), sibling schema
75
+ `schemas/ark.arkrules.schema.json` (`arkgate/schema/arkrules`), pure
76
+ `loadArkRulesContract` / `resolveEffectiveContract` with per-rule provenance, and fail-closed
77
+ diagnostics for missing/invalid referenced files. Absence of `arkRules` changes no inter-layer
78
+ verdict. Zero-match `appliesTo` emits `ARKRULE_SCOPE_EMPTY` (advisory warn / enforced fail).
79
+ - **AR02 — Effective Contract policyHash + policy-delta:** `loadContract` folds non-empty
80
+ ArkRules into `policyHash` (absent → historical hash preserved); policy-delta classifies
81
+ arkrule add/remove/promote/demote; CLI loads referenced files via
82
+ `bin/lib/effective-contract-load.mjs` and observes them for resident invalidation.
83
+ - **AR03 — Adapter contract 1.4:** diagnostics carry optional `evidence.arkruleId` +
84
+ `evidence.arkruleSource`; remediation/nextAction for `ARKRULE_*` / `INVARIANT_UNCOVERED`
85
+ is judgment-only with provenance on every surface.
86
+ - **ADR 0013** + resolved facts schema `1.1` optional `classShapes[]` (class-shape evidence)
87
+ produced on the write/CI path via `extractClassShapesFromSource`.
88
+ - **Tier-1 sensors:** `aggregate-private-state`, `always-valid-factory`,
89
+ `domain-event-on-mutation`, `orchestration-only`, `thin-adapter`.
90
+ - **Tier-2 advisory:** `no-anemic-model` (never promotable).
91
+ - **fileHints:** Tooling derives conservative `orchestrationHeavy` / `adapterThick` hints
92
+ (`deriveArkRuleFileHints`) and feeds the write/CI scan path (prefer false negatives;
93
+ default templates stay advisory).
94
+ - **Templates:** `templates/arkrules/*.json` + presets/init emit lean `arkRules` refs and
95
+ copy editable starter files.
96
+
97
+ ### Added — invariants, inventory, migration surfaces (shipped in package; progressive opt-in)
98
+
99
+ - **ADR 0014–0016:** invariant catalog + coverage evidence + promotion ladder; migration
100
+ routes through existing skills; no executable evaluator in core.
101
+ - **Invariant coverage:** test-title + symbol evidence; `INVARIANT_UNCOVERED`; partial when
102
+ test globs missing; `canPromoteInvariant` refuses uncovered promotions. Write path loads real
103
+ test contents via `bin/lib/invariant-coverage-io.mjs`. CLI policy-delta loads Effective ArkRules
104
+ + coverage so covered promotions can auto-strengthen.
105
+ - **Doctor/HTML** `rulesUnderContract` (counts, not a score); report parity key.
106
+ - **Rules inventory:** `ark-check --rules-inventory` + MCP `ark_rules_inventory`; extraction
107
+ cards for pilotLoop; freeze residual reuses baseline keys.
108
+ - Skills deepen (`ark-adopt`, `ark-contract`, `ark-place`) without new skill names.
109
+ - Pre-release field dogfood workflow: `.grok/workflows/pre-release-field-dogfood.rhai`.
110
+
111
+ ### Confidence / package budgets (4.0.0)
112
+
113
+ - **Branch floor** recalibrated **84.5% → 83.0%** after ArkRules dual-plane growth (measured
114
+ ~83.3% on the clean candidate). Statement / function / line floors and mutation gates unchanged.
115
+ - **Module LOC budgets** raised for `ark-check-runtime`, `doctor-plan`, `presets`,
116
+ `html-report-advisories` (evidence in `scripts/check-module-budgets.mjs`).
117
+ - **Package pack budgets** remeasured for 4.0.0 (≥10% headroom in `release/package-budgets.v1.json`).
118
+
119
+ ### Honesty / not claimed in 4.0.0
120
+
121
+ - No claim that Z09 / RB-11 retained field adoption is closed.
122
+ - No claim that a consented multi-adopter field pilot (AR16 gate) is complete — case-study docs
123
+ are present; cohort evidence is not.
124
+ - No numeric trust score; green with frozen residual still says so.
125
+ - **Dual-truth residual:** `ark upgrade --no-install` can refresh managed assets while leaving
126
+ package.json on an older pin — doctor exposes `packageVersionTruth` and upgrade JSON/human notes
127
+ when the pin is behind the CLI.
128
+ - **Field note — global 2.x PATH:** bare `ark upgrade` from a global **arkgate 2.x** install is
129
+ mutative (pre managed content-identity) and unsafe next to 3.8+/4.0 projects — prefer
130
+ `npx arkgate upgrade …`. Documented in [4.0.0 release notes](docs/releases/4.0.0.md#field-footgun--global-arkgate-2x-on-path);
131
+ CLI fail-closed guard ships under Unreleased.
132
+
8
133
  ## 3.9.2 — 2026-07-23
9
134
 
10
135
  **Patch** over 3.9.1. Product honesty for post-validity coaching, coverage/host write paths, and
package/README.md CHANGED
@@ -16,9 +16,9 @@ and makes sure a “green” check means something real.
16
16
 
17
17
  </div>
18
18
 
19
- > **ArkGate 3.9.2** is the next prepared patch (enforcement honesty: coverage/host write path,
20
- > design-weak one-pilot coaching, advisory graph-blind). **npm `latest` is still 3.9.1** until
21
- > publication succeeds. [3.9.2 notes](docs/releases/3.9.2.md) · [3.9.1](docs/releases/3.9.1.md) ·
19
+ > **ArkGate 4.0.1** is the next prepared patch (stale global CLI upgrade guard + ArkRules
20
+ > HTML/docs honesty). **npm `latest` is still 4.0.0** until publication succeeds.
21
+ > [4.0.1 notes](docs/releases/4.0.1.md) · [4.0.0](docs/releases/4.0.0.md) ·
22
22
  > [Docs hub](docs/README.md)
23
23
 
24
24
  ---
@@ -66,6 +66,16 @@ A machine-readable architecture file (`ark.config.json`) plus enforcement:
66
66
  | **While the AI writes** | Hard PreToolUse on supported hosts; advisory MCP elsewhere |
67
67
  | **Before merge** | `arkgate-check` as a **required** CI status |
68
68
 
69
+ ### Two planes (4.0)
70
+
71
+ | Plane | What it guards | Config |
72
+ |-------|----------------|--------|
73
+ | **Layers** (always) | Who may talk to whom — imports, placement, purity, isolation | `ark.config.json` layers + rules |
74
+ | **ArkRules** (opt-in) | Habits *inside* a layer — structure sensors + domain invariants as data | `arkRules` → `arkrules/<Layer>.json` |
75
+
76
+ Absence of ArkRules changes no inter-layer verdict. Label residual **`[Layer]`** vs **`[ArkRules]`**.
77
+ Details: [configuration](docs/configuration.md#arkrules-intra-layer-opt-in) · [use path](docs/use.md).
78
+
69
79
  **Not** a web framework, ORM, or job runner. Optional experimental runtime is separate and not required for the gate.
70
80
 
71
81
  **Name note:** npm package `arkgate` — not affiliated with the separate Archgate CLI project.
@@ -132,6 +142,7 @@ Setup per host: [docs/ai-gates.md](docs/ai-gates.md) · Develop path: [docs/deve
132
142
  | Contract agents can read (`ark://manifest`) | ✅ | ❌ |
133
143
  | Placement + preflight for multi-file changes | ✅ | ❌ |
134
144
  | Honest governed % + dual plan (edges vs shape) | ✅ | ❌ |
145
+ | Opt-in intra-layer ArkRules (structure + invariants) | ✅ | ❌ |
135
146
  | Incomplete analysis cannot look green | ✅ | varies |
136
147
 
137
148
  ---
@@ -176,7 +187,8 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
176
187
  | Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
177
188
  | Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
178
189
  | Security | [SECURITY.md](SECURITY.md) |
179
- | Latest release (3.9.1 on npm; 3.9.2 prepared) | [3.9.2](docs/releases/3.9.2.md) · [3.9.1](docs/releases/3.9.1.md) · [CHANGELOG](CHANGELOG.md) |
190
+ | Latest release (4.0.0 on npm; 4.0.1 prepared) | [4.0.1](docs/releases/4.0.1.md) · [4.0.0](docs/releases/4.0.0.md) · [CHANGELOG](CHANGELOG.md) |
191
+ | Previous (3.9.2) | [docs/releases/3.9.2.md](docs/releases/3.9.2.md) |
180
192
  | History / maintainer evidence | [docs/archive/](docs/archive/README.md) |
181
193
 
182
194
  ---
@@ -84,7 +84,11 @@ import {
84
84
  detectBestFitModel,
85
85
  dirSegmentsFromGlob,
86
86
  } from './lib/suggestions.mjs';
87
- import { ARCHITECTURE_PRESETS } from './lib/presets.mjs';
87
+ import {
88
+ ARCHITECTURE_PRESETS,
89
+ withDefaultArkRules,
90
+ writeArkRulesTemplates,
91
+ } from './lib/presets.mjs';
88
92
  import { loadArkConfigContract, parseArkConfigJson } from './lib/config-contract.mjs';
89
93
  import { createAdapterResult } from './lib/adapter-contract.mjs';
90
94
  import { collectGovernedFiles, normalize, walk } from './lib/scan-files.mjs';
@@ -180,6 +184,7 @@ function parseArgs(argv) {
180
184
  else if (arg === '--coverage') args.coverage = true;
181
185
  else if (arg === '--doctor') args.doctor = true;
182
186
  else if (arg === '--plan') args.plan = true;
187
+ else if (arg === '--rules-inventory') args.rulesInventory = true;
183
188
  else if (arg === '--recommend') args.recommend = true;
184
189
  else if (arg === '--write-plan') args.writePlan = true;
185
190
  else if (arg === '--list-policy-packs') args.listPolicyPacks = true;
@@ -239,6 +244,7 @@ function usage() {
239
244
  ' ark-check --doctor [--json] [--resident] [--fail-on-new-smells --base-ref <git-ref>] read-only diagnosis; resident JSON falls back cold',
240
245
  ' ark-check --coverage [--json] per-layer file counts + full unclassified list (report only, exit 0)',
241
246
  ' ark-check --plan [--json] classified remediation plan (mechanical-safe / judgment / deferred) + goal; report only',
247
+ ' ark-check --rules-inventory [--json] brownfield rules inventory (AR13; deterministic candidates, not a score)',
242
248
  ' ark-check --recommend [--json] [--write-plan] application-shape plan; --write-plan emits ark-adoption-plan.json',
243
249
  ' ark-check --list-policy-packs enthusiast packs (hexagonal, layered, feature-sliced, monorepo, ui-surface, vertical-slice, ddd-bounded-contexts)',
244
250
  ' ark-check --apply-policy-pack <id> [--force] write ark.config.json from templates/policy-packs/ (uses preset factory)',
@@ -515,6 +521,7 @@ function runApplyPolicyPack(args) {
515
521
  try {
516
522
  const { pack, config } = buildConfigFromPolicyPack(args.applyPolicyPack, args.root);
517
523
  fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
524
+ writeArkRulesTemplates(args.root, config, { force: args.force === true });
518
525
  if (args.json) {
519
526
  console.log(
520
527
  JSON.stringify(
@@ -750,6 +757,12 @@ function runInit(args) {
750
757
  args.root
751
758
  );
752
759
  fs.writeFileSync(configPath, `${JSON.stringify(finalConfig, null, 2)}\n`);
760
+ const arkrulesWritten = writeArkRulesTemplates(args.root, finalConfig, {
761
+ force: args.force === true,
762
+ });
763
+ if (arkrulesWritten.length > 0) {
764
+ console.log(`Wrote ArkRules templates: ${arkrulesWritten.join(', ')}`);
765
+ }
753
766
  console.log(`Wrote ${configPath} (${args.preset} preset)`);
754
767
  if (finalConfig.frameworkOverlay) {
755
768
  console.log(
@@ -788,7 +801,7 @@ function runInit(args) {
788
801
  // scaffold under) even when src/ doesn't exist yet — the layers are optional, so the
789
802
  // check passes today and governance switches on the moment src/domain/ etc. appear.
790
803
  // Detected configs also get framework overlays so Nest/Next flat files are classified.
791
- const finalConfig =
804
+ const finalConfig = withDefaultArkRules(
792
805
  mode === 'detected'
793
806
  ? applyFrameworkLayoutOverlays(config, args.root)
794
807
  : mode === 'monorepo'
@@ -799,9 +812,11 @@ function runInit(args) {
799
812
  : createElevenLayerConfig({
800
813
  rootDir: srcDir === '.' ? 'src' : srcDir,
801
814
  root: args.root,
802
- });
815
+ })
816
+ );
803
817
 
804
818
  fs.writeFileSync(configPath, `${JSON.stringify(finalConfig, null, 2)}\n`);
819
+ writeArkRulesTemplates(args.root, finalConfig, { force: args.force === true });
805
820
 
806
821
  console.log(`Wrote ${configPath}`);
807
822
  console.log('');
@@ -1244,6 +1259,63 @@ async function main() {
1244
1259
  return;
1245
1260
  }
1246
1261
 
1262
+ if (args.rulesInventory) {
1263
+ const { buildRulesInventory, inventoryToExtractionCard } = await import('./lib/rules-inventory.mjs');
1264
+ const fileContents = {};
1265
+ for (const file of files.slice(0, 400)) {
1266
+ const rel = normalize(path.relative(root, file));
1267
+ try {
1268
+ fileContents[rel] = fs.readFileSync(file, 'utf8');
1269
+ } catch {
1270
+ /* skip unreadable */
1271
+ }
1272
+ }
1273
+ const contracted = [];
1274
+ if (config.arkRules) {
1275
+ try {
1276
+ const { loadEffectiveArkRulesFromDisk } = await import('./lib/effective-contract-load.mjs');
1277
+ const loaded = loadEffectiveArkRulesFromDisk(root, config);
1278
+ for (const rule of loaded.arkRules.structure ?? []) contracted.push(rule.id);
1279
+ for (const inv of loaded.arkRules.invariants ?? []) contracted.push(inv.id);
1280
+ } catch {
1281
+ /* advisory inventory still useful */
1282
+ }
1283
+ }
1284
+ const inventory = buildRulesInventory({
1285
+ fileContents,
1286
+ contractedRuleIds: contracted,
1287
+ });
1288
+ const nextPilot =
1289
+ inventory.candidates[0] != null
1290
+ ? inventoryToExtractionCard(inventory.candidates[0])
1291
+ : null;
1292
+ const payload = {
1293
+ rulesInventory: inventory,
1294
+ rulesMigration: {
1295
+ inventoried: inventory.inventoried,
1296
+ underContract: inventory.underContract,
1297
+ frozen: inventory.frozen,
1298
+ notAScore: true,
1299
+ },
1300
+ nextPilot: nextPilot,
1301
+ };
1302
+ if (args.json) {
1303
+ console.log(JSON.stringify(payload, null, 2));
1304
+ } else {
1305
+ console.log(
1306
+ `Rules inventory: ${inventory.inventoried} inventoried, ${inventory.underContract} under contract, ${inventory.frozen} frozen (not a score).`
1307
+ );
1308
+ for (const c of inventory.candidates.slice(0, 12)) {
1309
+ console.log(` - [${c.confidence}] ${c.kind} @ ${c.file}:${c.line} — ${c.message}`);
1310
+ }
1311
+ if (nextPilot) {
1312
+ console.log(`Next extraction pilot: ${nextPilot.pilot} → ${nextPilot.pilotTarget}`);
1313
+ }
1314
+ }
1315
+ process.exitCode = 0;
1316
+ return;
1317
+ }
1318
+
1247
1319
  const skillGaps = detectSkillGaps(root);
1248
1320
  const codexHomeGap = detectCodexHomeGap(root);
1249
1321
  const codexRepoSkillGap = detectCodexRepoSkillGap(root);
@@ -22,7 +22,40 @@ import { createImportTargetResolver } from './lib/import-resolve.mjs';
22
22
  import { validateWithAutoPatch, resolveImportFileAbs } from './lib/auto-patch.mjs';
23
23
  import { composePrepareWrite } from './lib/prepare-write.mjs';
24
24
  import { loadArkConfigContract } from './lib/config-contract.mjs';
25
+ import { loadEffectiveArkRulesFromDisk } from './lib/effective-contract-load.mjs';
26
+ import {
27
+ buildRulesInventory,
28
+ inventoryToExtractionCard,
29
+ } from './lib/rules-inventory.mjs';
25
30
  import { ARK_ANALYSIS_RESULT_SCHEMA, createAdapterResult } from './lib/adapter-contract.mjs';
31
+
32
+ function arkRulesCatalogForManifest(root, config) {
33
+ if (!config?.arkRules || typeof config.arkRules !== 'object') return {};
34
+ try {
35
+ const loaded = loadEffectiveArkRulesFromDisk(root, config);
36
+ if (loaded.errors?.length || !loaded.arkRules) return {};
37
+ const structure = (loaded.arkRules.structure ?? []).map((r) => ({
38
+ id: r.id,
39
+ sensor: r.sensor,
40
+ mode: r.mode,
41
+ layer: r.provenance?.layer,
42
+ sourceFile: r.provenance?.sourceFile,
43
+ }));
44
+ const invariants = (loaded.arkRules.invariants ?? []).map((r) => ({
45
+ id: r.id,
46
+ description: r.description,
47
+ aggregate: r.aggregate,
48
+ mode: r.mode,
49
+ layer: r.provenance?.layer,
50
+ sourceFile: r.provenance?.sourceFile,
51
+ coverage: r.coverage,
52
+ }));
53
+ if (structure.length === 0 && invariants.length === 0) return {};
54
+ return { arkRulesCatalog: { structure, invariants } };
55
+ } catch {
56
+ return {};
57
+ }
58
+ }
26
59
  import { loadTypeScript } from './lib/typescript-host.mjs';
27
60
  import { validateSnippetAnalysis } from './lib/snippet-analysis.mjs';
28
61
  import { loadGoldenPattern, attachGoldenToPlacement } from './lib/golden-pattern.mjs';
@@ -1559,6 +1592,14 @@ export async function runArkMcp({ hookInput } = {}) {
1559
1592
  'or the contract misses package roots.',
1560
1593
  inputSchema: { type: 'object', properties: {} },
1561
1594
  },
1595
+ {
1596
+ name: 'ark_rules_inventory',
1597
+ description:
1598
+ 'Deterministic brownfield rules inventory (AR13): validation-in-controller, magic constants, ' +
1599
+ 'anemic entities, mutation-without-guard. Returns honest counts (inventoried/under-contract/frozen) ' +
1600
+ '— never a numeric score. Same plane as ark-check --rules-inventory.',
1601
+ inputSchema: { type: 'object', properties: {} },
1602
+ },
1562
1603
  ];
1563
1604
 
1564
1605
  const RESOURCES = [
@@ -1633,6 +1674,11 @@ export async function runArkMcp({ hookInput } = {}) {
1633
1674
  ...(config.safety && typeof config.safety === 'object'
1634
1675
  ? { safety: config.safety }
1635
1676
  : {}),
1677
+ // AR09 — expose ArkRules references + catalog when configured (ADR 0014).
1678
+ ...(config.arkRules && typeof config.arkRules === 'object'
1679
+ ? { arkRules: config.arkRules }
1680
+ : {}),
1681
+ ...arkRulesCatalogForManifest(args.root, config),
1636
1682
  ...(suggestions.length > 0
1637
1683
  ? {
1638
1684
  suggestedLayers: suggestions,
@@ -1981,6 +2027,53 @@ export async function runArkMcp({ hookInput } = {}) {
1981
2027
  }
1982
2028
  }
1983
2029
 
2030
+ function runRulesInventoryTool() {
2031
+ try {
2032
+ const governed = collectGovernedFiles(args.root, config);
2033
+ const fileContents = {};
2034
+ for (const file of governed.slice(0, 400)) {
2035
+ const rel = path.relative(args.root, file).split(path.sep).join('/');
2036
+ try {
2037
+ fileContents[rel] = fs.readFileSync(file, 'utf8');
2038
+ } catch {
2039
+ /* skip */
2040
+ }
2041
+ }
2042
+ const contracted = [];
2043
+ const loaded = loadEffectiveArkRulesFromDisk(args.root, config);
2044
+ for (const rule of loaded.arkRules?.structure ?? []) contracted.push(rule.id);
2045
+ for (const inv of loaded.arkRules?.invariants ?? []) contracted.push(inv.id);
2046
+ const inventory = buildRulesInventory({
2047
+ fileContents,
2048
+ contractedRuleIds: contracted,
2049
+ });
2050
+ const nextPilot =
2051
+ inventory.candidates[0] != null
2052
+ ? inventoryToExtractionCard(inventory.candidates[0])
2053
+ : null;
2054
+ const payload = {
2055
+ ok: true,
2056
+ rulesInventory: inventory,
2057
+ rulesMigration: {
2058
+ inventoried: inventory.inventoried,
2059
+ underContract: inventory.underContract,
2060
+ frozen: inventory.frozen,
2061
+ notAScore: true,
2062
+ },
2063
+ nextPilot,
2064
+ };
2065
+ return {
2066
+ content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }],
2067
+ structuredContent: payload,
2068
+ };
2069
+ } catch (error) {
2070
+ return {
2071
+ content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
2072
+ isError: true,
2073
+ };
2074
+ }
2075
+ }
2076
+
1984
2077
  function runSuggestIncludeTool() {
1985
2078
  try {
1986
2079
  const workspaces = detectWorkspaces(args.root);
@@ -2028,6 +2121,7 @@ export async function runArkMcp({ hookInput } = {}) {
2028
2121
  ark_prepare_change: runPrepareChange,
2029
2122
  ark_recommend: runRecommendTool,
2030
2123
  ark_suggest_include: runSuggestIncludeTool,
2124
+ ark_rules_inventory: runRulesInventoryTool,
2031
2125
  };
2032
2126
 
2033
2127
  const send = (msg) => process.stdout.write(`${JSON.stringify(msg)}\n`);
@@ -8,7 +8,8 @@
8
8
  * Pure CLI helper (bin/lib/adapter-contract.mjs). Zero Node I/O.
9
9
  */
10
10
 
11
- export const ARK_ANALYSIS_RESULT_SCHEMA_VERSION = '1.3';
11
+ /** 1.4 adds optional evidence.arkruleId + evidence.arkruleSource (ADR 0012 / AR03). */
12
+ export const ARK_ANALYSIS_RESULT_SCHEMA_VERSION = '1.4';
12
13
  function text(value) {
13
14
  return typeof value === 'string' && value.length > 0 ? value : undefined;
14
15
  }
@@ -40,6 +41,14 @@ function nextActionForDiagnostic(ruleId, evidence, violation) {
40
41
  return 'Publish through a registered intent creator, then run Ark again.';
41
42
  if (ruleId === 'PUBLISH_MISSING_SOURCE')
42
43
  return 'Add metadata.source to the publish call, then run Ark again.';
44
+ if (ruleId === 'ARKRULE_STRUCTURE' ||
45
+ ruleId === 'ARKRULE_INVARIANT' ||
46
+ ruleId === 'INVARIANT_UNCOVERED' ||
47
+ ruleId.startsWith('ARKRULE_')) {
48
+ const source = evidence.arkruleSource ?? 'arkrules/<Layer>.json';
49
+ const id = evidence.arkruleId ?? 'the ArkRule';
50
+ return `Fix the structure or invariant for ${id} (declared in ${source}), then preflight again. Do not demote the rule without a hash-bound policy acknowledgement.`;
51
+ }
43
52
  return `Resolve ${ruleId} without weakening ark.config.json, then run Ark again.`;
44
53
  }
45
54
  export function toAdapterDiagnostic(violation, fallbackSeverity = 'error') {
@@ -67,6 +76,8 @@ export function toAdapterDiagnostic(violation, fallbackSeverity = 'error') {
67
76
  : {}),
68
77
  ...(text(violation.capability) ? { capability: text(violation.capability) } : {}),
69
78
  ...(text(violation.edgeKind) ? { edgeKind: text(violation.edgeKind) } : {}),
79
+ ...(text(violation.arkruleId) ? { arkruleId: text(violation.arkruleId) } : {}),
80
+ ...(text(violation.arkruleSource) ? { arkruleSource: text(violation.arkruleSource) } : {}),
70
81
  };
71
82
  return {
72
83
  ruleId,
@@ -264,6 +275,8 @@ export const ARK_ANALYSIS_RESULT_SCHEMA = {
264
275
  peerIsolation: { type: 'boolean' },
265
276
  capability: { type: 'string', minLength: 1 },
266
277
  edgeKind: { type: 'string', minLength: 1 },
278
+ arkruleId: { type: 'string', minLength: 1 },
279
+ arkruleSource: { type: 'string', minLength: 1 },
267
280
  },
268
281
  },
269
282
  nextAction: { type: 'string', minLength: 1 },