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.
- package/CHANGELOG.md +73 -0
- package/README.md +16 -5
- package/bin/ark-check-runtime.mjs +75 -3
- package/bin/ark-mcp-runtime.mjs +94 -0
- package/bin/lib/adapter-contract.mjs +14 -1
- package/bin/lib/analysis-engine.mjs +8 -8
- package/bin/lib/architecture-scan.mjs +35 -2
- package/bin/lib/arkrule-file-hints.mjs +71 -0
- package/bin/lib/arkrules-contract.mjs +382 -0
- package/bin/lib/arkrules-sensors.mjs +411 -0
- package/bin/lib/config-contract.mjs +85 -6
- package/bin/lib/doctor-advisories.mjs +14 -1
- package/bin/lib/doctor-plan.mjs +21 -0
- package/bin/lib/effective-contract-load.mjs +116 -0
- package/bin/lib/field-install.mjs +104 -0
- package/bin/lib/graph-blind.mjs +19 -0
- package/bin/lib/html-report-advisories.mjs +24 -0
- package/bin/lib/install-migrate.mjs +20 -2
- package/bin/lib/invariant-coverage-io.mjs +157 -0
- package/bin/lib/invariant-coverage.mjs +127 -0
- package/bin/lib/policy-delta-io.mjs +33 -0
- package/bin/lib/presets.mjs +241 -1
- package/bin/lib/remediation.mjs +28 -0
- package/bin/lib/resolved-candidate-facts.mjs +14 -1
- package/bin/lib/rules-inventory.mjs +144 -0
- package/bin/lib/rules-under-contract.mjs +66 -0
- package/bin/lib/start-preview.mjs +24 -7
- package/bin/lib/upgrade-command.mjs +48 -2
- package/dist/{configTypes-DAPvBqK6.d.ts → configTypes-CC0FEXoF.d.ts} +16 -3
- package/dist/eslint/index.cjs +2 -2
- package/dist/eslint/index.d.ts +1 -1
- package/dist/eslint/index.js +2 -2
- package/dist/index.cjs +14 -7
- package/dist/index.d.ts +615 -20
- package/dist/index.js +13 -6
- package/docs/README.md +4 -3
- package/docs/agent-guide.md +7 -3
- package/docs/ai-gates.md +6 -1
- package/docs/brownfield-adoption.md +20 -0
- package/docs/configuration.md +37 -4
- package/docs/develop.md +8 -2
- package/docs/enthusiast/README.md +11 -0
- package/docs/package-surface.md +13 -11
- package/docs/product-voice.md +9 -2
- package/docs/use.md +9 -0
- package/package.json +4 -17
- package/schemas/ark.analysis-result.schema.json +9 -1
- package/schemas/ark.arkrules.schema.json +141 -0
- package/schemas/ark.config.schema.json +10 -2
- package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
- package/server.json +2 -2
- package/templates/arkrules/ApplicationOrchestration.json +14 -0
- package/templates/arkrules/DomainModel.json +32 -0
- package/templates/arkrules/PersistenceAdapters.json +14 -0
- package/templates/arkrules/PresentationAdapters.json +14 -0
- package/templates/skills/ark-adopt.md +28 -1
- package/templates/skills/ark-architect.md +23 -0
- package/templates/skills/ark-autopilot.md +27 -1
- package/templates/skills/ark-contract.md +27 -1
- package/templates/skills/ark-coverage.md +23 -0
- package/templates/skills/ark-explain.md +23 -0
- package/templates/skills/ark-explore.md +26 -1
- package/templates/skills/ark-fix.md +23 -0
- package/templates/skills/ark-loop.md +23 -0
- package/templates/skills/ark-place.md +26 -0
- package/templates/skills/ark-runtime.md +4 -0
- package/templates/skills/ark-think.md +24 -1
- package/templates/skills/ark-upgrade.md +23 -0
- package/compat/nestjs.cjs +0 -2
- package/compat/nestjs.d.ts +0 -2
- package/compat/nestjs.js +0 -1
- package/compat/runtime.cjs +0 -2
- package/compat/runtime.d.ts +0 -2
- 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
|
|
20
|
-
>
|
|
21
|
-
>
|
|
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
|
|
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 {
|
|
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);
|
package/bin/ark-mcp-runtime.mjs
CHANGED
|
@@ -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
|
-
|
|
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 },
|