arkgate 3.9.2 → 4.0.0

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 (74) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +16 -5
  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 +19 -0
  17. package/bin/lib/html-report-advisories.mjs +24 -0
  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/policy-delta-io.mjs +33 -0
  22. package/bin/lib/presets.mjs +241 -1
  23. package/bin/lib/remediation.mjs +28 -0
  24. package/bin/lib/resolved-candidate-facts.mjs +14 -1
  25. package/bin/lib/rules-inventory.mjs +144 -0
  26. package/bin/lib/rules-under-contract.mjs +66 -0
  27. package/bin/lib/start-preview.mjs +24 -7
  28. package/bin/lib/upgrade-command.mjs +48 -2
  29. package/dist/{configTypes-DAPvBqK6.d.ts → configTypes-CC0FEXoF.d.ts} +16 -3
  30. package/dist/eslint/index.cjs +2 -2
  31. package/dist/eslint/index.d.ts +1 -1
  32. package/dist/eslint/index.js +2 -2
  33. package/dist/index.cjs +14 -7
  34. package/dist/index.d.ts +615 -20
  35. package/dist/index.js +13 -6
  36. package/docs/README.md +4 -3
  37. package/docs/agent-guide.md +7 -3
  38. package/docs/ai-gates.md +6 -1
  39. package/docs/brownfield-adoption.md +20 -0
  40. package/docs/configuration.md +37 -4
  41. package/docs/develop.md +8 -2
  42. package/docs/enthusiast/README.md +11 -0
  43. package/docs/package-surface.md +13 -11
  44. package/docs/product-voice.md +9 -2
  45. package/docs/use.md +9 -0
  46. package/package.json +4 -17
  47. package/schemas/ark.analysis-result.schema.json +9 -1
  48. package/schemas/ark.arkrules.schema.json +141 -0
  49. package/schemas/ark.config.schema.json +10 -2
  50. package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
  51. package/server.json +2 -2
  52. package/templates/arkrules/ApplicationOrchestration.json +14 -0
  53. package/templates/arkrules/DomainModel.json +32 -0
  54. package/templates/arkrules/PersistenceAdapters.json +14 -0
  55. package/templates/arkrules/PresentationAdapters.json +14 -0
  56. package/templates/skills/ark-adopt.md +28 -1
  57. package/templates/skills/ark-architect.md +23 -0
  58. package/templates/skills/ark-autopilot.md +27 -1
  59. package/templates/skills/ark-contract.md +27 -1
  60. package/templates/skills/ark-coverage.md +23 -0
  61. package/templates/skills/ark-explain.md +23 -0
  62. package/templates/skills/ark-explore.md +26 -1
  63. package/templates/skills/ark-fix.md +23 -0
  64. package/templates/skills/ark-loop.md +23 -0
  65. package/templates/skills/ark-place.md +26 -0
  66. package/templates/skills/ark-runtime.md +4 -0
  67. package/templates/skills/ark-think.md +24 -1
  68. package/templates/skills/ark-upgrade.md +23 -0
  69. package/compat/nestjs.cjs +0 -2
  70. package/compat/nestjs.d.ts +0 -2
  71. package/compat/nestjs.js +0 -1
  72. package/compat/runtime.cjs +0 -2
  73. package/compat/runtime.d.ts +0 -2
  74. package/compat/runtime.js +0 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,79 @@ in the immutable pre-2.0 archive linked below.
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ## 4.0.0 — 2026-07-24
9
+
10
+ **Major** over 3.9.2. **Breaking:** deprecated root subpaths `arkgate/runtime` and `arkgate/nestjs`
11
+ are removed (use `@arkgate/runtime`). **ArkRules (opt-in):** intra-layer structural sensors,
12
+ invariant catalogs, coverage evidence, brownfield rules inventory, and modular `arkrules/*.json`
13
+ on the same enforcement plane (CLI, MCP, PreToolUse, CI, doctor). Absence of `arkRules` changes no
14
+ inter-layer verdict. **Not field-cohort proven:** Z09/RB-11 retained adoption remains open; AR16
15
+ case-study docs are scaffolding, not a closed field gate.
16
+
17
+ ### Breaking (AR04)
18
+
19
+ - **Removed** deprecated root package subpaths `arkgate/runtime` and `arkgate/nestjs`
20
+ (and the `compat/` forwarders). Import `@arkgate/runtime` / `@arkgate/runtime/nestjs`
21
+ instead. See [migration note](docs/migrate-from-ark-runtime-kernel.md#arkgate-4--ar04--root-runtime-forwarders-removed).
22
+
23
+ ### Added — ArkRules foundations + sensors (AR01–AR08)
24
+
25
+ - **AR01 — ArkRules foundations (ADR 0012):** optional `arkRules` map on `ark.config.json`
26
+ (`schemaVersion` `1.0→1.1` additive migration), sibling schema
27
+ `schemas/ark.arkrules.schema.json` (`arkgate/schema/arkrules`), pure
28
+ `loadArkRulesContract` / `resolveEffectiveContract` with per-rule provenance, and fail-closed
29
+ diagnostics for missing/invalid referenced files. Absence of `arkRules` changes no inter-layer
30
+ verdict. Zero-match `appliesTo` emits `ARKRULE_SCOPE_EMPTY` (advisory warn / enforced fail).
31
+ - **AR02 — Effective Contract policyHash + policy-delta:** `loadContract` folds non-empty
32
+ ArkRules into `policyHash` (absent → historical hash preserved); policy-delta classifies
33
+ arkrule add/remove/promote/demote; CLI loads referenced files via
34
+ `bin/lib/effective-contract-load.mjs` and observes them for resident invalidation.
35
+ - **AR03 — Adapter contract 1.4:** diagnostics carry optional `evidence.arkruleId` +
36
+ `evidence.arkruleSource`; remediation/nextAction for `ARKRULE_*` / `INVARIANT_UNCOVERED`
37
+ is judgment-only with provenance on every surface.
38
+ - **ADR 0013** + resolved facts schema `1.1` optional `classShapes[]` (class-shape evidence)
39
+ produced on the write/CI path via `extractClassShapesFromSource`.
40
+ - **Tier-1 sensors:** `aggregate-private-state`, `always-valid-factory`,
41
+ `domain-event-on-mutation`, `orchestration-only`, `thin-adapter`.
42
+ - **Tier-2 advisory:** `no-anemic-model` (never promotable).
43
+ - **fileHints:** Tooling derives conservative `orchestrationHeavy` / `adapterThick` hints
44
+ (`deriveArkRuleFileHints`) and feeds the write/CI scan path (prefer false negatives;
45
+ default templates stay advisory).
46
+ - **Templates:** `templates/arkrules/*.json` + presets/init emit lean `arkRules` refs and
47
+ copy editable starter files.
48
+
49
+ ### Added — invariants, inventory, migration surfaces (shipped in package; progressive opt-in)
50
+
51
+ - **ADR 0014–0016:** invariant catalog + coverage evidence + promotion ladder; migration
52
+ routes through existing skills; no executable evaluator in core.
53
+ - **Invariant coverage:** test-title + symbol evidence; `INVARIANT_UNCOVERED`; partial when
54
+ test globs missing; `canPromoteInvariant` refuses uncovered promotions. Write path loads real
55
+ test contents via `bin/lib/invariant-coverage-io.mjs`. CLI policy-delta loads Effective ArkRules
56
+ + coverage so covered promotions can auto-strengthen.
57
+ - **Doctor/HTML** `rulesUnderContract` (counts, not a score); report parity key.
58
+ - **Rules inventory:** `ark-check --rules-inventory` + MCP `ark_rules_inventory`; extraction
59
+ cards for pilotLoop; freeze residual reuses baseline keys.
60
+ - Skills deepen (`ark-adopt`, `ark-contract`, `ark-place`) without new skill names.
61
+ - Pre-release field dogfood workflow: `.grok/workflows/pre-release-field-dogfood.rhai`.
62
+
63
+ ### Confidence / package budgets (4.0.0)
64
+
65
+ - **Branch floor** recalibrated **84.5% → 83.0%** after ArkRules dual-plane growth (measured
66
+ ~83.3% on the clean candidate). Statement / function / line floors and mutation gates unchanged.
67
+ - **Module LOC budgets** raised for `ark-check-runtime`, `doctor-plan`, `presets`,
68
+ `html-report-advisories` (evidence in `scripts/check-module-budgets.mjs`).
69
+ - **Package pack budgets** remeasured for 4.0.0 (≥10% headroom in `release/package-budgets.v1.json`).
70
+
71
+ ### Honesty / not claimed in 4.0.0
72
+
73
+ - No claim that Z09 / RB-11 retained field adoption is closed.
74
+ - No claim that a consented multi-adopter field pilot (AR16 gate) is complete — case-study docs
75
+ are present; cohort evidence is not.
76
+ - No numeric trust score; green with frozen residual still says so.
77
+ - **Dual-truth residual:** `ark upgrade --no-install` can refresh managed assets while leaving
78
+ package.json on an older pin — doctor exposes `packageVersionTruth` and upgrade JSON/human notes
79
+ when the pin is behind the CLI.
80
+
8
81
  ## 3.9.2 — 2026-07-23
9
82
 
10
83
  **Patch** over 3.9.1. Product honesty for post-validity coaching, coverage/host write paths, and
package/README.md CHANGED
@@ -16,10 +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) ·
22
- > [Docs hub](docs/README.md)
19
+ > **ArkGate 4.0.0** is prepared on this line (ArkRules opt-in + AR04 breaking forwarder removal).
20
+ > Last npm `latest` remains **3.9.2** until publish. [4.0.0 notes](docs/releases/4.0.0.md) ·
21
+ > [3.9.2](docs/releases/3.9.2.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
23
22
 
24
23
  ---
25
24
 
@@ -66,6 +65,16 @@ A machine-readable architecture file (`ark.config.json`) plus enforcement:
66
65
  | **While the AI writes** | Hard PreToolUse on supported hosts; advisory MCP elsewhere |
67
66
  | **Before merge** | `arkgate-check` as a **required** CI status |
68
67
 
68
+ ### Two planes (4.0)
69
+
70
+ | Plane | What it guards | Config |
71
+ |-------|----------------|--------|
72
+ | **Layers** (always) | Who may talk to whom — imports, placement, purity, isolation | `ark.config.json` layers + rules |
73
+ | **ArkRules** (opt-in) | Habits *inside* a layer — structure sensors + domain invariants as data | `arkRules` → `arkrules/<Layer>.json` |
74
+
75
+ Absence of ArkRules changes no inter-layer verdict. Label residual **`[Layer]`** vs **`[ArkRules]`**.
76
+ Details: [configuration](docs/configuration.md#arkrules-intra-layer-opt-in) · [use path](docs/use.md).
77
+
69
78
  **Not** a web framework, ORM, or job runner. Optional experimental runtime is separate and not required for the gate.
70
79
 
71
80
  **Name note:** npm package `arkgate` — not affiliated with the separate Archgate CLI project.
@@ -132,6 +141,7 @@ Setup per host: [docs/ai-gates.md](docs/ai-gates.md) · Develop path: [docs/deve
132
141
  | Contract agents can read (`ark://manifest`) | ✅ | ❌ |
133
142
  | Placement + preflight for multi-file changes | ✅ | ❌ |
134
143
  | Honest governed % + dual plan (edges vs shape) | ✅ | ❌ |
144
+ | Opt-in intra-layer ArkRules (structure + invariants) | ✅ | ❌ |
135
145
  | Incomplete analysis cannot look green | ✅ | varies |
136
146
 
137
147
  ---
@@ -176,7 +186,8 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
176
186
  | Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
177
187
  | Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
178
188
  | 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) |
189
+ | Latest prepared (4.0.0) | [docs/releases/4.0.0.md](docs/releases/4.0.0.md) · [CHANGELOG](CHANGELOG.md) |
190
+ | Last published (3.9.2) | [docs/releases/3.9.2.md](docs/releases/3.9.2.md) |
180
191
  | History / maintainer evidence | [docs/archive/](docs/archive/README.md) |
181
192
 
182
193
  ---
@@ -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 },