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.
- package/CHANGELOG.md +153 -0
- package/README.md +7 -5
- package/bin/ark-check-runtime.mjs +41 -91
- package/bin/ark-dashboard.mjs +56 -53
- package/bin/ark-layer-match.mjs +707 -121
- package/bin/ark-mcp-runtime.mjs +448 -169
- package/bin/ark-mcp.mjs +68 -4
- package/bin/ark-shared.mjs +42 -22
- package/bin/ark.mjs +65 -97
- package/bin/lib/adapter-contract-types.mjs +5 -0
- package/bin/lib/adapter-contract.mjs +15 -0
- package/bin/lib/adoption-stance.mjs +2 -2
- package/bin/lib/adr-path.mjs +3 -3
- package/bin/lib/agent-gates.mjs +0 -61
- package/bin/lib/agent-homes.mjs +12 -21
- package/bin/lib/agent-projection-command.mjs +9 -5
- package/bin/lib/ambient-state.mjs +9 -30
- package/bin/lib/analysis-engine.mjs +15 -11
- package/bin/lib/architecture-scan.mjs +61 -15
- package/bin/lib/ark-order-doctor.mjs +4 -29
- package/bin/lib/ark-order-facts.mjs +319 -17
- package/bin/lib/ark-order-sensors.mjs +28 -101
- package/bin/lib/ark-order-types.mjs +3 -2
- package/bin/lib/ark-run-doctor.mjs +17 -28
- package/bin/lib/ark-run-facts.mjs +461 -48
- package/bin/lib/ark-run-sensors.mjs +6 -4
- package/bin/lib/arkgate-pins.mjs +91 -0
- package/bin/lib/arkrule-file-hints.mjs +2 -47
- package/bin/lib/arkrules-contract.mjs +124 -133
- package/bin/lib/arkrules-sensors.mjs +55 -252
- package/bin/lib/check-args.mjs +85 -1
- package/bin/lib/ci-and-commands.mjs +24 -78
- package/bin/lib/ci-merge-boundary.mjs +1 -1
- package/bin/lib/class-source-scan.mjs +479 -0
- package/bin/lib/clone-detection.mjs +474 -0
- package/bin/lib/codex-home.mjs +5 -3
- package/bin/lib/config-contract-slices.mjs +739 -0
- package/bin/lib/config-contract.mjs +42 -607
- package/bin/lib/config-extras.mjs +16 -22
- package/bin/lib/config-warnings.mjs +32 -0
- package/bin/lib/contract-smells.mjs +33 -15
- package/bin/lib/core-ratchet.mjs +0 -2
- package/bin/lib/dashboard-cli.mjs +62 -0
- package/bin/lib/deep-module-coach.mjs +2 -4
- package/bin/lib/deploy-path.mjs +13 -38
- package/bin/lib/design-delta.mjs +1 -1
- package/bin/lib/design-smells.mjs +2 -2
- package/bin/lib/diagnostic-catalog.mjs +23 -9
- package/bin/lib/doctor-advisories.mjs +78 -8
- package/bin/lib/doctor-green-cite.mjs +1 -1
- package/bin/lib/doctor-human.mjs +1 -1
- package/bin/lib/doctor-next-actions.mjs +19 -4
- package/bin/lib/doctor-plan.mjs +9 -13
- package/bin/lib/duplication-io.mjs +572 -0
- package/bin/lib/effective-contract-load.mjs +282 -83
- package/bin/lib/enforcement-profiles.mjs +40 -4
- package/bin/lib/entry-points-io.mjs +424 -0
- package/bin/lib/extra-merge-teeth.mjs +36 -0
- package/bin/lib/field-install.mjs +38 -14
- package/bin/lib/first-run-help.mjs +10 -5
- package/bin/lib/flat-parent-importers.mjs +139 -0
- package/bin/lib/flat-parent-pilot.mjs +40 -100
- package/bin/lib/gate-files.mjs +58 -90
- package/bin/lib/git-change-scope.mjs +109 -0
- package/bin/lib/github-enforcement.mjs +166 -29
- package/bin/lib/golden-pattern.mjs +1 -1
- package/bin/lib/hook-templates.mjs +17 -15
- package/bin/lib/host-support-matrix.mjs +3 -1
- package/bin/lib/html-report-advisories.mjs +4 -0
- package/bin/lib/html-report-depth.mjs +2 -2
- package/bin/lib/html-report.mjs +2 -2
- package/bin/lib/import-graph-projection.mjs +112 -0
- package/bin/lib/import-resolve.mjs +13 -1
- package/bin/lib/improvement-compass-doctor.mjs +1 -24
- package/bin/lib/install-migrate.mjs +50 -73
- package/bin/lib/invariant-coverage-io.mjs +4 -3
- package/bin/lib/invariant-coverage.mjs +85 -11
- package/bin/lib/invariant-probe-cli.mjs +347 -0
- package/bin/lib/invariant-probe-io.mjs +165 -0
- package/bin/lib/invariant-probe-runner.mjs +268 -0
- package/bin/lib/invariant-probe-sites.mjs +174 -0
- package/bin/lib/invariant-probe-workspace.mjs +183 -0
- package/bin/lib/invariant-probe.mjs +461 -0
- package/bin/lib/invariant-tests-path.mjs +16 -22
- package/bin/lib/literal-path-drift.mjs +4 -2
- package/bin/lib/managed-upgrade.mjs +2 -8
- package/bin/lib/mcp-adoption.mjs +13 -6
- package/bin/lib/mcp-hook-payload.mjs +403 -63
- package/bin/lib/orphan-modules-io.mjs +458 -0
- package/bin/lib/orphan-modules.mjs +251 -0
- package/bin/lib/outside-importers.mjs +197 -0
- package/bin/lib/package-manager.mjs +60 -2
- package/bin/lib/parse-health.mjs +1 -1
- package/bin/lib/passthrough-commands.mjs +83 -0
- package/bin/lib/persistence-write-hint.mjs +106 -0
- package/bin/lib/pin-dependent-gates.mjs +117 -0
- package/bin/lib/policy-delta-io.mjs +219 -14
- package/bin/lib/post-green-path.mjs +1 -1
- package/bin/lib/prepare-change.mjs +115 -7
- package/bin/lib/prepare-write.mjs +2 -2
- package/bin/lib/presets.mjs +59 -73
- package/bin/lib/product-copy.mjs +1 -6
- package/bin/lib/project-root.mjs +31 -62
- package/bin/lib/remediation.mjs +23 -4
- package/bin/lib/resolved-candidate-facts.mjs +405 -117
- package/bin/lib/rules-inventory.mjs +22 -3
- package/bin/lib/rules-under-contract.mjs +247 -9
- package/bin/lib/scan-files.mjs +36 -4
- package/bin/lib/schema-validation.mjs +130 -0
- package/bin/lib/sensor-promote-cli.mjs +2 -11
- package/bin/lib/sensor-promote-io.mjs +33 -22
- package/bin/lib/sensor-promotion.mjs +1 -1
- package/bin/lib/skill-catalog-apply.mjs +1 -1
- package/bin/lib/skill-install.mjs +42 -38
- package/bin/lib/snippet-analysis.mjs +140 -6
- package/bin/lib/source-policy.mjs +34 -0
- package/bin/lib/start-install-recovery.mjs +72 -2
- package/bin/lib/start-preview.mjs +31 -1
- package/bin/lib/states-transitions-presence.mjs +3 -3
- package/bin/lib/status-command.mjs +35 -22
- package/bin/lib/status-manifest.mjs +50 -30
- package/bin/lib/status-transition-catalog.mjs +0 -6
- package/bin/lib/suggestions.mjs +3 -3
- package/bin/lib/team-parliament-io.mjs +76 -88
- package/bin/lib/team-parliament.mjs +21 -10
- package/bin/lib/upgrade-behavior-changes.mjs +2 -2
- package/bin/lib/upgrade-command.mjs +2 -24
- package/bin/lib/upgrade-outcome.mjs +1 -1
- package/bin/lib/upstream-report.mjs +21 -10
- package/bin/lib/violations.mjs +10 -9
- package/bin/lib/weakest-link.mjs +13 -0
- package/bin/lib/write-path-capabilities.mjs +6 -4
- package/dist/chunk-A5EIY3HI.cjs +5 -0
- package/dist/chunk-AHACEKSV.js +16 -0
- package/dist/chunk-SVSQVGFJ.js +5 -0
- package/dist/chunk-UN5EH4QV.cjs +16 -0
- package/dist/{configTypes-Bbf_MFGe.d.ts → configTypes-GPvvdleM.d.cts} +30 -4
- package/dist/configTypes-GPvvdleM.d.ts +257 -0
- package/dist/eslint/index.cjs +18 -5
- package/dist/eslint/index.d.cts +368 -0
- package/dist/eslint/index.d.ts +139 -156
- package/dist/eslint/index.js +11 -5
- package/dist/index.cjs +36 -35
- package/dist/index.d.cts +5547 -0
- package/dist/index.d.ts +3283 -198
- package/dist/index.js +36 -35
- package/dist/inspectorListen-6DQVJ4DC.js +1 -0
- package/dist/inspectorListen-KGMIQ2TF.cjs +1 -0
- package/dist/nestjs/index.cjs +1 -18
- package/dist/nestjs/index.d.cts +22 -0
- package/dist/nestjs/index.d.ts +1 -3
- package/dist/nestjs/index.js +1 -18
- package/dist/order/index.cjs +1 -1
- package/dist/order/index.d.cts +155 -0
- package/dist/order/index.d.ts +15 -3
- package/dist/order/index.js +1 -1
- package/dist/runtime/index.cjs +13 -30
- package/dist/{diagnosticCatalog-35mrwAHe.d.ts → runtime/index.d.cts} +828 -276
- package/dist/runtime/index.d.ts +2741 -133
- package/dist/runtime/index.js +13 -30
- package/dist/{types-Bdj32QwA.d.ts → types-CnauMQQL.d.cts} +607 -163
- package/dist/types-CnauMQQL.d.ts +1800 -0
- package/docs/README.md +3 -1
- package/docs/agent-guide.md +217 -16
- package/docs/ai-gates.md +174 -26
- package/docs/arkorder.md +32 -15
- package/docs/brownfield-adoption.md +5 -1
- package/docs/configuration.md +230 -31
- package/docs/develop.md +2 -1
- package/docs/diagnostics.md +212 -19
- package/docs/package-surface.md +35 -9
- package/docs/product-voice.md +7 -1
- package/package.json +69 -19
- package/schemas/ark.analysis-result.schema.json +15 -0
- package/schemas/ark.config.schema.json +77 -12
- package/schemas/ark.invariant-probe.schema.json +368 -0
- package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
- package/schemas/ark.status-manifest.schema.json +22 -0
- package/server.json +3 -9
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +5 -1
- package/templates/agent-skills/ark-contract/SKILL.md +17 -0
- package/templates/agent-skills/ark-coverage/SKILL.md +2 -0
- package/templates/agent-skills/ark-explore/SKILL.md +7 -0
- package/templates/agent-skills/ark-fix/SKILL.md +20 -0
- package/templates/agent-skills/ark-order/SKILL.md +4 -2
- package/templates/agent-skills/ark-place/SKILL.md +8 -0
- package/templates/agent-skills/ark-runtime/SKILL.md +6 -1
- package/templates/arkrules/PersistenceAdapters.json +1 -1
- package/templates/arkrules/PresentationAdapters.json +1 -1
- package/templates/skills/ark-adopt.md +5 -1
- package/templates/skills/ark-contract.md +17 -0
- package/templates/skills/ark-coverage.md +2 -0
- package/templates/skills/ark-explore.md +7 -0
- package/templates/skills/ark-fix.md +20 -0
- package/templates/skills/ark-order.md +4 -2
- package/templates/skills/ark-place.md +8 -0
- package/templates/skills/ark-runtime.md +6 -1
- package/bin/lib/ark-order-error.mjs +0 -18
- package/bin/lib/ark-order-invariants.mjs +0 -346
- package/dist/types-Ik_OakcI.d.ts +0 -183
package/docs/README.md
CHANGED
|
@@ -73,7 +73,9 @@ These are **not** the day-to-day product path. They stay in the repo for evidenc
|
|
|
73
73
|
| Field adoption kit (scaffolding, not closed) | [field/](field/) |
|
|
74
74
|
| Runtime hardening (experimental) | [production-hardening.md](production-hardening.md) |
|
|
75
75
|
|
|
76
|
-
Current release: [CHANGELOG](../CHANGELOG.md) (`arkgate@4.8.
|
|
76
|
+
Current release: [CHANGELOG](../CHANGELOG.md) (`arkgate@4.8.25`; confirm npm with `npm view arkgate@4.8.25 version`; does not close `K01`).
|
|
77
|
+
Prior published: [CHANGELOG](../CHANGELOG.md) (`arkgate@4.8.24`).
|
|
78
|
+
Prior published: [CHANGELOG](../CHANGELOG.md) (`arkgate@4.8.23`).
|
|
77
79
|
Prior published: [CHANGELOG](../CHANGELOG.md) (`arkgate@4.8.22`).
|
|
78
80
|
Prior published: [CHANGELOG](../CHANGELOG.md) (`arkgate@4.8.21`).
|
|
79
81
|
Prior published: [CHANGELOG](../CHANGELOG.md) (`arkgate@4.8.20`). Plain-English history: [arkgate.online/changelog](https://www.arkgate.online/changelog/).
|
package/docs/agent-guide.md
CHANGED
|
@@ -41,7 +41,7 @@ the project API is unusable. See the distribution and completeness boundary in
|
|
|
41
41
|
ark start → ark start --apply → ark-check --doctor
|
|
42
42
|
day to day: compact router / MCP place + validate + check
|
|
43
43
|
guided work: install skill pack → /ark-autopilot
|
|
44
|
-
optional: arkRules map + arkrules/*.json (intra-layer; starts advisory)
|
|
44
|
+
optional: arkRules map (one path or a list) + arkrules/*.json, or childSlices.arkRulesFile at a slice root (intra-layer; starts advisory)
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
`arkgate-check --doctor` shows what's wrong and what to do first. From **4.0.0**, doctor may
|
|
@@ -207,6 +207,16 @@ npx ark status --json
|
|
|
207
207
|
npx ark status --json --expected-root /abs/project/root
|
|
208
208
|
```
|
|
209
209
|
|
|
210
|
+
`--config <file>` reads a non-default contract (same for `ark agents-md --config <file>`).
|
|
211
|
+
When the resolved config fails the shared contract validator, status adds
|
|
212
|
+
`contract: { valid: false, errors: [...] }` and the next action is `fix-config` — the same
|
|
213
|
+
config every enforcing surface refuses is never reported as ready. `ark-check --json` (and
|
|
214
|
+
`--doctor --json`) print `{ "ok": false, "error": "CONFIG_INVALID", "configPath", "messages" }`
|
|
215
|
+
on stdout for that case (exit 2), so a JSON consumer always gets the reason. Any other fatal
|
|
216
|
+
error under `--json` prints `{ "ok": false, "error": "POLICY_BASE_UNREADABLE" | "CHECK_ERROR", "message" }`
|
|
217
|
+
(also exit 2); `POLICY_BASE_UNREADABLE` means the policy base ref (for example `GITHUB_BASE_REF`)
|
|
218
|
+
could not be read in this checkout — fetch it or pass `--base <ref>`.
|
|
219
|
+
|
|
210
220
|
MCP parity tool: **`ark_status`** (same envelope; pass `project.expectedRoot` after `ark_identity`).
|
|
211
221
|
Schema: `arkgate/schema/status-manifest`. Never prompts; under `CI=1` JSON is forced. **Not a
|
|
212
222
|
score** — counts, honesty modes, and residual ids only. Write-path interpretation of activation vs
|
|
@@ -312,14 +322,35 @@ npx ark-check --ratchet-cores # require populated core layers
|
|
|
312
322
|
npx ark-check --watch # debounced re-check when governed files change
|
|
313
323
|
```
|
|
314
324
|
|
|
325
|
+
**Package pin:** `ark start` and `ark init` both pin `arkgate` in `package.json`
|
|
326
|
+
`devDependencies` (and install it unless `--skip-package-manager`) before writing the CI workflow
|
|
327
|
+
and host hooks, because those call the local `ark-check` / `arkgate-mcp` bins. `--no-install`
|
|
328
|
+
skips the pin; without a `package.json` setup prints a warning instead. When arkgate is not a
|
|
329
|
+
local dependency (no pin, no `node_modules/arkgate`), every generated command runs
|
|
330
|
+
`npx -y -p arkgate@<exact version> <bin>` instead of a bare `npx ark-check`, which npm would
|
|
331
|
+
resolve as a nonexistent `ark-check` package (404) or a stale global. `--doctor` leads with
|
|
332
|
+
`PACKAGE_PIN_ABSENT` when an existing generated file still uses the local runner without the pin.
|
|
333
|
+
`start --remove-host`
|
|
334
|
+
never runs the package manager. An explicit `--archetype` / `--preset` on `ark start` is the
|
|
335
|
+
contract that is written (not only a way past the shape-confidence gate), and re-running
|
|
336
|
+
`ark start --apply` on a project that already has `ark.config.json` keeps that contract and is not
|
|
337
|
+
re-gated on detected shape confidence (so the `--remove-host` restore command works).
|
|
338
|
+
The generated `typecheck` script and CI step are added only when `typescript` is a dependency
|
|
339
|
+
(or resolves from the project root); otherwise setup prints a note.
|
|
340
|
+
|
|
315
341
|
**Day-zero origin (2.12+):** `ark init` freezes `.ark/reports/origin.*` before writing agent
|
|
316
|
-
docs or CI templates. Compact `ark start` previews first and keeps the applied setup small
|
|
342
|
+
docs or CI templates (the day-zero HTML is written under `.ark/reports/`, not the project root). Compact `ark start` previews first and keeps the applied setup small
|
|
317
343
|
(budget: 8 files / 32 KB including project `.mcp.json`);
|
|
318
344
|
run `ark-check --report ark-report.html` explicitly when you want to establish an origin/evolution
|
|
319
345
|
baseline. Do not `--reset-origin` unless the user explicitly wants a new baseline.
|
|
320
346
|
`ark-check --report --no-archive` still creates `origin.*` on the first report (or on an explicit
|
|
321
347
|
reset) and refreshes `latest.*`; it skips only the timestamped JSON file under
|
|
322
348
|
`.ark/reports/history/`.
|
|
349
|
+
`--report` applies the committed `.ark-baseline.json` by default (the same freeze `--doctor`
|
|
350
|
+
reads), so `latest.json` and `ark status` count frozen debt as frozen, not active. Under
|
|
351
|
+
`--strict` / `--strict-merge` / `--contract-diff` the implicit freeze is not applied (pass
|
|
352
|
+
`--baseline` explicitly), so `--report` never changes a merge verdict. Only
|
|
353
|
+
`--report` refreshes that snapshot; a plain check or `--doctor` does not.
|
|
323
354
|
Snapshots record best-effort, shell-free Git provenance (`HEAD`, attached branch, and dirty
|
|
324
355
|
worktree state). Evolution keeps raw metrics visible across ArkGate upgrades, but an Ark score
|
|
325
356
|
delta is comparable—and therefore rendered—only when origin and current snapshots use the same
|
|
@@ -590,6 +621,9 @@ block a promotion, and the surface names which one fired:
|
|
|
590
621
|
- **`no-coverage-evidence`** — an invariant whose evidence does not support
|
|
591
622
|
promotion. The text is `canPromoteInvariant`'s own, so this surface can never
|
|
592
623
|
promise a promotion the gate then refuses.
|
|
624
|
+
- **`probe-survived`** — the committed invariant probe shows the covering tests
|
|
625
|
+
still pass with the rule broken, or never call it. Same judge; named apart
|
|
626
|
+
because the fix is a stronger test. See [Probe an invariant](#probe-an-invariant).
|
|
593
627
|
|
|
594
628
|
It needs no TypeScript and runs no analysis: the contract, the ArkRules
|
|
595
629
|
documents it points at, and the coverage evidence walk (a filesystem walk plus a
|
|
@@ -625,7 +659,9 @@ Plan by default — there is no `--dry-run` anywhere in `bin/`. `--apply` needs
|
|
|
625
659
|
one named rule id: `--promote <ruleId> --apply`, or `--promote=<ruleId>` when
|
|
626
660
|
the id starts with `-`. It refuses a bare `--promote --apply` rather than
|
|
627
661
|
rewriting the contract in bulk behind a single flag, refuses an id declared in
|
|
628
|
-
two documents rather than silently writing the first
|
|
662
|
+
two documents rather than silently writing the first (starter ids are unique:
|
|
663
|
+
`thin-persistence-adapter` / `thin-presentation-adapter`, and a layer that clones
|
|
664
|
+
another layer's archetype gets its ids prefixed with its kebab-cased name), and refuses to make a
|
|
629
665
|
contract change on a cost this run did not measure. `--promote` cannot be
|
|
630
666
|
combined with a mode that answers first (`--sensors`, `--coverage`, `--plan`,
|
|
631
667
|
`--doctor`, …) — that printed the report and exited 0 having written nothing —
|
|
@@ -642,6 +678,63 @@ than a targeted text edit. Exit 0 for a preview or a successful write, 1 for an
|
|
|
642
678
|
unknown rule id or a refused write, 2 for bad arguments or ArkRules references
|
|
643
679
|
that will not load.
|
|
644
680
|
|
|
681
|
+
### Probe an invariant
|
|
682
|
+
|
|
683
|
+
A test title that names an invariant counts as coverage. That proves the test
|
|
684
|
+
exists, not that it would fail if the rule broke. The probe checks that, on
|
|
685
|
+
request:
|
|
686
|
+
|
|
687
|
+
```bash
|
|
688
|
+
npx arkgate-check --probe-invariants # every invariant with a coverage.symbol
|
|
689
|
+
npx arkgate-check --probe-invariants=INV-REFUND-WINDOW
|
|
690
|
+
npx arkgate-check --probe-invariants --write # save .ark/invariant-probe.json
|
|
691
|
+
npx arkgate-check --probe-invariants --runner vitest # only when the runner is unclear
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
```text
|
|
695
|
+
INV-REFUND-WINDOW: the tests still pass when the guard is negated (src/domain/refunds/refundPolicy.ts:14).
|
|
696
|
+
The test names the rule but does not pin it. Next: add a case that fails when the rule is broken, then re-run with --write.
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
For each invariant with a `coverage.symbol`, it copies the project to a
|
|
700
|
+
temporary folder, then runs only the covering tests (titles that name the id,
|
|
701
|
+
and tests that import the symbol's file), with your own runner (vitest, jest or
|
|
702
|
+
`node --test`):
|
|
703
|
+
|
|
704
|
+
1. unchanged — they must pass;
|
|
705
|
+
2. with the file throwing on load — a test must fail, or the copy is not the one
|
|
706
|
+
the tests load (`inconclusive`);
|
|
707
|
+
3. with the symbol throwing when called — a test must fail, or the tests never
|
|
708
|
+
call it (`not-reached`);
|
|
709
|
+
4. with at most three small changes inside the symbol (negate a guard, drop a
|
|
710
|
+
`throw`, flip or shift a comparison, shift a constant) — each must make a test
|
|
711
|
+
fail, or the change went unnoticed (`survived`).
|
|
712
|
+
|
|
713
|
+
Every row reads `killed`, `survived`, `not-reached`, `inconclusive` or
|
|
714
|
+
`unprobeable` (no symbol, a type or interface, no covering test — a declaration
|
|
715
|
+
is not a test). It is not a score.
|
|
716
|
+
|
|
717
|
+
**Your files are never changed.** Every change happens in the copy, which is
|
|
718
|
+
removed when the run ends, including on Ctrl-C. `--write` adds only
|
|
719
|
+
`.ark/invariant-probe.json`. Exit 0 when nothing survived or went unreached,
|
|
720
|
+
1 when something did, 2 when it could not run (`reasonCode` in the JSON).
|
|
721
|
+
|
|
722
|
+
**What the result can do.** Only refuse. When the committed file has a fresh
|
|
723
|
+
`survived` or `not-reached` row, `--promote` and the policy delta refuse to
|
|
724
|
+
promote that invariant to `enforced` (blocker `probe-survived`). Fresh means
|
|
725
|
+
the symbol file, every covering test and the invariant are unchanged since the
|
|
726
|
+
run; anything else is stale and changes nothing, like `killed`, `inconclusive`
|
|
727
|
+
or no file at all. Status and `--rules-inventory` show the state per invariant
|
|
728
|
+
(`INVARIANT_PROBE_SURVIVED` when it refuses). CI sees the refusal only if you
|
|
729
|
+
commit the file.
|
|
730
|
+
|
|
731
|
+
**Trust.** Running the probe runs your tests, exactly like `npm test`; you start
|
|
732
|
+
it, nothing else does. It never runs from the write hook, MCP, ESLint, the
|
|
733
|
+
GitHub Action or `--strict-merge`, and refuses to combine with them. The runner
|
|
734
|
+
gets a short list of environment variables (no tokens, proxies pointed at a
|
|
735
|
+
dead port, `HOME` and the temp folder inside the copy). That is best effort,
|
|
736
|
+
not a sandbox.
|
|
737
|
+
|
|
645
738
|
### Literal path drift after a rename (`--path-drift`)
|
|
646
739
|
|
|
647
740
|
A repo path written inside a **string, a comment or a docstring** is invisible to
|
|
@@ -714,6 +807,78 @@ TS/TSX gate the type-aware passes use, because a comment is not code and the
|
|
|
714
807
|
class was first found in a `.css` file. Generated files are skipped, and every
|
|
715
808
|
file the walk refuses is counted by reason in the output.
|
|
716
809
|
|
|
810
|
+
### Files nothing imports
|
|
811
|
+
|
|
812
|
+
A rewrite often lands next to the old file, and the old one stays. Nothing
|
|
813
|
+
imports it. Status reads that from the import edges the check already resolved,
|
|
814
|
+
so it costs no second analysis and never changes the check.
|
|
815
|
+
|
|
816
|
+
```bash
|
|
817
|
+
npx arkgate-check --doctor # one dim count line at most
|
|
818
|
+
npx arkgate-check --doctor --all # the list, plus exports nothing imports by name
|
|
819
|
+
npx arkgate-check --doctor --json # doctor.orphanModules
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
```text
|
|
823
|
+
Nothing imports src/lib/legacy-pricing.ts, and no entry point covers it.
|
|
824
|
+
Next: Delete it through the write gate, or add it to .ark/entry-points.json if a framework loads it.
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
A file is listed only when no governed file, no test, no project script and no
|
|
828
|
+
known entry point imports it. Entry points come from `package.json` (`main`,
|
|
829
|
+
`exports`, `bin`, scripts — built `dist/` paths map back to source), CI workflow
|
|
830
|
+
run steps, framework conventions (Next routes and `middleware`, Vite
|
|
831
|
+
`index.html`, Nest `main`, Vercel `api/`, Storybook stories), the arkRun /
|
|
832
|
+
arkOrder roots in `ark.config.json`, config and setup files, ambient declaration
|
|
833
|
+
files, and `.ark/entry-points.json`. A file only tests import is counted, not
|
|
834
|
+
listed.
|
|
835
|
+
|
|
836
|
+
When a dynamic import, `import.meta.glob`, `new URL(…, import.meta.url)`, a
|
|
837
|
+
quoted path, or an import that did not resolve could still reach a file, the
|
|
838
|
+
item says so (`maybe-dynamic`, `maybe-unresolved`) and the section is
|
|
839
|
+
`partial`. Read those before deleting. A framework or loader ArkGate does not
|
|
840
|
+
know goes in the sidecar, with a reason and an optional review date:
|
|
841
|
+
|
|
842
|
+
```json
|
|
843
|
+
{
|
|
844
|
+
"schemaVersion": "1",
|
|
845
|
+
"entryPoints": [
|
|
846
|
+
{ "glob": "src/plugins/**", "reason": "loaded by name from the plugin manifest", "reviewBy": "2027-01-01" }
|
|
847
|
+
]
|
|
848
|
+
}
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
Delete one file at a time through the write gate (`/ark-fix`), then run status
|
|
852
|
+
again. Never delete a `maybe-*` item without reading what may load it.
|
|
853
|
+
|
|
854
|
+
### Copies across a wall
|
|
855
|
+
|
|
856
|
+
A slice wall denies the import. The cheapest way around it is to copy the code,
|
|
857
|
+
and a copy is not an import, so the check cannot see it. Status details can.
|
|
858
|
+
|
|
859
|
+
```bash
|
|
860
|
+
npx arkgate-check --doctor --all # the list, with a place the shared code could live
|
|
861
|
+
npx arkgate-check --doctor --all --json # doctor.crossWallDuplication
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
```text
|
|
865
|
+
This code is copied between src/features/billing and src/features/invoices (34 of 35 names match).
|
|
866
|
+
The wall stops the import, not the copy. Next: move it to src/shared/ with /ark-place.
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
A copy is listed only when its two files sit on two sides of a wall the check
|
|
870
|
+
would enforce — two slices, two child slices of one universe — or in two
|
|
871
|
+
layers. Copies inside one slice are counted, never listed: the slice may
|
|
872
|
+
import its own code. The suggested home comes from your config: the declared
|
|
873
|
+
`sharedRoots`, the universe common folder (`childSlices.commonFolders`), or the
|
|
874
|
+
lower layer both sides may import. When none fits, it says `/ark-place`.
|
|
875
|
+
|
|
876
|
+
Compact status never runs this; its JSON says `not-run` and names the command.
|
|
877
|
+
Tests, `.d.ts`, generated files and files over 256 KB are never read for it. The
|
|
878
|
+
thresholds are fixed (at least 50 tokens and 5 lines, and most names matching);
|
|
879
|
+
there is no config key. Move one copy at a time through the write gate
|
|
880
|
+
(`/ark-fix`), import it from both sides, then run status again.
|
|
881
|
+
|
|
717
882
|
### Presets
|
|
718
883
|
|
|
719
884
|
- `hexagonal` / `layered` / `feature-sliced` / `monorepo` / **`ui-surface`** (UI/Vite/Remotion-style) / **`vertical-slice`** (features/* + peerIsolation) / **`ddd-bounded-contexts`** (contexts/*/domain|application|infra + shared kernel)
|
|
@@ -1005,6 +1170,13 @@ a `Domain.*` event — the publish throws `ObservedLayerFlowViolationError` befo
|
|
|
1005
1170
|
reaches history, outbox, or subscribers. Use `'soft'` to record `layer.observedViolation`
|
|
1006
1171
|
trace/audit records without blocking, or `'off'` to disable. Agents should name the event's
|
|
1007
1172
|
`source` honestly: it is checked against the layer matrix, not just the intent name.
|
|
1173
|
+
Kernels built from `ark.config.json` map intents to layers through `layers[].intentPrefixes`;
|
|
1174
|
+
canonical layer names (`DomainModel`, `ApplicationOrchestration`, … as `ark init` writes them)
|
|
1175
|
+
without prefixes get the built-in ones. A custom-named deny-rule layer with no prefixes is
|
|
1176
|
+
recorded once as a `layer.observedFlowUnresolvable` audit record
|
|
1177
|
+
(`ARKRUN_LAYER_FLOW_UNRESOLVABLE`) and listed by `ark doctor`; pass
|
|
1178
|
+
`enforceObservedLayerFlow: 'hard'` explicitly to make it throw `ArkKernelConfigError`.
|
|
1179
|
+
`peerIsolation` slice walls are not evaluated at runtime (names cannot place a slice).
|
|
1008
1180
|
|
|
1009
1181
|
Strict kernels also require published events to have a registered source intent
|
|
1010
1182
|
and a matching event contract:
|
|
@@ -1150,9 +1322,9 @@ edges are not denied by that rule.
|
|
|
1150
1322
|
- **Denied:** `src/features/auth/**` → `src/features/payments/**` (different slice id).
|
|
1151
1323
|
- **Allowed:** same-slice imports when both paths classify; classic non-peerIsolation denies still apply across layers.
|
|
1152
1324
|
- **`sliceFolders`:** optional parent segments (default: inferred from layer globs). A bare name stays an unanchored one-segment match (`features` on `src/features/auth` is `features/auth`). A starred anchored pattern with `sliceIdentity` `path` (the default) includes the literal prefix plus the star bindings (`lib/features/*/*` → `lib/features/projects/rfi`), so parallel trees get different ids.
|
|
1153
|
-
- **`sliceIdentity`:** `path` (default, also when the key is absent) or `stars`. `path` keeps today's slice ids. `stars` names a starred prefix as the last literal plus
|
|
1325
|
+
- **`sliceIdentity`:** `path` (default, also when the key is absent) or `stars`. `path` keeps today's slice ids. `stars` names a starred prefix as the last literal plus every star binding (`lib/features/*/*` and `lib/repositories/features/*/*` both yield `features/projects/rfi`; `modules/*/api/*` keeps the module: `orders/api/v1`). Doctor names both prefixes when they collapse onto one id. Additive — no migration.
|
|
1154
1326
|
- **Fail-closed:** missing paths, empty/unresolvable slice folders, or unclassifiable either side → **deny** via peerIsolation (cannot prove same-slice).
|
|
1155
|
-
- **`childSlices`** (optional): a second wall under the same rule. The universe wall runs first and is unchanged when the block is absent. With it present, a cross-universe deny is `reasonId` `CROSS_PARENT_SLICE` (never advisory). Inside one universe, `commonFolders`
|
|
1327
|
+
- **`childSlices`** (optional): a second wall under the same rule. The universe wall runs first and is unchanged when the block is absent. With it present, a cross-universe deny is `reasonId` `CROSS_PARENT_SLICE` (never advisory). Needs `peerIsolation: true` and `allowed: false` on the rule. Inside one universe, flat files and a `commonFolders` directory directly under the universe are common (the same folder name inside a child belongs to the child); a child may import them; common may import a child only when `parentMayImportChild` is true. Sibling crossings are `CROSS_SIBLING_SLICE` — error when `siblings` is `deny` (the default), warning when `advisory`. With a baseline, a new advisory crossing fails only when the baseline already records an advisory crossing of that rule, or with `siblings.ratchet: true`; `ratchet: false` measures only. `childSlices.message` is the inner-wall text; the rule `message` stays the universe-wall text. `ruleId` stays `LAYER_IMPORT_VIOLATION`; adapter diagnostics carry `evidence.reasonId`. arkgate 4.8.22 and older reject the key; `CONFIG_CHILD_SLICES_VERSION` fires only when the repo pins such a version (package.json, hook, CI), at that file.
|
|
1156
1328
|
- **`sharedRoots`** (4.8.4): roots the repo declares shared on purpose (`["ui", "hooks", "lib/permissions"]`). A file under a declared shared root is evidence, not an unclassifiable path, so fail-closed stops firing on every shared file. **Anchored** — the root starts the path, optionally after one `src/` or `app/`; write deeper or monorepo roots out (`packages/web/src/ui`) or glob them, and a bare `*` / `**` is refused. A path that still resolves to a slice keeps its slice.
|
|
1157
1329
|
- **`allowedCrossSlice`** (4.8.4): `[{ "from": "features/checkout", "to": "features/catalog" }]` — one directed slice→slice edge the repo declares on purpose. The reverse still denies.
|
|
1158
1330
|
- **The denial names its reason:** `cross-slice edge a → b` (a fact about the code) vs `unclassifiable path (…)`, `no slice folders`, `no path evidence` (facts about the evidence ArkGate had).
|
|
@@ -1241,8 +1413,14 @@ imports/exports; resolved CLI/preflight is authoritative outside that envelope),
|
|
|
1241
1413
|
`ark/no-denied-capabilities` (per-layer capability deny sets),
|
|
1242
1414
|
`ark/no-arkrun-kernel-in-domain` / `ark/no-arkrun-direct-new` /
|
|
1243
1415
|
`ark/no-arkrun-transport-bypass` (ArkRun extra; silent when absent; import / `new`
|
|
1244
|
-
envelope only), `ark/no-
|
|
1245
|
-
`ark/
|
|
1416
|
+
envelope only), `ark/no-arkorder-kernel-in-domain` / `ark/no-arkorder-generic-update`
|
|
1417
|
+
(ArkOrder extra), `ark/arkrules-structure` (ArkRules structure sensors, file-local),
|
|
1418
|
+
`ark/no-raw-event-publish`, and `ark/require-publish-source`. Findings ark-check reports as
|
|
1419
|
+
warnings (type-only placement debt, advisory slice walls, advisory extras) report on the
|
|
1420
|
+
warn-level `ark/architecture-advisory`, so ESLint errors only where ark-check fails (a
|
|
1421
|
+
config without that rule gets them on the blocking rule id, tagged advisory). An
|
|
1422
|
+
invalid contract is one `configInvalid` error per file, not a crash. See
|
|
1423
|
+
[ai-gates.md](ai-gates.md#eslint-editor-feedback--bounded-parity-envelope).
|
|
1246
1424
|
|
|
1247
1425
|
## Runtime Observability
|
|
1248
1426
|
|
|
@@ -1324,7 +1502,10 @@ not replace your web framework, HTTP clients, or job scheduler.
|
|
|
1324
1502
|
The strongest place to constrain an AI agent is the moment it writes a file, not after.
|
|
1325
1503
|
`arkgate-mcp` / `ark-mcp` exposes ArkGate over MCP (JSON-RPC over stdio; it prefers a usable
|
|
1326
1504
|
project TypeScript API, then exact `typescript-ark-host@6.0.3`) so a host can gate
|
|
1327
|
-
the write path
|
|
1505
|
+
the write path. `arkgate mcp` / `ark mcp` starts the same server; that is the form
|
|
1506
|
+
`npx arkgate@<version> mcp --root .` and the MCP Registry use, since npx runs the bin named
|
|
1507
|
+
after the package. Without `--config` the server reads `ark.config.json` when it exists and still
|
|
1508
|
+
starts in a fresh project; an explicit `--config` that points at a missing file exits 1:
|
|
1328
1509
|
|
|
1329
1510
|
For a complete multi-file candidate, use `ark preflight --changes changes.json --json` or MCP
|
|
1330
1511
|
`ark_prepare_change`. Add `--change-map map.json` (or MCP `changeMap`) only for an explicit schema
|
|
@@ -1370,13 +1551,13 @@ The server exposes these thirteen tools. Every tool accepts the additive
|
|
|
1370
1551
|
|------|---------------------------|
|
|
1371
1552
|
| `ark_identity` | `{ project: { expectedRoot, expectedProjectId? } }`: return the canonical root/config, stable project id, contract identity, and live runtime identity; use it before every other project-bound surface. |
|
|
1372
1553
|
| `ark_manifest` | No non-project args: return the machine-readable architecture contract with an authoritative binding after the identity handshake. |
|
|
1373
|
-
| `validate_code` | `{ source, layer?, filePath? }`:
|
|
1554
|
+
| `validate_code` | `{ source, layer?, filePath? }`: single-file lexical check (layer plane plus the enforced ArkRun / ArkRules structure / ArkOrder sensors CI runs on that file); infer the layer from `filePath` when possible. Always partial: `valid:false` / `isError:true` until complete-candidate preflight — read `lexicalValid` for the one-file verdict. Not a hook; hard blocking is `arkgate-mcp --hook`. |
|
|
1374
1555
|
| `ark_check` | `{ strict?, baseline? }`: run the full project architecture check. `verdict` separates `identity`, `completeness`, `graph`, `coverage`, `gates`, and `overallOk`; no individual green fact substitutes for the combined verdict. |
|
|
1375
|
-
| `ark_policy_delta` | `{ baseConfig, candidateConfig?, acknowledgement? }`: classify a complete contract transition; never edits the contract. Weakening / new layer / new allow edge needs `adrPath` on the acknowledgement. |
|
|
1556
|
+
| `ark_policy_delta` | `{ baseConfig, candidateConfig?, baseArkRuleFiles?, candidateArkRuleFiles?, acknowledgement? }`: classify a complete contract transition, ArkRules included; never edits the contract. `baseArkRuleFiles` (`{ "<path from baseConfig.arkRules>": <file JSON> }`) is required when `baseConfig` maps `arkRules`. Include each `childSlices.arkRulesFile` path in that map when the wall discovers one. Weakening / new layer / new allow edge needs `adrPath` on the acknowledgement. |
|
|
1376
1557
|
| `ark_coverage` | No args: report per-layer counts, every unclassified file, unmatched layers, and missing rule edges. |
|
|
1377
|
-
| `ark_place` | `{ filePath
|
|
1378
|
-
| `ark_prepare_write` | `{ source, filePath
|
|
1379
|
-
| `ark_prepare_change` | `{ changes, changeMap? }`: preflight one complete create/update/delete batch in memory; never writes files. |
|
|
1558
|
+
| `ark_place` | `{ filePath, description? }`: resolve the governed home for `filePath` (fail-closed without it; never invents a path) and return its import/global constraints. |
|
|
1559
|
+
| `ark_prepare_write` | `{ source, filePath, description?, layer? }`: compose placement and snippet validation, with hashes and a mechanical-safe patch when available. `filePath` is required (fail-closed). Partial like `validate_code`: read `lexicalValid`. |
|
|
1560
|
+
| `ark_prepare_change` | `{ changes, changeMap? }`: preflight one complete create/update/delete batch in memory against the same Effective Contract as `ark-check` (ArkRules included: same `policyHash`, structure/invariant findings; a missing referenced ArkRules file fails closed); ArkRules-plane findings the base tree already has are returned as `preExisting: true` warnings and do not block, so baselined brownfield debt does not reject an unrelated batch; never writes files. |
|
|
1380
1561
|
| `ark_recommend` | No args: return the deterministic application-shape plan used by `ark-check --recommend --json`. |
|
|
1381
1562
|
| `ark_suggest_include` | No args: propose TypeScript/JavaScript include roots from workspaces and nested packages. |
|
|
1382
1563
|
| `ark_rules_inventory` | No args: inventory possible intra-layer rules using configured layer evidence when available; test/fixture/seed/migration surfaces and narrow technical constants are excluded from extraction pilots. Counts are not a score. |
|
|
@@ -1406,7 +1587,13 @@ Every project-bound tool success, tool error, and JSON-RPC error data carries:
|
|
|
1406
1587
|
```
|
|
1407
1588
|
|
|
1408
1589
|
`projectId` stays stable across process restarts and contract edits; `runtimeId` and
|
|
1409
|
-
`processStartedAt` identify this live process.
|
|
1590
|
+
`processStartedAt` identify this live process. The contract (`ark.config.json`, the project
|
|
1591
|
+
manifest, referenced ArkRules files) is loaded once at startup: after any of them changes on
|
|
1592
|
+
disk, every project tool except `ark_identity` returns `CONTRACT_STALE` (`isError`,
|
|
1593
|
+
`authoritative: false`, `contractStale: true`) until the MCP server restarts — the process never
|
|
1594
|
+
answers authoritatively from an old contract. `ark_identity` keeps answering with
|
|
1595
|
+
`contractStale: true` so the host can diagnose it; the project-local CLI reads the current
|
|
1596
|
+
contract meanwhile. Binding states are:
|
|
1410
1597
|
|
|
1411
1598
|
- `matched` — canonical `expectedRoot` is the exact project root, or it is a contained
|
|
1412
1599
|
descendant and the caller also supplied the matching project id; `authoritative` is `true`;
|
|
@@ -1513,8 +1700,22 @@ doctor → compact router (and `/ark-autopilot` only after the skill pack).
|
|
|
1513
1700
|
| `GET /workflows` | Workflows monitor: counts + `workflows[]` summaries (`id`, `name`, `status`, optional `currentStep` / `error`) |
|
|
1514
1701
|
|
|
1515
1702
|
Dual package bins **`ark-dashboard`** and **`arkgate-dashboard`**
|
|
1516
|
-
(`bin/ark-dashboard.mjs`) poll `--url`
|
|
1517
|
-
|
|
1703
|
+
(`bin/ark-dashboard.mjs`) poll `--url` and sibling `/outbox` + `/workflows` on an
|
|
1704
|
+
interval (`--interval` / `--timeout`, clamped to 200–60000 ms). `--url` accepts the
|
|
1705
|
+
inspector root (`handle.url`) or `handle.snapshotUrl`. `startInspector()` binds a
|
|
1706
|
+
**random port** unless you pass `{ port }`, so pass the URL your app printed (or set
|
|
1707
|
+
`ARK_DASHBOARD_URL`); the fallback `http://127.0.0.1:3000/snapshot` only matches
|
|
1708
|
+
`startInspector({ port: 3000 })`, and the dashboard prints that hint when it cannot
|
|
1709
|
+
connect. `--once` renders one frame and exits (non-zero when unreachable);
|
|
1710
|
+
`--help` / `--version` exit 0; unknown flags exit 2 with one line.
|
|
1711
|
+
|
|
1712
|
+
The default in-memory event buffer (`InMemoryEventBuffer`) is capped at
|
|
1713
|
+
`maxHistorySize` (default 1000) and its records are marked `dispatched` once the kernel
|
|
1714
|
+
finishes local delivery, so `/outbox` shows real backlog only. Broker sends are marked
|
|
1715
|
+
`dispatched` / `failed` from the handoff outcome, and a failed handoff (awaited or
|
|
1716
|
+
fire-and-forget) records an `event.handoffFailed` trace + audit entry. An injected
|
|
1717
|
+
`eventBuffer` stays relay-owned: records remain `pending` for your relay's `claim` /
|
|
1718
|
+
`markDispatched` (except broker handoffs the kernel performs itself).
|
|
1518
1719
|
ANSI escape sequences + polling only — no React, Ink, or Blessed. Use
|
|
1519
1720
|
`ark dashboard` / `arkgate dashboard` (passthrough to `bin/ark-dashboard.mjs`) or the
|
|
1520
1721
|
dual bins `ark-dashboard` / `arkgate-dashboard`.
|
package/docs/ai-gates.md
CHANGED
|
@@ -50,7 +50,8 @@ fail-open. OpenCode is MCP-only. Required CI is always the merge line.
|
|
|
50
50
|
|
|
51
51
|
Everything below uses the same `ark.config.json` as `arkgate-check` / `ark-check` (CI) — one
|
|
52
52
|
rules file shared by every surface. From **4.0**, optional **ArkRules** (`arkRules` map +
|
|
53
|
-
`arkrules/*.json`) ride the **same** write path, doctor, and CI adapter
|
|
53
|
+
`arkrules/*.json`) ride the **same** write path, doctor, and CI adapter (ESLint runs the
|
|
54
|
+
file-local structure sensors only — see [ESLint](#eslint-editor-feedback--bounded-parity-envelope)); absence of ArkRules
|
|
54
55
|
does not change inter-layer verdicts. Optional ArkRun / ArkOrder extras ride that envelope
|
|
55
56
|
when on (`proposeRelease` then `apply`). Absence is silent. Label residual **`[Layer]`** vs
|
|
56
57
|
**`[ArkRules]`** vs **`[ArkRun]`** vs **`[ArkOrder]`**. See
|
|
@@ -128,7 +129,11 @@ project id before using project evidence.
|
|
|
128
129
|
Like `ark-check --baseline`, the hook ratchets: an edit is blocked only when it **adds**
|
|
129
130
|
violations relative to the file's current on-disk state, so files with pre-existing
|
|
130
131
|
(baselined) violations stay editable — they just can't get worse. New files block on
|
|
131
|
-
every violation.
|
|
132
|
+
every violation. A contract failure (`WRITE_GATE_UNAVAILABLE`, e.g. a referenced ArkRules
|
|
133
|
+
file missing) is never ratcheted: it blocks edits to existing files as well as new ones.
|
|
134
|
+
The Codex `apply_patch` atomic preflight ratchets ArkRules-plane findings (`ARKRULE_*`,
|
|
135
|
+
`INVARIANT_*`) the same way against the base tree: debt the repository already has is
|
|
136
|
+
reported as a warning (`preExisting: true`), and only findings the patch adds block.
|
|
132
137
|
|
|
133
138
|
The hook classifies import targets with the same `layerForRelativePath` specificity as
|
|
134
139
|
`ark-check` (an explicit file pattern beats a broader glob such as `src/lib/**`).
|
|
@@ -250,7 +255,11 @@ After edits run: npx ark-check --root . --config ark.config.json --strict
|
|
|
250
255
|
```
|
|
251
256
|
|
|
252
257
|
The hook belongs in the **project's** `.claude/settings.json` (that's what
|
|
253
|
-
`--install-agent-gates` generates).
|
|
258
|
+
`--install-agent-gates` generates). An existing `.claude/settings.json` (or `.codex/hooks.json`)
|
|
259
|
+
is **merged**, not skipped or replaced: Ark upserts only its own `SessionStart` / `PreToolUse`
|
|
260
|
+
`arkgate-mcp` entries and keeps `permissions`, `env`, other hook events, and your own hook
|
|
261
|
+
entries; re-running is a no-op. Only a file that is not a JSON object is left untouched (and
|
|
262
|
+
`--require-write-hook` then refuses until you fix it or pass `--force`). It is also safe by construction if you prefer it in
|
|
254
263
|
your global settings: without an `ark.config.json` in the project, `--session-context`
|
|
255
264
|
prints nothing and exits 0, so non-Ark projects are untouched.
|
|
256
265
|
|
|
@@ -721,8 +730,25 @@ If your runtime can run a shell command before file writes and pass the tool pay
|
|
|
721
730
|
- Grok: `{ "decision": "deny", "reason": "…" }` on stdout when blocked
|
|
722
731
|
- Antigravity: **stdout `decision` is required** — allow → `{ "decision": "allow" }`; deny →
|
|
723
732
|
`{ "decision": "deny", "reason": "…" }` (exit 2 still set on deny)
|
|
724
|
-
- plumbing problems (no stdin, non-source files, files outside
|
|
725
|
-
still emits `{ "decision": "allow" }` on those fail-open paths
|
|
733
|
+
- plumbing problems (no stdin, malformed payload, non-file tools, non-source files, files outside
|
|
734
|
+
`--root`) never block; Antigravity still emits `{ "decision": "allow" }` on those fail-open paths.
|
|
735
|
+
"Outside `--root`" is judged after realpath: `/tmp` vs `/private/tmp` or a symlinked checkout is
|
|
736
|
+
the same workspace and is gated
|
|
737
|
+
- a gate that **cannot run** blocks every governed source write (exit `2`, `WRITE_GATE_UNAVAILABLE`,
|
|
738
|
+
plus the host deny JSON): `ark.config.json` missing / unparsable / invalid, a referenced ArkRules
|
|
739
|
+
file missing or invalid, or `dist/` missing or broken. No checker, no write. This applies to
|
|
740
|
+
existing files too: the same-file ratchet never cancels a contract failure. Writes to the law
|
|
741
|
+
files (`ark.config.json` and every ArkRules file it references under `arkRules`) are still
|
|
742
|
+
allowed so the contract can be repaired, but a write or `apply_patch` delete that would leave
|
|
743
|
+
one unloadable is denied (`WRITE_GATE_UNAVAILABLE`)
|
|
744
|
+
- Codex `apply_patch`: the full grammar is reconstructed (`*** End of File`, a first hunk without
|
|
745
|
+
`@@`, `*** Move to:` judged at the destination). A patch that truly cannot be reconstructed is
|
|
746
|
+
allowed with a stderr notice and stays CI-backed
|
|
747
|
+
- the hook runs the same file-local extra planes CI runs on that file: enforced ArkRun editor
|
|
748
|
+
sensors, enforced ArkRules structure sensors (`ARKRULE_STRUCTURE`), and enforced ArkOrder editor
|
|
749
|
+
sensors (`ARKORDER_KERNEL_IN_DOMAIN`, `ARKORDER_GENERIC_UPDATE`, …), under the same
|
|
750
|
+
classification floor. Cross-file evidence (invariant coverage, missing plane roots) stays
|
|
751
|
+
CI-authoritative
|
|
726
752
|
|
|
727
753
|
## ESLint (editor feedback) — bounded parity envelope
|
|
728
754
|
|
|
@@ -731,31 +757,108 @@ Layer imports and purity globals are driven by **`ark.config.json`** (walk-up fr
|
|
|
731
757
|
linted file).
|
|
732
758
|
|
|
733
759
|
```js
|
|
734
|
-
// eslint.config.js (flat config)
|
|
760
|
+
// eslint.config.js (flat config, ESM)
|
|
735
761
|
import ark from 'arkgate/eslint';
|
|
736
762
|
|
|
737
763
|
export default [
|
|
738
764
|
ark.configs.recommended,
|
|
739
|
-
// no-domain-infra-imports
|
|
740
|
-
// no-forbidden-globals
|
|
741
|
-
//
|
|
742
|
-
//
|
|
743
|
-
//
|
|
744
|
-
// no-
|
|
765
|
+
// error: no-domain-infra-imports → config-driven layer edges that fail ark-check
|
|
766
|
+
// error: no-forbidden-globals → layer.forbiddenGlobals from ark.config.json
|
|
767
|
+
// error: no-denied-capabilities → layer.capabilities.deny / layer.pure
|
|
768
|
+
// error: no-arkrun-kernel-in-domain + no-arkrun-direct-new + no-arkrun-transport-bypass
|
|
769
|
+
// → arkRun extra (silent when absent; import / `new` envelope only)
|
|
770
|
+
// error: no-arkorder-kernel-in-domain + no-arkorder-generic-update → arkOrder extra
|
|
771
|
+
// error: arkrules-structure → ArkRules structure sensors (file-local)
|
|
772
|
+
// error: no-raw-event-publish + require-publish-source → runtime event hygiene
|
|
773
|
+
// warn: architecture-advisory → the findings ark-check reports as warnings
|
|
745
774
|
];
|
|
746
775
|
```
|
|
747
776
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
`ark.config.json` when its content changes and never invents a not-yet-created target.
|
|
777
|
+
```js
|
|
778
|
+
// eslint.config.cjs (CommonJS works the same way)
|
|
779
|
+
const ark = require('arkgate/eslint');
|
|
780
|
+
module.exports = [ark.configs.recommended];
|
|
781
|
+
```
|
|
754
782
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
783
|
+
`require('arkgate/eslint')` returns the plugin itself (`configs`, `rules`, `meta`), the same
|
|
784
|
+
object as the ESM default export.
|
|
785
|
+
|
|
786
|
+
**Severity follows ark-check, per finding.** ESLint takes severity from the rule's configured
|
|
787
|
+
level; one rule cannot report some findings as errors and others as warnings. So the plugin
|
|
788
|
+
splits them by rule id. The blocking rules above report only findings that fail ark-check
|
|
789
|
+
(`severity: "error"`). Every finding ark-check reports as a warning (`failsStrict: false`)
|
|
790
|
+
reports on **`ark/architecture-advisory`** instead, which `recommended` sets to `warn`:
|
|
791
|
+
|
|
792
|
+
- type-only placement debt (a non-peer `import type` across a denied edge);
|
|
793
|
+
- a `CROSS_SIBLING_SLICE` crossing under `childSlices.siblings: "advisory"` (or not listed in
|
|
794
|
+
`enforce`);
|
|
795
|
+
- `arkRun.mode`, `arkOrder.mode`, or an ArkRules rule set to `advisory`.
|
|
796
|
+
|
|
797
|
+
So `eslint` exits `0` wherever `arkgate-check` exits `0` for these findings, and a Next.js
|
|
798
|
+
build that runs ESLint does not fail on them. The warning text names the ark-check rule id.
|
|
799
|
+
The blocking rule and `architecture-advisory` share one evaluation per file, so enabling both
|
|
800
|
+
does not resolve a file's imports twice.
|
|
801
|
+
|
|
802
|
+
A hand-written config that turns on single rules without `architecture-advisory` does not
|
|
803
|
+
lose these findings. They fall back to the blocking rule id, at that rule's level, with the
|
|
804
|
+
text `[<ark-check rule id>; advisory — does not fail ark-check. Enable
|
|
805
|
+
ark/architecture-advisory …]`. Add `'ark/architecture-advisory': 'warn'` to see them as
|
|
806
|
+
warnings instead.
|
|
807
|
+
There is no baseline in the editor: a new advisory crossing past the recorded baseline, which
|
|
808
|
+
fails in CI, is still only a warning here.
|
|
809
|
+
|
|
810
|
+
**Messages are the ark-check text.** A layer denial reads
|
|
811
|
+
`Architecture: <ark-check message> Specifier: <import>`. That message carries the rule-level
|
|
812
|
+
`message` and the slice reason (cross-slice, cross-sibling, cross-parent, unclassifiable) plus
|
|
813
|
+
the type-only suffix. ArkOrder and ArkRules findings print the sensor's own message.
|
|
814
|
+
|
|
815
|
+
**Invalid contract.** When `ark.config.json` does not parse or validate, or a referenced
|
|
816
|
+
ArkRules file is missing or invalid, ESLint does not crash. The file gets one
|
|
817
|
+
`configInvalid` error carrying the validator text (fail closed, same as `ark-check`, which
|
|
818
|
+
exits `2`), and other rules and files keep linting.
|
|
819
|
+
|
|
820
|
+
**`eslint --cache`.** The plugin has `meta` (`arkgate@<version>`), so an upgrade invalidates
|
|
821
|
+
cached results. `configs.recommended` also carries `settings.ark.contractHash`, a fingerprint of
|
|
822
|
+
the `ark.config.json` (and referenced ArkRules files) found by walking up from the **current
|
|
823
|
+
working directory** of the process that reads `configs.recommended` — not from the config
|
|
824
|
+
file's directory. Editing that contract therefore invalidates the cache too. When ESLint runs
|
|
825
|
+
from another directory (`eslint -c some/dir/eslint.config.js` from elsewhere, or an editor
|
|
826
|
+
whose cwd is a workspace root above the project), the fingerprint covers a different contract
|
|
827
|
+
or none. Pin it to the config file's directory yourself:
|
|
828
|
+
|
|
829
|
+
```js
|
|
830
|
+
// eslint.config.js
|
|
831
|
+
import ark, { contractFingerprint } from 'arkgate/eslint';
|
|
832
|
+
const contractHash = contractFingerprint(import.meta.dirname);
|
|
833
|
+
export default [
|
|
834
|
+
ark.configs.recommended,
|
|
835
|
+
...(contractHash ? [{ settings: { ark: { contractHash } } }] : []),
|
|
836
|
+
];
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
Not covered by the fingerprint: a second `ark.config.json` deeper in a monorepo,
|
|
840
|
+
`tsconfig.json` edits, and a cross-file target that appears or disappears. Use
|
|
841
|
+
`--cache-strategy content`, clear the cache, or rely on `ark-check` / CI, which stay the
|
|
842
|
+
source of truth.
|
|
843
|
+
|
|
844
|
+
**Exact layer-edge parity envelope:** the linted production source is inside `include`,
|
|
845
|
+
outside configured/generated exclusions, parse-clean, and uses a static `import`/`export` with
|
|
846
|
+
a **relative** or **tsconfig `paths` / `baseUrl` alias** (e.g. `@/*`) literal whose target is
|
|
847
|
+
on disk. Inside that envelope, ESLint uses the same layer glob specificity, rule decision,
|
|
848
|
+
severity, rule id, message, and evidence as the resolved CLI. It reloads `ark.config.json`
|
|
849
|
+
when its content changes and never invents a not-yet-created target. File-local facts
|
|
850
|
+
(`new`, ArkOrder, ArkRules structure) come from the editor buffer, so unsaved edits and
|
|
851
|
+
`--stdin --stdin-filename` are linted as written.
|
|
852
|
+
|
|
853
|
+
**Path-alias resolution:** like the CLI (`ts.findConfigFile`), the tsconfig is the one nearest
|
|
854
|
+
to the linted file, so a nested `apps/web/tsconfig.json` is used under a single root
|
|
855
|
+
`ark.config.json`. It is read as JSONC (comments, trailing commas, and `"@/*"` / `"**/*.ts"`
|
|
856
|
+
strings are safe). `extends` may be relative, absolute, a package (`@tsconfig/next/...`), or an
|
|
857
|
+
array. Inheritance follows TypeScript: a child's `paths` replaces the parent's, and `paths`
|
|
858
|
+
resolve against `baseUrl`, else against the tsconfig that declares them. Each target in a path
|
|
859
|
+
array is tried in order, and a `*` may sit mid-pattern. Parsed tsconfigs are cached and re-read
|
|
860
|
+
when the file changes. Not claimed: TypeScript project references, catch-all `*` mappings,
|
|
861
|
+
package/workspace bare imports, or symlink hops. Those stay CI / preflight / write-gate truth.
|
|
759
862
|
|
|
760
863
|
Outside that envelope—packages/workspaces, symlinks, CommonJS, `import = require`, dynamic
|
|
761
864
|
imports, virtual creates/deletes, unresolved targets, or complete cross-file candidates—ESLint
|
|
@@ -765,8 +868,9 @@ ApplyPatch hook, or final strict CI; those paths consume the canonical resolved
|
|
|
765
868
|
Additional rule notes:
|
|
766
869
|
|
|
767
870
|
- Relative and tsconfig-aliased imports resolve only to existing on-disk TS/JS targets; package bare imports are left to CI/TS.
|
|
768
|
-
- Value forbidden edges error (same pass/fail as `arkgate-check`). Type-only forbidden edges are **placement debt
|
|
769
|
-
- `no-forbidden-globals` applies from the file layer’s `forbiddenGlobals`; the `globals` option is only a standalone fallback when no project config applies, never an override that weakens the project contract. Layers without either surface are not inventively restricted. `process` also owns exact value imports of `process` / `node:process`; type-only forms, subpaths, and `child_process` stay excluded. If the same layer also denies the `process` capability, this rule is the single `FORBIDDEN_GLOBAL` voice.
|
|
871
|
+
- Value forbidden edges error (same pass/fail as `arkgate-check`). Type-only forbidden edges that are not `peerIsolation` walls are **placement debt**: a warning on `ark/architecture-advisory`, never an error — align with doctor `typeEdgePolicy`.
|
|
872
|
+
- `no-forbidden-globals` applies from the file layer’s `forbiddenGlobals`; the `globals` option is only a standalone fallback when no project config applies, never an override that weakens the project contract. Layers without either surface are not inventively restricted. It sees dotted access (`Date.now`), static bracket access (`Date['now']`, ``Date[`now`]``, `globalThis['fetch']`), and destructuring from a global (`const { now } = Date`, `const { now: n } = globalThis.Date`); a dynamic key (`Date[k]`) is not a verdict. `process` also owns exact value imports of `process` / `node:process`; type-only forms, subpaths, and `child_process` stay excluded. If the same layer also denies the `process` capability, this rule is the single `FORBIDDEN_GLOBAL` voice.
|
|
873
|
+
- `require-publish-source` uses the ark-check candidate test: the first argument is an Ark intent string (`'Domain.Order.Placed'`), an `{ intent }` object, or an intent-creator reference (`OrderPlaced`, `Events.OrderPlaced`). `source` counts in the first argument's `metadata`, or in the second or third argument. A generic `pubsub.publish('USER_CREATED', …)` or `client.publish({ topic })` is not flagged. The rule is silent without an `ark.config.json` and outside `include`, like ark-check.
|
|
770
874
|
- Without `ark.config.json`, `no-domain-infra-imports` emits no contract verdict.
|
|
771
875
|
- **ArkRun (RN06):** when `arkRun` is present, `no-arkrun-kernel-in-domain`,
|
|
772
876
|
`no-arkrun-direct-new`, and `no-arkrun-transport-bypass` reuse the same
|
|
@@ -779,6 +883,21 @@ Additional rule notes:
|
|
|
779
883
|
kernel imports (including type-only). Type-only broker imports do not flag
|
|
780
884
|
transport-bypass. Missing-root and undeclared emit/handle/depend are **not**
|
|
781
885
|
in this adapter — use CLI / preflight / CI.
|
|
886
|
+
- **ArkOrder:** when `arkOrder` is present, `no-arkorder-kernel-in-domain` flags an
|
|
887
|
+
`arkgate/order` import, export-from, `require`, or dynamic import in a Domain-role layer — a
|
|
888
|
+
layer named `Domain` / `DomainModel` / `Domain…`, or one whose `intentPrefixes` include
|
|
889
|
+
`Domain.` — with the same sensor as ark-check. `no-arkorder-generic-update` runs the
|
|
890
|
+
file-local ξ sensors: generic `update()`, ξ field writes, `ingest()` into a Release, and
|
|
891
|
+
oversized `release()`. Missing-plane stays CLI / preflight / CI.
|
|
892
|
+
- **ArkRules:** `arkrules-structure` resolves the same Effective Contract as ark-check (a
|
|
893
|
+
missing or invalid referenced file is the `configInvalid` error above) and runs the
|
|
894
|
+
**structure** sensors on the linted file: `aggregate-private-state`,
|
|
895
|
+
`always-valid-factory`, `domain-event-on-mutation`, `no-anemic-model` (always advisory),
|
|
896
|
+
and the text heuristics for `orchestration-only`, `thin-adapter`, and
|
|
897
|
+
`writes-via-aggregate`. Not in the ESLint adapter — use preflight, `ark_prepare_change`, or
|
|
898
|
+
CI: invariant coverage and `INVARIANT_*` findings (they need test globs across files),
|
|
899
|
+
empty `appliesTo` and empty invariant catalogs, the structural-hint budget, and persistence
|
|
900
|
+
hints that need resolved import layers.
|
|
782
901
|
|
|
783
902
|
Rule ids are `ark/<kebab-name>`. Individual rules are also on `ark.rules` if you wire them by hand.
|
|
784
903
|
Prefer keeping editor + CI on the same `ark.config.json`. Use the rule-local `globals` list only
|
|
@@ -822,10 +941,27 @@ but stays advisory.
|
|
|
822
941
|
**command**. The hard merge boundary is making that job a **required GitHub status context** —
|
|
823
942
|
not “workflow file present.”
|
|
824
943
|
|
|
825
|
-
|
|
944
|
+
**Skipped jobs and `needs:`:** GitHub reports a skipped job as a status that satisfies branch
|
|
945
|
+
protection. Doctor therefore counts an Ark job as fail-closed only when nothing can skip it on
|
|
946
|
+
purpose:
|
|
947
|
+
|
|
948
|
+
| Ark job shape | Workflow evidence | Required-status verdict (`ARK_DOCTOR_GITHUB=1`) |
|
|
949
|
+
|---------------|-------------------|--------------------------------------------------|
|
|
950
|
+
| No `if:`, or `if: true` / `always()` | fail-closed | required when its context is required |
|
|
951
|
+
| `needs:` jobs with no conditional `if:` anywhere up the chain | fail-closed (it only skips when a needed job fails) | required only when every needed job is also required; otherwise `unverified` plus gap `enforcement-ark-check-upstream-not-required` |
|
|
952
|
+
| Job-level `if:` on a profile output (or any other condition), or a conditional job in its `needs:` chain | not fail-closed (the skip reports success) | not counted |
|
|
953
|
+
| `continue-on-error: true` on the job or the Ark step | not fail-closed | not counted |
|
|
954
|
+
|
|
955
|
+
A required check bound to the GitHub Actions app counts. A check bound to another app stays
|
|
956
|
+
`unverified`. Doctor queries branch protection only with `ARK_DOCTOR_GITHUB=1` and a working `gh`.
|
|
957
|
+
|
|
958
|
+
Or use the repository's composite Action at a pinned release or commit. Pin the tag (or commit
|
|
959
|
+
SHA) that matches the `arkgate` version in your `package.json` and write hook. The Action runs the
|
|
960
|
+
checker from the ref you pin, so an older ref runs an older checker, and that checker rejects newer
|
|
961
|
+
config keys (for example `schemaVersion` 1.3, `childSlices`, `sliceIdentity`) with exit 2:
|
|
826
962
|
|
|
827
963
|
```yaml
|
|
828
|
-
- uses: pedroknigge/arkgate@
|
|
964
|
+
- uses: pedroknigge/arkgate@v4.8.25 # same version as the arkgate devDependency
|
|
829
965
|
with:
|
|
830
966
|
root: .
|
|
831
967
|
config: ark.config.json
|
|
@@ -912,6 +1048,18 @@ npx ark-check --update-baseline # writes .ark-baseline.json — commit it
|
|
|
912
1048
|
npx ark-check --baseline # only NEW violations fail
|
|
913
1049
|
```
|
|
914
1050
|
|
|
1051
|
+
`--update-baseline` always measures the whole governed tree. It refuses `--changed` /
|
|
1052
|
+
`--local` and ignores `ARK_CHECK_LOCAL=1`, because a changed-files scan would overwrite the
|
|
1053
|
+
freeze with only that subset.
|
|
1054
|
+
|
|
1055
|
+
`ark-check --report` applies the committed `.ark-baseline.json` by default (the same file
|
|
1056
|
+
`--doctor` reads), so the report, `.ark/reports/latest.json`, and `ark status` agree with
|
|
1057
|
+
doctor on what is active versus frozen. A plain `ark-check` without `--baseline` is
|
|
1058
|
+
unchanged, and so is a merge verdict: with `--strict` / `--strict-merge` / `--contract-diff`,
|
|
1059
|
+
`--report` does **not** apply the implicit freeze (pass `--baseline` explicitly), so adding a
|
|
1060
|
+
reporting flag can never turn a failing merge run green. Only `--report` refreshes the `ark status` last-check snapshot; a plain check or
|
|
1061
|
+
`--doctor` does not write it.
|
|
1062
|
+
|
|
915
1063
|
`--update-baseline` also patches existing check **invocations** so CI keeps using the
|
|
916
1064
|
new freeze file: `package.json` scripts and GitHub workflow `run:` lines that already
|
|
917
1065
|
call `ark-check` / `arkgate-check` via `npx`, `pnpm`, `yarn`, `npm`, or `node`. Those
|