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/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,78 @@ in the immutable pre-2.0 archive linked below.
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 4.8.25 — 2026-10-05
|
|
9
|
+
|
|
10
|
+
**Patch** over **4.8.24**. Weekly accumulate (#342, #344, #345, #346, #347, #348, #349, #350). **Write. Check. Ship.** **No required config migration.** `childSlices.arkRulesFile` and pinned `sliceAliases` need 4.8.25. No `schemaVersion` bump. Does not close `K01` / `Z09`. This mother `ark.config.json` still does **not** turn `arkOrder` on.
|
|
11
|
+
|
|
12
|
+
**Status: released** (tag `v4.8.25`). Run `npm view arkgate@4.8.25 version` to confirm (#314).
|
|
13
|
+
|
|
8
14
|
### Added
|
|
15
|
+
- Per-slice ArkRules file `childSlices.arkRulesFile` and pinned `sliceAliases` (#342, #341).
|
|
16
|
+
- `arkgate-check --probe-invariants[=<id>]` checks that the tests covering an
|
|
17
|
+
invariant actually pin it. A test title that names the rule proves the test
|
|
18
|
+
exists, not that it would fail if the rule broke. For each invariant with a
|
|
19
|
+
`coverage.symbol`, the probe copies the project to a temporary folder and
|
|
20
|
+
runs only the covering tests (titles that name the id, and tests that import
|
|
21
|
+
the symbol's file) with your own runner — vitest, jest or `node --test`:
|
|
22
|
+
unchanged, then with the file throwing on load, then with the symbol
|
|
23
|
+
throwing when called, then with at most three small changes inside it
|
|
24
|
+
(negate a guard, drop a `throw`, flip or shift a comparison, shift a
|
|
25
|
+
constant). Each invariant reads `killed`, `survived`, `not-reached`,
|
|
26
|
+
`inconclusive` or `unprobeable`, with the change that went unnoticed and the
|
|
27
|
+
line. Your files are never changed. Report by default; `--write` saves
|
|
28
|
+
`.ark/invariant-probe.json` (new schema export `arkgate/schema/invariant-probe`).
|
|
29
|
+
Exit 0 when nothing survived, 1 when something did, 2 when it could not run.
|
|
30
|
+
Not a score.
|
|
31
|
+
- Promotion reads the committed probe result. When `.ark/invariant-probe.json`
|
|
32
|
+
has a fresh `survived` or `not-reached` row — the symbol file, its covering
|
|
33
|
+
tests and the invariant unchanged since the run — `--promote` and the policy
|
|
34
|
+
delta refuse to promote that invariant to `enforced` (blocker
|
|
35
|
+
`probe-survived`). A stale, `killed`, `inconclusive` or missing result
|
|
36
|
+
changes nothing, so every promotion without the file behaves as before.
|
|
37
|
+
Status and `--rules-inventory` show the probe state per invariant when the
|
|
38
|
+
file exists; new catalog id `INVARIANT_PROBE_SURVIVED` (advisory, never in
|
|
39
|
+
the check). The probe runs only when you start it: never from the write
|
|
40
|
+
hook, MCP, ESLint, the GitHub Action or `--strict-merge`, and it refuses to
|
|
41
|
+
combine with them. The runner gets a short environment allowlist (no
|
|
42
|
+
tokens); that is best effort, not a sandbox. No config key, no new skill;
|
|
43
|
+
`/ark-contract` and `/ark-coverage` explain the blocker and the next test to
|
|
44
|
+
write.
|
|
45
|
+
- Status lists code copied across a wall. When a slice wall denies an import,
|
|
46
|
+
the cheapest way around it is a copy, and a copy is not an import.
|
|
47
|
+
`--doctor --all`, the HTML report and `--doctor --all --json` (under
|
|
48
|
+
`doctor.crossWallDuplication`) now list near-identical code whose copies sit
|
|
49
|
+
on two sides of a slice wall, in two child slices of one universe, or in two
|
|
50
|
+
layers, with a place the shared code could live (the declared shared root,
|
|
51
|
+
the universe common folder, or the lower layer both sides may import).
|
|
52
|
+
Copies inside one slice are counted, never listed. It reads token
|
|
53
|
+
fingerprints taken while status runs, with the TypeScript ArkGate already
|
|
54
|
+
loaded; they never enter the check, so the verdict and `factsHash` do not
|
|
55
|
+
change. Tests, `.d.ts`, generated files and files over 256 KB are skipped.
|
|
56
|
+
Fixed thresholds (at least 50 tokens, 5 lines, most names matching), no
|
|
57
|
+
config key. Compact status never runs it; its JSON says `not-run` and names
|
|
58
|
+
the command. When a cap stops the pass the section says `partial` and how
|
|
59
|
+
much was left. New catalog ids `CROSS_WALL_DUPLICATE` and
|
|
60
|
+
`CROSS_LAYER_DUPLICATE` (advisory). Never a score, never in the write hook,
|
|
61
|
+
MCP, ESLint, `--changed`, or `--strict-merge`. No new skill; `/ark-place`
|
|
62
|
+
names the destination and `/ark-fix` moves one copy at a time.
|
|
63
|
+
- Status lists the files nothing imports. A governed file with no importer —
|
|
64
|
+
no governed file, test, project script, or known entry point — shows in
|
|
65
|
+
`--doctor --all`, the HTML report, and `--doctor --json` under
|
|
66
|
+
`doctor.orphanModules`; compact status prints one count line at most. It
|
|
67
|
+
reads the import edges the check already resolved, so the verdict and
|
|
68
|
+
`factsHash` do not change. Entry points come from `package.json` (built
|
|
69
|
+
`dist/` paths map back to source), CI workflow run steps, Next / Vite / Nest /
|
|
70
|
+
Vercel / Storybook conventions, the arkRun / arkOrder roots, config and setup
|
|
71
|
+
files, ambient declaration files, and an optional `.ark/entry-points.json`
|
|
72
|
+
(glob + reason + optional `reviewBy`; not a config key). Files only tests
|
|
73
|
+
import are counted, not listed. When a dynamic import, `import.meta.glob`, a
|
|
74
|
+
quoted path, or an unresolved import may still reach a file, the item says
|
|
75
|
+
so and the list is `partial`. `--doctor --all` and `--report` also list
|
|
76
|
+
exports nothing imports by name. New catalog ids `ORPHAN_MODULE` and
|
|
77
|
+
`UNUSED_EXPORT` (advisory). Never a score, never in the write hook, MCP
|
|
78
|
+
write tools, ESLint, or `--strict-merge`. No new skill; `/ark-explore` maps
|
|
79
|
+
the list and `/ark-fix` deletes one file at a time through the write gate.
|
|
9
80
|
- Soft doctor residual when Domain is declared but empty and the UI holds the
|
|
10
81
|
rules (`noDomainFrontend`). Projects empty Domain + presentation share, or
|
|
11
82
|
the existing `domain-logic-in-ui` smell. Friendly next step: one Domain file
|
|
@@ -48,6 +119,10 @@ in the immutable pre-2.0 archive linked below.
|
|
|
48
119
|
short phrase templates so the shape is visible. No new skill, schema, or flag.
|
|
49
120
|
|
|
50
121
|
### Changed
|
|
122
|
+
- `brace-expansion` is overridden to 5.0.12 (Dependabot medium, quadratic expansion) (#350).
|
|
123
|
+
- Eval fixtures pin Next.js 16.3.6 (#346).
|
|
124
|
+
- The package no longer ships `bin/lib/ark-order-invariants.mjs`, `bin/lib/ark-order-error.mjs`, or `bin/lib/stable-hash.mjs`; no CLI loaded them. ArkOrder invariants still ship in `arkgate/order`. One new generated CLI module, `bin/lib/schema-validation.mjs`, holds the JSON-Schema walker shared by the config and ArkRules contracts.
|
|
125
|
+
- Internal cleanup with no public export or behavior change: about 390 unused internal exports removed (knip) and hand-written copy/paste duplication cut from 1.01% to 0% (jscpd). Maintainers: `npm run check:knip` and `npm run check:duplication` run in the CI build job.
|
|
51
126
|
- Compact `--doctor` names ArkRules only when the `arkRules` map is on
|
|
52
127
|
(one breath + counts, not a score). Absence stays silent. Reuses
|
|
53
128
|
`rulesUnderContract` — no new schema, flag, or skill. `--doctor --all`
|
|
@@ -66,6 +141,16 @@ in the immutable pre-2.0 archive linked below.
|
|
|
66
141
|
Required CI is still the shared merge line. No new skill, schema, or host.
|
|
67
142
|
|
|
68
143
|
### Fixed
|
|
144
|
+
- `writes-via-aggregate` flags tagged-template SQL writes (`UPDATE`, `MERGE`, `TRUNCATE`, including `ONLY`, quoted names, and interpolated tables). Comments, `FOR UPDATE`, and `DO UPDATE SET` stay silent (#344, #343).
|
|
145
|
+
- `--policy-base` and MCP policy-delta now judge an advisory → enforced
|
|
146
|
+
invariant promotion with the declared `coverage.coverageRoots`, the same way
|
|
147
|
+
`--promote` does. Before, a covered promotion with roots declared came out
|
|
148
|
+
`judgment-required` ("Declare coverage.coverageRoots…") while `--promote`
|
|
149
|
+
allowed it.
|
|
150
|
+
- The flat-parent suggestion in status reads the resolved importer index
|
|
151
|
+
instead of re-reading every file for import text.
|
|
152
|
+
- Doctor sees CI jobs gated by `needs:`: a job that runs the fail-closed Ark check behind unconditional jobs (such as a CI-profile job) now counts as a merge gate; `ciMergeBoundary.ci.workflowPresent` / `merge-gate` no longer report it absent. Jobs a profile output can skip, jobs with `continue-on-error`, and chains with a conditional job still do not count.
|
|
153
|
+
- With `ARK_DOCTOR_GITHUB=1`, the Ark check counts as required only when every job that can skip it is required too (a skipped job satisfies branch protection); otherwise the doctor reports `unverified`, `arkCheckUpstreamNotRequired`, and the gap `enforcement-ark-check-upstream-not-required`. Required checks bound to the GitHub Actions app now correlate (`arkCheckSourceBound: true`).
|
|
69
154
|
- Write hook and `ark-check` now agree on overlapping layer globs: the hook
|
|
70
155
|
probes the same specifier extensions as `ark-check` and classifies with
|
|
71
156
|
`layerForRelativePath` (explicit `money.ts` beats `src/lib/**`). Field
|
|
@@ -96,6 +181,74 @@ in the immutable pre-2.0 archive linked below.
|
|
|
96
181
|
Existing configs keep whatever URL they already have. Editor completion
|
|
97
182
|
matches the 4.x line you just installed.
|
|
98
183
|
|
|
184
|
+
## 4.8.24 — 2026-09-29
|
|
185
|
+
|
|
186
|
+
**Patch** over **4.8.23**. Follow-ups to the hierarchical slices shipped in 4.8.23
|
|
187
|
+
(issues #335–#338), every defect a full audit of the gate and its add-ons
|
|
188
|
+
confirmed, and much lower memory per validation (#339). **Write. Check. Ship.**
|
|
189
|
+
**No required config migration:** a config that loads in 4.8.23 still loads;
|
|
190
|
+
ids that changed meaning warn (`CONFIG_CHILD_SLICES_INERT`,
|
|
191
|
+
`CONFIG_SLICE_LEGACY_STARS_ID`) and keep their 4.8.23 verdict for one release.
|
|
192
|
+
No `schemaVersion` bump. Does not close `K01` / `Z09`. This mother
|
|
193
|
+
`ark.config.json` still does **not** turn `arkOrder` on.
|
|
194
|
+
|
|
195
|
+
**Minimum version 4.8.24 for the new config fields** (`sharedImportsSlice`
|
|
196
|
+
object form with `stopAt`, `childSlices.message`, `childSlices.siblings.ratchet`).
|
|
197
|
+
arkgate 4.8.23 and older reject them at config load. Pin 4.8.24 in the write
|
|
198
|
+
hook and CI first.
|
|
199
|
+
|
|
200
|
+
**Status: released** (tag `v4.8.24`). This file does not assert registry
|
|
201
|
+
state; run `npm view arkgate@4.8.24 version` to confirm (#314).
|
|
202
|
+
|
|
203
|
+
### Added
|
|
204
|
+
- `sharedImportsSlice` accepts `{ "mode": "deny-cross-parent", "stopAt": [...] }`. The whole-graph `CROSS_PARENT_VIA_SHARED` walk never starts at or passes through a stop file, so a declared composition root (`kernel/bootstrap.ts`, `kernel/registrations/**`) is no longer reported as a crossing. `stopAt` matches like a shared root or an alias glob; a stop that covers the whole tree or a whole layer root is rejected. Findings carry `via`, and doctor `sharedWalkHubs` names a likely undeclared composition root. Adding stops is a weakening policy delta. The string forms are unchanged. arkgate 4.8.23 and older reject the object (issue [#335](https://github.com/pedroknigge/arkgate/issues/335)).
|
|
205
|
+
- Optional `childSlices.message` sets the text for inner-wall findings (`CROSS_SIBLING_SLICE`, universe common code importing a child). Without it ArkGate uses its own text; the rule `message` stays the universe-wall text and is never reused on inner-wall findings. Same text in ark-check, CI, the write hook, MCP, and ESLint (issue [#337](https://github.com/pedroknigge/arkgate/issues/337)).
|
|
206
|
+
- Optional `childSlices.siblings.ratchet`: `true` always fails a new advisory crossing, `false` only measures (issue [#336](https://github.com/pedroknigge/arkgate/issues/336)).
|
|
207
|
+
- `arkgate mcp` / `ark mcp` start the stdio MCP server (same as `arkgate-mcp`).
|
|
208
|
+
- ESLint: warn-level `ark/architecture-advisory` rule (findings ark-check reports as warnings) and `ark/arkrules-structure` (ArkRules structure sensors on the same Effective Contract).
|
|
209
|
+
- New advisory `ARKRULE_FILE_UNREFERENCED` for an `arkrules/*.json` the config does not reference, and `doctor.rulesMigration`.
|
|
210
|
+
- `ark-dashboard --help`, `--version`, `--once`, `ARK_DASHBOARD_URL`.
|
|
211
|
+
- `npm run bench:memory` regression guard (budgets in `eval/performance/memory-budgets.v1.json`).
|
|
212
|
+
|
|
213
|
+
### Changed
|
|
214
|
+
- **Memory and speed.** ark-check, `--doctor`, the write hook, ApplyPatch preflight, the MCP server and the ESLint plugin use much less memory; verdicts are byte-identical. On a 20k-file repo: ark-check peak 422 → 303 MB (3.7 → 2.3 s), ApplyPatch hook 649 → 414 MB (9.5 → 3.6 s), Write hook 163 → 146 MB (0.68 → 0.24 s), MCP server after 50 calls 725 → 442 MB. Sources are read one at a time and released, the facts hash streams, module resolution is memoized per run, the Write/Edit hook no longer walks the whole `include` tree for tsconfig aliases, and preflight reuses base parses.
|
|
215
|
+
- Advisory sibling crossings are advisory on day 1. The anti-growth ratchet is judged per rule and turns on only when the baseline in use already records an advisory sibling crossing of that rule (issue [#336](https://github.com/pedroknigge/arkgate/issues/336)).
|
|
216
|
+
- `CONFIG_CHILD_SLICES_VERSION` (and the other new-key version warnings) fire only with evidence that the repo pins an older arkgate (exact `package.json` dependency, installed copy, lockfile, hook script, or CI workflow). The finding points at that file and line and names the version to bump to (issue [#338](https://github.com/pedroknigge/arkgate/issues/338)).
|
|
217
|
+
- `childSlices` on a rule without `peerIsolation: true` and `allowed: false` still loads (as in 4.8.23) but is inert; ark-check now warns `CONFIG_CHILD_SLICES_INERT` and says how to fix it (add both flags to the rule, or remove `childSlices`). No config that loads in 4.8.23 fails to load.
|
|
218
|
+
- `sliceIdentity: "stars"` keeps every star binding (`modules/*/api/*` binds `orders/api/v1`, not `api/v1`). **Migration:** a `sliceAliases` `to` or an `allowedCrossSlice` / `childSlices.allowedCrossSlice` entry written against the 4.8.23 id (`api/v1/x`, `api/v1`) keeps loading and keeps its 4.8.23 verdict for this release: the alias joins the universe whose old id it names, and the allowance still clears the edge it cleared. ark-check warns `CONFIG_SLICE_LEGACY_STARS_ID` and names the new id to write (`orders/api/v1/x`). A legacy alias target is rejected at config load only when it maps to more than one universe shape of the rule. The legacy form is removed in the next minor release.
|
|
219
|
+
- `childSlices.commonFolders` counts only a folder directly under the universe; the same name inside a child belongs to that child.
|
|
220
|
+
- The write hook fails closed: if it cannot run inside an Ark project (invalid config, missing ArkRules file, missing `dist/`), governed writes are denied with exit 2 (`WRITE_GATE_UNAVAILABLE`) instead of exit 1, which hosts let through. The hook finds its project root like ark-check: a set `--root-env` variable, else the root that holds `ark.config.json`, else it walks up from the payload (written file, `cwd`, `workspace_roots`) to the nearest `ark.config.json`. A write outside any Ark project (no `ark.config.json` found) is allowed, as in 4.8.23.
|
|
221
|
+
- `ark start --apply --remove-host <host>` removes only the Ark hook entries from a merged `.claude/settings.json`, `.codex/hooks.json`, `.cursor/hooks.json` or `.agents/hooks.json` (same identity as the merge) and keeps every user key and hook; the file is deleted only when nothing else remains. "customized" is reported only when the Ark entry itself was edited.
|
|
222
|
+
- An enforced invariant with `coverage: { test: false }` and no `coverage.symbol` is uncovered; `coverage.symbol: "Class.member"` needs the member declared in that class.
|
|
223
|
+
- ArkOrder `apply()` only accepts a proposal bound to the current Release (`ARKORDER_STALE_PROPOSAL`); `restore()` never changes ξ or rolls the version back; `ReleaseStore.load()` is validated; the plane advances only after `save()`; `informationBudget` is enforced when a Release is created.
|
|
224
|
+
- ArkRun: the default event buffer is capped and settles after local delivery; broker handoff failures are recorded (`event.handoffFailed`); config kernels with canonical layer names enforce hard observed-layer-flow.
|
|
225
|
+
- `ark-check --update-baseline` always freezes the whole tree and refuses `--changed` / `--local`.
|
|
226
|
+
|
|
227
|
+
### Fixed
|
|
228
|
+
- `--changed`, `--local`, `--against` and `--persona` no longer give a false green: paths resolve relative to `--root` (monorepo packages), non-ASCII names are kept, large or failed git listings exit 2, and a missing base ref exits 2 (`changed-needs-base`) instead of "nothing changed".
|
|
229
|
+
- `--changed` catches a new `CROSS_PARENT_VIA_SHARED` created by editing only a shared-root file, and no longer warns `CONFIG_LAYER_PATTERN_NO_MATCHES` for untouched layers.
|
|
230
|
+
- `ark-check --report`, `latest.json`, `--doctor` and `ark status` agree on frozen versus active findings.
|
|
231
|
+
- Write path parity with CI: the hook, `validate_code`, `ark_prepare_write`, `ark_prepare_change` and ApplyPatch preflight enforce ArkRules structure sensors and ArkOrder editor sensors, load ArkRules like ark-check, handle realpath aliases, and accept the full Codex `apply_patch` format.
|
|
232
|
+
- MCP: `validate_code` / `ark_check` output schemas pass MCP SDK validation (tools no longer disappear); a stale contract returns `CONTRACT_STALE`; `ark_status` honors `--config`; `arkgate-mcp --help` / `--version` exit.
|
|
233
|
+
- MCP Registry: `server.json` runs `npx arkgate@<version> mcp --root .`; the old descriptor never started the server.
|
|
234
|
+
- CommonJS TypeScript consumers: `.d.cts` declarations ship and are mapped for every entry.
|
|
235
|
+
- ESLint: warnings in ark-check are warnings in ESLint; messages carry the rule message and slice reason; ArkOrder rules show real text at 1-based columns; tsconfig JSONC / nearest tsconfig / `extends` are read correctly; an invalid config reports one diagnostic instead of crashing; `require('arkgate/eslint')` returns the plugin (it also carries a non-enumerable `__esModule`, so Babel `_interopRequireWildcard` and TypeScript `__importStar` still see every named export); `--cache` invalidates on contract change; forbidden-globals catches destructuring and bracket access.
|
|
236
|
+
- Slices: adapter diagnostics carry `reasonId` and the universe pair; `CROSS_PARENT_VIA_SHARED` honors the universe `allowedCrossSlice`; fail-closed reasons are named in the hook and MCP; folder-form `sliceAliases` cover their subtree; the flat-parent move card counts every resolved importer; `SHARED_IMPORTS_SLICE` is catalogued.
|
|
237
|
+
- Setup: `ark start --archetype/--preset` writes that shape; re-running `ark start --apply` works; `--remove-host` does not touch packages; existing `.claude/settings.json` / `.codex/hooks.json` are merged instead of skipped or wiped; `typecheck` is added only when TypeScript is present; the steward lock holds under `--strict-merge --contract-session`; `ark status --config` / `ark agents-md --config` work; starter ArkRules ids are unique.
|
|
238
|
+
- ArkRules: policy delta reads base ArkRules from the base ref (demoting an enforced rule is weakening); class-shape sensors see generic classes, ignore braces in strings and regexes, and skip a `static { … }` initialization block (it is not a public member); `ArkRulesValidationError` is exported from the root.
|
|
239
|
+
- ArkRun: undeclared emit/handle/depend count only kernel-traced receivers (`res.send`, `require.resolve`, rxjs `subscribe` are ignored); typed `send(Creator)` resolves names; `ArkModule.forRoot()` is a composition root; `arkgate/runtime` and `arkgate/nestjs` share one kernel chunk (`instanceof` works). A namespace import of a composition root (`import * as main from '../main'; main.ark.publisher(..)`) is traced as a kernel receiver, and an untraced receiver counts when its name carries one of the project's effective intent prefixes (configured `layers[].intentPrefixes` plus the canonical ones), not only `Domain.`/`Application.`….
|
|
240
|
+
- ArkOrder: `ARKORDER_XI_FIELD_WRITE` recognizes Prisma/ORM clients by construction, `GENERIC_UPDATE` / `INGEST_WRITES_XI` false positives removed, `xi-ttl` and `information-budget` sensors emit, NaN/Infinity rejected, documented types exported, and the shipped `bin/lib/ark-order-invariants.mjs` loads.
|
|
241
|
+
- Docs: the composite Action pin tracks the current release, and configuration.md names the real policy-delta entry points.
|
|
242
|
+
- Generated CI workflows, host hooks and MCP entries in a project that does not
|
|
243
|
+
have arkgate as a local dependency run `npx -y -p arkgate@<exact version> <bin>`
|
|
244
|
+
instead of a bare `npx ark-check` / `npx arkgate-mcp` (npm resolved those as a
|
|
245
|
+
nonexistent package, or ran a stale global). Without a `package.json` the workflow
|
|
246
|
+
skips the install step and the npm cache. `--migrate-commands` rewrites bare runners
|
|
247
|
+
into the pinned form, and `--doctor` leads with `PACKAGE_PIN_ABSENT` when a generated
|
|
248
|
+
file still uses the local runner without the pin.
|
|
249
|
+
- `ark-check --json` answers every fatal error with a JSON envelope (`POLICY_BASE_UNREADABLE` when a CI base ref such as `GITHUB_BASE_REF` cannot be read in the checkout, otherwise `CHECK_ERROR`), not only `CONFIG_INVALID`; exit code stays 2.
|
|
250
|
+
- Security: CodeQL `js/polynomial-redos` in the steward email check and two incomplete-sanitization alerts are closed.
|
|
251
|
+
|
|
99
252
|
## 4.8.23 — 2026-09-28
|
|
100
253
|
|
|
101
254
|
**Patch** over **4.8.22**. Hierarchical slices: the issue #326 series
|
package/README.md
CHANGED
|
@@ -25,11 +25,11 @@ Design the boundary before you implement. Put a small interface in front of the
|
|
|
25
25
|
|
|
26
26
|
## Try it
|
|
27
27
|
|
|
28
|
-
One command. Preview only — no files change. Current release is **4.8.
|
|
28
|
+
One command. Preview only — no files change. Current release is **4.8.25**
|
|
29
29
|
(check npm `latest` with `npm view arkgate version`):
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
npx arkgate@4.8.
|
|
32
|
+
npx arkgate@4.8.25 start
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
## What a red result looks like
|
|
@@ -320,9 +320,11 @@ queue monitors:
|
|
|
320
320
|
|
|
321
321
|
Poll those facts from the dual bins **`ark-dashboard`** / **`arkgate-dashboard`**
|
|
322
322
|
(`bin/ark-dashboard.mjs`). ANSI + polling only (no React/Ink/Blessed). Point
|
|
323
|
-
`--url` / `-u` at the inspector
|
|
324
|
-
|
|
325
|
-
|
|
323
|
+
`--url` / `-u` at the inspector (`handle.url` or `handle.snapshotUrl`); the dashboard
|
|
324
|
+
also fetches sibling `/outbox` and `/workflows`. `startInspector()` binds a random port
|
|
325
|
+
unless you pass `{ port }`, so the fallback `http://127.0.0.1:3000/snapshot` (or
|
|
326
|
+
`ARK_DASHBOARD_URL`) only matches `startInspector({ port: 3000 })`. `--interval` / `-i`
|
|
327
|
+
is clamped to 200–60000 ms (default 2000); `--once`, `--help`, `--version` are supported.
|
|
326
328
|
Also available as `ark dashboard` / `arkgate dashboard` (passthrough to the same bin). Kernel stays
|
|
327
329
|
JSON-only; presentation stays in `bin/`.
|
|
328
330
|
|
|
@@ -77,6 +77,7 @@ import {
|
|
|
77
77
|
printViolationBreakdown,
|
|
78
78
|
printHumanWarnings,
|
|
79
79
|
CONCENTRATION_MIN_VIOLATIONS,
|
|
80
|
+
color,
|
|
80
81
|
} from './lib/violations.mjs';
|
|
81
82
|
import {
|
|
82
83
|
detectBestFitModel,
|
|
@@ -106,7 +107,7 @@ import {
|
|
|
106
107
|
normalize,
|
|
107
108
|
walk,
|
|
108
109
|
} from './lib/scan-files.mjs';
|
|
109
|
-
import { configWarning } from './lib/config-warnings.mjs';
|
|
110
|
+
import { configInvalidJsonEnvelope, configWarning } from './lib/config-warnings.mjs';
|
|
110
111
|
import { runArchitectureScan } from './lib/architecture-scan.mjs';
|
|
111
112
|
import {
|
|
112
113
|
ANALYSIS_COMPLETENESS,
|
|
@@ -124,6 +125,7 @@ import {
|
|
|
124
125
|
applyAgainstRatchet,
|
|
125
126
|
bindTeamBaseRefs,
|
|
126
127
|
contractSessionFrom,
|
|
128
|
+
pruneScopedPatternWarnings,
|
|
127
129
|
runTeamPreflight,
|
|
128
130
|
ungovernedDumpMessage,
|
|
129
131
|
} from './lib/team-parliament-io.mjs';
|
|
@@ -135,7 +137,9 @@ import {
|
|
|
135
137
|
resolveEffectiveProjectRoot,
|
|
136
138
|
} from './lib/project-root.mjs';
|
|
137
139
|
import { demoteArkRuleTeethUnderClassificationFloor } from './lib/rules-under-contract.mjs';
|
|
138
|
-
import {
|
|
140
|
+
import {
|
|
141
|
+
effectiveBaselineName, localCheckEnvelope, parseArgs, resolveDesignDeltaBaseRef, UPDATE_BASELINE_SCOPE_MESSAGE,
|
|
142
|
+
} from './lib/check-args.mjs';
|
|
139
143
|
import { detectConfig, proposeForUncovered } from './lib/check-config-detect.mjs';
|
|
140
144
|
import { runWatchMode } from './lib/check-watch.mjs';
|
|
141
145
|
|
|
@@ -1030,15 +1034,6 @@ function readManifest(root, manifestPath) {
|
|
|
1030
1034
|
return readJson(fullPath);
|
|
1031
1035
|
}
|
|
1032
1036
|
|
|
1033
|
-
const useColor = process.stderr.isTTY && !process.env.NO_COLOR;
|
|
1034
|
-
const color = {
|
|
1035
|
-
red: (s) => (useColor ? `\x1b[31m${s}\x1b[0m` : s),
|
|
1036
|
-
yellow: (s) => (useColor ? `\x1b[33m${s}\x1b[0m` : s),
|
|
1037
|
-
green: (s) => (useColor ? `\x1b[32m${s}\x1b[0m` : s),
|
|
1038
|
-
dim: (s) => (useColor ? `\x1b[2m${s}\x1b[0m` : s),
|
|
1039
|
-
bold: (s) => (useColor ? `\x1b[1m${s}\x1b[0m` : s),
|
|
1040
|
-
};
|
|
1041
|
-
|
|
1042
1037
|
/**
|
|
1043
1038
|
* Monorepo honesty: when cwd/--root has no ark.config.json, walk parents (bounded).
|
|
1044
1039
|
* Stamps configRoot / configWalkedUp. Mutates args.root only for read paths, or
|
|
@@ -1144,6 +1139,14 @@ async function main() {
|
|
|
1144
1139
|
return;
|
|
1145
1140
|
}
|
|
1146
1141
|
|
|
1142
|
+
if (args.probeInvariants) {
|
|
1143
|
+
// ADR 0039: owner-invoked, outside the gate path. Loaded only here; parseArgs
|
|
1144
|
+
// refuses every pairing with --strict-merge, --changed, --local or --doctor.
|
|
1145
|
+
const { runProbeInvariants } = await import('./lib/invariant-probe-cli.mjs');
|
|
1146
|
+
await runProbeInvariants(args, readConfig);
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1147
1150
|
if (args.sensors) {
|
|
1148
1151
|
const { runSensors } = await import('./lib/sensor-promote-cli.mjs');
|
|
1149
1152
|
await withSensorsPartialModeHonesty(args, () => runSensors(args, readConfig));
|
|
@@ -1333,6 +1336,7 @@ async function main() {
|
|
|
1333
1336
|
strictMerge: args.strictMerge || args.contractDiff,
|
|
1334
1337
|
basePath: args.policyBase,
|
|
1335
1338
|
baseRef: args.policyBaseRef,
|
|
1339
|
+
adoptMissingBase: args.policyBaseFromTeam === true,
|
|
1336
1340
|
acknowledgementPath: args.policyAck,
|
|
1337
1341
|
});
|
|
1338
1342
|
const preflight = runTeamPreflight({
|
|
@@ -1433,7 +1437,7 @@ async function main() {
|
|
|
1433
1437
|
|
|
1434
1438
|
const {
|
|
1435
1439
|
violations: rawViolations, warnings, safety, parseHealth, completeness, completenessReasons,
|
|
1436
|
-
mode, policyHash, resolverIdentity, factsHash, candidateTreeHash,
|
|
1440
|
+
mode, policyHash, resolverIdentity, factsHash, candidateTreeHash, importGraph,
|
|
1437
1441
|
} = runArchitectureScan({
|
|
1438
1442
|
root,
|
|
1439
1443
|
config,
|
|
@@ -1445,8 +1449,11 @@ async function main() {
|
|
|
1445
1449
|
files: args.changed ? files : [],
|
|
1446
1450
|
ts,
|
|
1447
1451
|
args,
|
|
1452
|
+
// ADR 0037: doctor / report read a compact importer index, never the verdict path.
|
|
1453
|
+
graphProjection: Boolean(args.doctor || args.report) && !args.changed,
|
|
1448
1454
|
});
|
|
1449
1455
|
|
|
1456
|
+
if (args.changed) pruneScopedPatternWarnings(warnings, root, loadGovernedFiles);
|
|
1450
1457
|
// Align merge with extraMergeTeeth stamp: demote enforced ArkRules under classification floor.
|
|
1451
1458
|
const preCov = computeCoverage(root, config, files, rules);
|
|
1452
1459
|
const populatedLayerCount = Array.isArray(preCov.layers)
|
|
@@ -1503,8 +1510,8 @@ async function main() {
|
|
|
1503
1510
|
configWalkedUp: args.configWalkedUp === true,
|
|
1504
1511
|
safety, designDelta,
|
|
1505
1512
|
ts, parseHealth, completeness,
|
|
1506
|
-
all: args.all === true, requireGates: args.requireGates === true,
|
|
1507
|
-
warnings,
|
|
1513
|
+
all: args.all === true, changed: args.changed === true, requireGates: args.requireGates === true,
|
|
1514
|
+
warnings, architectureFacts: importGraph ? { importGraph } : undefined,
|
|
1508
1515
|
});
|
|
1509
1516
|
if (designDelta) process.exitCode = !designDelta.complete ? 2 : designDelta.valid ? 0 : 1; return;
|
|
1510
1517
|
}
|
|
@@ -1515,6 +1522,8 @@ async function main() {
|
|
|
1515
1522
|
}
|
|
1516
1523
|
|
|
1517
1524
|
if (args.updateBaseline) {
|
|
1525
|
+
// A persona can turn on --changed after parseArgs; a subset scan must never rewrite the baseline.
|
|
1526
|
+
if (args.changed) { console.error(UPDATE_BASELINE_SCOPE_MESSAGE); process.exitCode = 2; return; }
|
|
1518
1527
|
if (!contractSessionFrom(args)) {
|
|
1519
1528
|
console.error('Growing the baseline requires --contract-session. Freeze in a law-only PR.');
|
|
1520
1529
|
process.exitCode = 1;
|
|
@@ -1587,14 +1596,16 @@ async function main() {
|
|
|
1587
1596
|
changed: args.changed,
|
|
1588
1597
|
changedPaths,
|
|
1589
1598
|
occurrenceKeys: baselineOccurrenceKeys(violations),
|
|
1599
|
+
rules,
|
|
1600
|
+
layers: config.layers,
|
|
1590
1601
|
});
|
|
1591
1602
|
activeViolations = ratcheted.activeViolations;
|
|
1592
1603
|
suppressed = ratcheted.suppressed;
|
|
1593
|
-
} else if (args
|
|
1594
|
-
const baseline = readBaseline(root, args
|
|
1604
|
+
} else if (effectiveBaselineName(args, root)) {
|
|
1605
|
+
const baseline = readBaseline(root, effectiveBaselineName(args, root));
|
|
1595
1606
|
if (baseline.exists) {
|
|
1596
1607
|
const occurrenceKeys = baselineOccurrenceKeys(violations);
|
|
1597
|
-
const judged = applyAdvisorySiblingRatchet(violations, occurrenceKeys, baseline.keys);
|
|
1608
|
+
const judged = applyAdvisorySiblingRatchet(violations, occurrenceKeys, baseline.keys, { rules, layers: config.layers });
|
|
1598
1609
|
suppressed = judged.filter((_, index) => baseline.keys.has(occurrenceKeys[index]));
|
|
1599
1610
|
activeViolations = judged.filter((_, index) => !baseline.keys.has(occurrenceKeys[index]));
|
|
1600
1611
|
const currentKeys = new Set(occurrenceKeys);
|
|
@@ -1639,81 +1650,16 @@ async function main() {
|
|
|
1639
1650
|
}
|
|
1640
1651
|
|
|
1641
1652
|
if (args.rulesInventory) {
|
|
1642
|
-
|
|
1643
|
-
const
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
fileContents[rel] = fs.readFileSync(file, 'utf8');
|
|
1649
|
-
const layer = layerForFile(root, file, config.layers);
|
|
1650
|
-
if (layer) fileLayers[rel] = layer;
|
|
1651
|
-
} catch {
|
|
1652
|
-
/* skip unreadable */
|
|
1653
|
-
}
|
|
1654
|
-
}
|
|
1655
|
-
const contracted = [];
|
|
1656
|
-
if (config.arkRules) {
|
|
1657
|
-
try {
|
|
1658
|
-
const { loadEffectiveArkRulesFromDisk } = await import('./lib/effective-contract-load.mjs');
|
|
1659
|
-
const loaded = loadEffectiveArkRulesFromDisk(root, config);
|
|
1660
|
-
for (const rule of loaded.arkRules.structure ?? []) contracted.push(rule.id);
|
|
1661
|
-
for (const inv of loaded.arkRules.invariants ?? []) contracted.push(inv.id);
|
|
1662
|
-
} catch {
|
|
1663
|
-
/* advisory inventory still useful */
|
|
1664
|
-
}
|
|
1665
|
-
}
|
|
1666
|
-
const inventory = buildRulesInventory({
|
|
1667
|
-
fileContents,
|
|
1668
|
-
fileLayers,
|
|
1669
|
-
layerContexts: (config.layers ?? []).map((layer) => ({
|
|
1670
|
-
name: layer.name,
|
|
1671
|
-
intentPrefixes: layer.intentPrefixes ?? [],
|
|
1672
|
-
})),
|
|
1673
|
-
contractedRuleIds: contracted,
|
|
1674
|
-
});
|
|
1675
|
-
const nextPilot =
|
|
1676
|
-
inventory.candidates[0] != null
|
|
1677
|
-
? inventoryToExtractionCard(inventory.candidates[0])
|
|
1678
|
-
: null;
|
|
1679
|
-
let evidenceLines = [];
|
|
1680
|
-
let coverageEvidence = null;
|
|
1681
|
-
if (config.arkRules && Object.keys(config.arkRules).length > 0) {
|
|
1682
|
-
try {
|
|
1683
|
-
const { summarizeRulesUnderContract, formatArkRulesEvidenceLines } = await import(
|
|
1684
|
-
'./lib/rules-under-contract.mjs'
|
|
1685
|
-
);
|
|
1686
|
-
const section = summarizeRulesUnderContract(root, config, {
|
|
1687
|
-
files: files.map((file) => ({ path: normalize(path.relative(root, file)) })),
|
|
1688
|
-
});
|
|
1689
|
-
evidenceLines = formatArkRulesEvidenceLines(section);
|
|
1690
|
-
if (section.active === true) {
|
|
1691
|
-
coverageEvidence = {
|
|
1692
|
-
symbolEvidence: section.symbolEvidence ?? [],
|
|
1693
|
-
discarded: section.coverageStats?.discarded ?? null,
|
|
1694
|
-
};
|
|
1695
|
-
}
|
|
1696
|
-
} catch {
|
|
1697
|
-
/* inventory still useful when coverage cannot be read */
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1700
|
-
const payload = {
|
|
1701
|
-
rulesInventory: inventory,
|
|
1702
|
-
rulesMigration: {
|
|
1703
|
-
inventoried: inventory.inventoried,
|
|
1704
|
-
underContract: inventory.underContract,
|
|
1705
|
-
frozen: inventory.frozen,
|
|
1706
|
-
notAScore: true,
|
|
1707
|
-
},
|
|
1708
|
-
nextPilot: nextPilot,
|
|
1709
|
-
...(coverageEvidence ? { coverageEvidence } : {}),
|
|
1710
|
-
};
|
|
1653
|
+
// Same helper as MCP ark_rules_inventory and doctor.rulesMigration (AR15).
|
|
1654
|
+
const { buildRulesInventoryPayload, formatRulesMigrationCounts } = await import(
|
|
1655
|
+
'./lib/rules-under-contract.mjs'
|
|
1656
|
+
);
|
|
1657
|
+
const { payload, evidenceLines } = buildRulesInventoryPayload(root, config, files);
|
|
1658
|
+
const { rulesInventory: inventory, nextPilot } = payload;
|
|
1711
1659
|
if (args.json) {
|
|
1712
1660
|
console.log(JSON.stringify(payload, null, 2));
|
|
1713
1661
|
} else {
|
|
1714
|
-
console.log(
|
|
1715
|
-
`Rules inventory: ${inventory.inventoried} inventoried, ${inventory.underContract} under contract, ${inventory.frozen} frozen (not a score).`
|
|
1716
|
-
);
|
|
1662
|
+
console.log(`Rules inventory: ${formatRulesMigrationCounts(payload.rulesMigration)}.`);
|
|
1717
1663
|
for (const c of inventory.candidates.slice(0, 12)) {
|
|
1718
1664
|
console.log(` - [${c.confidence}] ${c.kind} @ ${c.file}:${c.line} — ${c.message}`);
|
|
1719
1665
|
}
|
|
@@ -1846,8 +1792,9 @@ async function main() {
|
|
|
1846
1792
|
files,
|
|
1847
1793
|
ts,
|
|
1848
1794
|
parseHealth,
|
|
1849
|
-
undefined,
|
|
1850
|
-
activeViolations
|
|
1795
|
+
importGraph ? { importGraph } : undefined,
|
|
1796
|
+
activeViolations,
|
|
1797
|
+
{ details: true, changed: args.changed === true }
|
|
1851
1798
|
),
|
|
1852
1799
|
// Doctor parity: always emit improvement compass when doctor would (reportParity).
|
|
1853
1800
|
...(designDepth?.improvementCompass
|
|
@@ -1865,6 +1812,7 @@ async function main() {
|
|
|
1865
1812
|
? renderBeginnerHtmlReport(reportPayload)
|
|
1866
1813
|
: renderHtmlReport(reportPayload);
|
|
1867
1814
|
const reportPath = path.isAbsolute(args.report) ? args.report : path.join(root, args.report);
|
|
1815
|
+
fs.mkdirSync(path.dirname(reportPath), { recursive: true });
|
|
1868
1816
|
fs.writeFileSync(reportPath, html);
|
|
1869
1817
|
|
|
1870
1818
|
const archive = archiveReportSnapshots(root, {
|
|
@@ -2087,6 +2035,8 @@ async function main() {
|
|
|
2087
2035
|
}
|
|
2088
2036
|
|
|
2089
2037
|
main().catch((error) => {
|
|
2038
|
+
const envelope = configInvalidJsonEnvelope(error, process.argv);
|
|
2039
|
+
if (envelope) console.log(envelope);
|
|
2090
2040
|
console.error(
|
|
2091
2041
|
process.env.ARK_DEBUG_STACK === '1' && error instanceof Error
|
|
2092
2042
|
? error.stack
|
package/bin/ark-dashboard.mjs
CHANGED
|
@@ -1,43 +1,55 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
2
3
|
import { parseArgs } from 'node:util';
|
|
4
|
+
import {
|
|
5
|
+
DASHBOARD_DEFAULT_INTERVAL_MS,
|
|
6
|
+
DASHBOARD_DEFAULT_TIMEOUT_MS,
|
|
7
|
+
DASHBOARD_DEFAULT_URL,
|
|
8
|
+
DASHBOARD_EPHEMERAL_PORT_HINT,
|
|
9
|
+
DASHBOARD_HELP,
|
|
10
|
+
clampDashboardMs,
|
|
11
|
+
dashboardSiblingUrl,
|
|
12
|
+
} from './lib/dashboard-cli.mjs';
|
|
3
13
|
|
|
4
|
-
const DEFAULT_INTERVAL_MS = '2000';
|
|
5
|
-
const DEFAULT_SNAPSHOT_URL = 'http://127.0.0.1:3000/snapshot';
|
|
6
|
-
const DEFAULT_FETCH_TIMEOUT_MS = 5_000;
|
|
7
14
|
const MAX_FIELD_LEN = 120;
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
function parseDashboardArgs() {
|
|
17
|
+
try {
|
|
18
|
+
return parseArgs({
|
|
19
|
+
options: {
|
|
20
|
+
interval: { type: 'string', short: 'i' },
|
|
21
|
+
url: { type: 'string', short: 'u' },
|
|
22
|
+
timeout: { type: 'string', short: 't' },
|
|
23
|
+
once: { type: 'boolean' },
|
|
24
|
+
help: { type: 'boolean', short: 'h' },
|
|
25
|
+
version: { type: 'boolean', short: 'v' },
|
|
26
|
+
},
|
|
27
|
+
allowPositionals: true,
|
|
28
|
+
});
|
|
29
|
+
} catch (err) {
|
|
30
|
+
const message = err instanceof Error ? err.message.split('. ')[0] : String(err);
|
|
31
|
+
process.stderr.write(`ark-dashboard: ${message}. Run ark-dashboard --help.\n`);
|
|
32
|
+
process.exit(2);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const args = parseDashboardArgs();
|
|
37
|
+
if (args.values.help || args.positionals[0] === 'help') {
|
|
38
|
+
console.log(DASHBOARD_HELP);
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
if (args.values.version) {
|
|
42
|
+
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
43
|
+
console.log(pkg.version);
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
29
46
|
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
const parsedTimeout = parseInt(args.values.timeout, 10);
|
|
37
|
-
const fetchTimeoutMs =
|
|
38
|
-
Number.isFinite(parsedTimeout) && parsedTimeout >= 200 && parsedTimeout <= 60_000
|
|
39
|
-
? parsedTimeout
|
|
40
|
-
: DEFAULT_FETCH_TIMEOUT_MS;
|
|
47
|
+
const envUrl = process.env.ARK_DASHBOARD_URL?.trim();
|
|
48
|
+
const urlExplicit = Boolean(args.values.url || envUrl);
|
|
49
|
+
const targetUrl = args.values.url || envUrl || DASHBOARD_DEFAULT_URL;
|
|
50
|
+
const interval = clampDashboardMs(args.values.interval, DASHBOARD_DEFAULT_INTERVAL_MS);
|
|
51
|
+
const fetchTimeoutMs = clampDashboardMs(args.values.timeout, DASHBOARD_DEFAULT_TIMEOUT_MS);
|
|
52
|
+
const once = Boolean(args.values.once);
|
|
41
53
|
|
|
42
54
|
const RESET = '\x1b[0m';
|
|
43
55
|
const RED = '\x1b[31m';
|
|
@@ -73,22 +85,6 @@ function sanitizeField(value, maxLen = MAX_FIELD_LEN) {
|
|
|
73
85
|
return text;
|
|
74
86
|
}
|
|
75
87
|
|
|
76
|
-
function siblingUrl(snapshotUrl, suffix) {
|
|
77
|
-
try {
|
|
78
|
-
const u = new URL(snapshotUrl);
|
|
79
|
-
let basePath = u.pathname;
|
|
80
|
-
if (basePath.endsWith('/snapshot')) {
|
|
81
|
-
basePath = basePath.slice(0, -'/snapshot'.length);
|
|
82
|
-
} else if (basePath.endsWith('/snapshot/')) {
|
|
83
|
-
basePath = basePath.slice(0, -'/snapshot/'.length);
|
|
84
|
-
}
|
|
85
|
-
u.pathname = `${basePath}${suffix}`;
|
|
86
|
-
return u.toString();
|
|
87
|
-
} catch {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
88
|
/**
|
|
93
89
|
* @returns {{ ok: true, data: unknown } | { ok: false, kind: 'waiting' | 'failure', error?: string }}
|
|
94
90
|
*/
|
|
@@ -340,6 +336,7 @@ function renderHardening(snapshotResult) {
|
|
|
340
336
|
} else {
|
|
341
337
|
const detail = snapshotResult.error ? ` (${snapshotResult.error})` : '';
|
|
342
338
|
console.log(`${RED}Failure contacting kernel${detail}${RESET}`);
|
|
339
|
+
if (!urlExplicit) console.log(`${MUTED}${DASHBOARD_EPHEMERAL_PORT_HINT}${RESET}`);
|
|
343
340
|
}
|
|
344
341
|
return;
|
|
345
342
|
}
|
|
@@ -381,13 +378,13 @@ function renderHardening(snapshotResult) {
|
|
|
381
378
|
}
|
|
382
379
|
|
|
383
380
|
async function render() {
|
|
384
|
-
process.stdout.write('\x1b[2J\x1b[H');
|
|
381
|
+
if (!once) process.stdout.write('\x1b[2J\x1b[H');
|
|
385
382
|
console.log(`ArkGate Observability Dashboard`);
|
|
386
383
|
console.log(`Time: ${sanitizeField(new Date().toISOString())}`);
|
|
387
384
|
console.log(`Endpoint: ${sanitizeField(targetUrl)}\n`);
|
|
388
385
|
|
|
389
|
-
const outboxUrl =
|
|
390
|
-
const workflowsUrl =
|
|
386
|
+
const outboxUrl = dashboardSiblingUrl(targetUrl, '/outbox');
|
|
387
|
+
const workflowsUrl = dashboardSiblingUrl(targetUrl, '/workflows');
|
|
391
388
|
const [snapshotResult, outboxResult, workflowsResult] = await Promise.all([
|
|
392
389
|
fetchJsonResult(targetUrl),
|
|
393
390
|
fetchJsonResult(outboxUrl),
|
|
@@ -404,9 +401,15 @@ async function render() {
|
|
|
404
401
|
snapshot?.outbox,
|
|
405
402
|
snapshot?.workflows,
|
|
406
403
|
);
|
|
404
|
+
return snapshotResult.ok;
|
|
407
405
|
}
|
|
408
406
|
|
|
409
407
|
async function startDashboard() {
|
|
408
|
+
if (once) {
|
|
409
|
+
const ok = await render();
|
|
410
|
+
process.exitCode = ok ? 0 : 1;
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
410
413
|
console.log(
|
|
411
414
|
`Starting ArkGate Observability Dashboard (polling every ${interval}ms, fetch timeout ${fetchTimeoutMs}ms)`,
|
|
412
415
|
);
|