arkgate 4.8.3 → 4.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +279 -1
  2. package/README.md +17 -7
  3. package/bin/ark-check-runtime.mjs +340 -5
  4. package/bin/ark-layer-match.mjs +170 -13
  5. package/bin/ark-mcp-runtime.mjs +9 -2
  6. package/bin/lib/agent-homes.mjs +41 -6
  7. package/bin/lib/analysis-completeness.mjs +86 -0
  8. package/bin/lib/analysis-engine.mjs +8 -8
  9. package/bin/lib/architecture-scan.mjs +2 -0
  10. package/bin/lib/ark-order-invariants.mjs +37 -0
  11. package/bin/lib/ark-order-sensors.mjs +4 -0
  12. package/bin/lib/arkrules-contract.mjs +8 -1
  13. package/bin/lib/check-args.mjs +68 -0
  14. package/bin/lib/config-contract.mjs +26 -0
  15. package/bin/lib/design-smells.mjs +85 -0
  16. package/bin/lib/diagnostic-catalog.mjs +9 -1
  17. package/bin/lib/first-run-help.mjs +16 -4
  18. package/bin/lib/invariant-coverage-io.mjs +175 -19
  19. package/bin/lib/invariant-coverage.mjs +110 -7
  20. package/bin/lib/literal-path-drift-io.mjs +569 -0
  21. package/bin/lib/literal-path-drift.mjs +761 -0
  22. package/bin/lib/policy-delta-io.mjs +5 -0
  23. package/bin/lib/remediation.mjs +25 -1
  24. package/bin/lib/rules-under-contract.mjs +5 -0
  25. package/bin/lib/scan-files.mjs +54 -0
  26. package/bin/lib/sensor-promote-cli.mjs +372 -0
  27. package/bin/lib/sensor-promote-io.mjs +246 -0
  28. package/bin/lib/sensor-promotion.mjs +363 -0
  29. package/bin/lib/skill-catalog-apply.mjs +18 -1
  30. package/bin/lib/skill-install.mjs +4 -3
  31. package/bin/lib/upgrade-whats-new.mjs +1 -1
  32. package/dist/{configTypes-dNJ2C0yx.d.ts → configTypes-dy5PfTqS.d.ts} +31 -0
  33. package/dist/{diagnosticCatalog-C5GgeyEE.d.ts → diagnosticCatalog-DMO30svh.d.ts} +75 -7
  34. package/dist/eslint/index.cjs +5 -5
  35. package/dist/eslint/index.d.ts +34 -1
  36. package/dist/eslint/index.js +5 -5
  37. package/dist/index.cjs +30 -30
  38. package/dist/index.d.ts +68 -5
  39. package/dist/index.js +29 -29
  40. package/dist/nestjs/index.cjs +5 -5
  41. package/dist/nestjs/index.d.ts +3 -3
  42. package/dist/nestjs/index.js +5 -5
  43. package/dist/order/index.cjs +1 -1
  44. package/dist/order/index.d.ts +11 -2
  45. package/dist/order/index.js +1 -1
  46. package/dist/runtime/index.cjs +11 -11
  47. package/dist/runtime/index.d.ts +30 -7
  48. package/dist/runtime/index.js +11 -11
  49. package/dist/{types-dK24fDZa.d.ts → types-BuM8WNqe.d.ts} +1 -1
  50. package/dist/{types-DeK7SYGC.d.ts → types-CzE6LMaW.d.ts} +2 -2
  51. package/docs/README.md +5 -4
  52. package/docs/agent-guide.md +186 -4
  53. package/docs/ai-gates.md +13 -2
  54. package/docs/arkorder.md +210 -0
  55. package/docs/configuration.md +88 -5
  56. package/docs/develop.md +4 -2
  57. package/docs/diagnostics.md +97 -1
  58. package/docs/enthusiast/how-to-agent-gates.md +1 -1
  59. package/docs/package-surface.md +37 -5
  60. package/docs/use.md +1 -1
  61. package/package.json +3 -2
  62. package/schemas/ark.config.schema.json +63 -0
  63. package/server.json +3 -3
  64. package/templates/agent-skills/README.md +1 -1
  65. package/templates/agent-skills/ark-adopt/SKILL.md +5 -0
  66. package/templates/agent-skills/ark-coverage/SKILL.md +1 -0
  67. package/templates/agent-skills/ark-upgrade/SKILL.md +1 -1
  68. package/templates/skills/ark-adopt.md +5 -0
  69. package/templates/skills/ark-coverage.md +1 -0
  70. package/templates/skills/ark-upgrade.md +1 -1
package/CHANGELOG.md CHANGED
@@ -3,11 +3,289 @@
3
3
  All notable changes to ArkGate (`arkgate`; formerly `ark-runtime-kernel`) are documented here or
4
4
  in the immutable pre-2.0 archive linked below.
5
5
 
6
+ ## 4.8.5 — 2026-08-31
7
+
8
+ **Patch** over **4.8.4**. Phase **XP** (ArkOrder × ArkRun) plus Antigravity skill
9
+ refresh. ArkOrder is a **library plus sensors, not a service** — that sentence
10
+ now has a canonical page. The runtime half (shadow / replay / compare) is
11
+ ArkRun and in-memory. **No required config migration.** Does not close `K01` /
12
+ `Z09`.
13
+
14
+ **Status: prepared** (not yet on npm `latest`).
15
+
16
+ ### Added
17
+ - **`docs/arkorder.md`:** named APIs adopters were redesigning (`createOrderPlane`,
18
+ `IngestEscalate`, `Projector`, `maxXiKeys`, `ProposeResult`, `XiSchema`,
19
+ `xiKeys`, four verbs). Ships in the npm tarball. [ADR 0033](docs/adr/0033-arkorder-runtime-half-is-arkrun.md):
20
+ shadow/replay/provenance belong to ArkRun.
21
+ - **Information budget** (`createOrderPlane({ informationBudget })`, not a
22
+ config key) — a scale may not observe a denied kind
23
+ (`ARKORDER_INFORMATION_BUDGET`).
24
+ - **σ freshness, never ξ** — `createOrderPlane({ sigmaMaxAgeMs })` /
25
+ `σ.freshUntil`; age falls back to `release.releasedAt` when σ has no
26
+ timestamp. ξ keys named ttl/freshUntil/maxAge fail
27
+ (`ARKORDER_XI_TTL`, `ARKORDER_STALE_SIGMA`).
28
+ - **`IngestEscalate.target`** including `human`.
29
+ - **ArkRun** `shadowInformationPackage` / `compareInformationPackages` /
30
+ `replayInformationPackages` — in-memory, not a bus, not durable.
31
+ - **`--antigravity-home`:** monotonic install into `$ANTIGRAVITY_HOME/skills`
32
+ (default `~/.gemini/config/skills`). `--agent-homes` includes it. Unlike
33
+ Claude/Grok homes, this catalog is the machine floor for every workspace.
34
+
35
+ ### Changed
36
+ - Extra activation is one shape (`mode` + `managedLayers`). Roots stay distinct:
37
+ `arkOrder.planeRoots` vs `arkRun.kernelRoots` (`compositionRoots` alias).
38
+ `ark-check --sensors` lists both extras in one table.
39
+ - Default `--install-agent-gates` host set includes `antigravity` when nothing
40
+ is detected.
41
+
42
+ ## 4.8.4 — 2026-08-30
43
+
44
+ **Status: published** (on npm `latest`; see `docs/releases/4.8.4.md`).
45
+
46
+ **Patch** over **4.8.3**. Invariant coverage stops hiding its own limits: the scan budget and the
47
+ test globs are config, every discarded file is counted in the diagnostic, and coverage stops
48
+ implying it knows a test runs. New `--path-drift` sees the class the gate is structurally blind to:
49
+ repo paths living in strings, comments and docstrings that a rename left behind.
50
+ **No required config migration.**
51
+
52
+ ### Added
53
+ - **peerIsolation declared exceptions — `sharedRoots` and `allowedCrossSlice` on a rule.** Fail-closed denies on absence of evidence, so a repo that keeps shared code outside `features/<slice>/` (`ui/`, `hooks/`, `lib/permissions/`) got hundreds to thousands of violations, essentially none of them a real cross-slice import. `sharedRoots` declares those roots shared on purpose — a declaration is evidence, so they stop reading as unclassifiable. It is **anchored** (the root starts the path, optionally after one `src/` or `app/`) and a bare `*` / `**` is refused, so a root can never exempt a tree the author did not declare; `allowedCrossSlice: [{ from, to }]` declares one directed slice→slice edge, same shape as the layer edges in `rules[]`. Two different slices with nothing declared still deny, a file that is neither in a slice nor under a declared shared root still fails closed, and a shared root never shadows a real slice id. Both are `weakening` findings in `ark policy-delta`. The layer route stays the recommended model; this enforces the design of a repo that chose otherwise on purpose.
54
+ - The peerIsolation denial now **names which reason fired**: `cross-slice edge features/a → features/b` (a fact about your code) vs `unclassifiable path (src/widgets/x.tsx)`, `no slice folders`, `no path evidence` (facts about our evidence). Both the engine and the ESLint adapter emit it.
55
+ - **`arkgate-check --sensors` and `arkgate-check --promote` — promotability, before you pay for a run.**
56
+ Moving a rule from `advisory` to `enforced` was discovered by trial: edit the ArkRules JSON, wait
57
+ ~160s for a full run on a real repository, read the result, `git checkout` it back. Four attempts
58
+ before the map was clear, and one ended in a rejection naming a sensor id the author had never
59
+ typed — a rule called `types-only` refused with *"sensor `no-anemic-model` is Tier-2
60
+ advisory-only"*. On the field repository almost every declared rule turned out to be promotable
61
+ and nobody knew. Both surfaces are read-only by default and neither invents a second opinion: they
62
+ project the declarations the gate already reads.
63
+ **`--sensors [--json]`** lists all 22 sensors ArkGate ships across the three planes (ArkRules,
64
+ ArkRun, ArkOrder) with plane, tier and whether each can *ever* be enforced — 19 can, 3 never —
65
+ and it says **how**: only ArkRules is promoted per rule, ArkRun and ArkOrder are switched by the
66
+ plane-level `arkRun.mode` / `arkOrder.mode`, so a bare "can be enforced" would answer in a
67
+ currency this surface cannot spend. Tier-2 therefore shows up **before** the rule is written, not
68
+ after the run. Underneath it lists every rule the contract declares, each with its local id, its
69
+ sensor, its layer, the file it was declared in, its mode, and the reason it can or cannot be
70
+ promoted. Three blockers, each named: `tier-2-advisory-only` (`no-anemic-model`,
71
+ `arkrun-skip-resolve` — heuristics, advisory forever), `no-structure-teeth` (`invariant-coverage`
72
+ as a *structure* entry emits no coverage findings; only a zero-match `appliesTo` would still
73
+ fail), and `no-coverage-evidence`, whose wording is `canPromoteInvariant`'s own so this surface
74
+ can never promise a promotion the gate then refuses. No TypeScript, no analysis — the contract,
75
+ the ArkRules documents and the coverage evidence walk, which is a filesystem walk plus a text
76
+ match and never executes a test. Exit 0 on a report, **2** when the contract or its ArkRules
77
+ references will not load, or when the governed-file scan itself fails: reporting every invariant
78
+ as uncovered because ArkGate could not collect the inputs would be our limitation printed as a
79
+ fact about your tests.
80
+ **`--promote [<ruleId>] [--json] [--apply]`** adds the price. Advisory rules are already evaluated
81
+ on every run, so the findings each one produces are in the analysis that just happened, stamped
82
+ with the rule that produced them: **one** run prices **every** declared rule, which is the whole
83
+ difference from one run per attempt. Findings are keyed on `<sourceFile>#<ruleId>`, not the bare
84
+ id — ids are unique per ArkRules *document*, not across them, and the bare id pooled two rules'
85
+ findings and reported each one's as the other's. **A price the run could not measure is not
86
+ printed as a price:** an incomplete analysis, or a classification floor that demotes every
87
+ enforced ArkRules finding to a warning, is named above the numbers and drops `wouldBlock` to zero,
88
+ because promoting under the floor buys a label and not a tooth — the gate would still pass, which
89
+ is the exact false green this patch set exists to remove. Plan by default — there is no
90
+ `--dry-run` in `bin/` and this does not introduce one. `--apply` needs one named rule id
91
+ (`--promote <ruleId> --apply`, or `--promote=<ruleId>` for an id starting with `-`); it refuses a
92
+ bare `--promote --apply` rather than rewriting the contract in bulk, refuses an id declared in two
93
+ documents rather than silently writing the first, and refuses to make a contract change on a cost
94
+ this run did not measure. `--promote` cannot be combined with a mode that answers first
95
+ (`--sensors`, `--coverage`, `--plan`, `--doctor`, …), which used to print that report and exit 0
96
+ having written nothing, nor with a narrowed or baselined scope (`--changed`, `--against`,
97
+ `--baseline`), which would report an undercount as the price. The write binds to the rule that was
98
+ priced (a concurrent edit that changed its sensor is refused), writes every byte before it
99
+ truncates so a failed write cannot leave the project without a loadable contract, goes through one
100
+ `O_NOFOLLOW` descriptor with an nlink check and a realpath containment test, and names its
101
+ refusals: `symlink`, `hard-link`, `not-utf8`, `outside-root`, `short-write`. A document that was
102
+ already indented keeps its indentation and its trailing newline; a minified one comes back
103
+ pretty-printed, because the write is a JSON round-trip rather than a targeted text edit. Exits:
104
+ `0` preview or write succeeded, `1` unknown rule id or refused write, `2` bad arguments or the
105
+ ArkRules references would not load.
106
+ - **`arkgate-check --path-drift` — literal path drift (`LITERAL_PATH_DRIFT`, `LITERAL_PATH_UNRESOLVED`).**
107
+ A repo path written inside a string, a comment or a docstring is invisible to every tool in the
108
+ gate: `tsc` resolves imports, not strings, and ESLint does not either, so a rename compiles green
109
+ and the reference lies afterwards. Field data from a real 783-rename migration (49 source
110
+ directories gone) found the drift in **four** forms and every hand sweep covered one and let the
111
+ others through: the tsconfig alias, a relative literal, a path written without the `src/` prefix,
112
+ and prose — the largest class (24 references in 21 files) and the only one with no detector
113
+ anywhere, found in `.ts`, `.tsx` and a `.css`. Two modes, because they make different claims.
114
+ **Anchored** (default) matches against the rename set from `git diff --find-renames <base-ref>`
115
+ taken *against the working tree*, so a staged-but-uncommitted rename counts too; a finding carries a
116
+ replacement written in the author's own form — alias stays alias, relative is recomputed relative,
117
+ a prefix-less path keeps its coordinate space — the rewrite is mechanical and one-directional, so
118
+ `--write` applies it under the house plan-by-default convention, and a run exits 1 while anchored
119
+ drift remains. **Unanchored** (`--all`) lists literals that look like a repo path and do not
120
+ resolve; advisory, never written, never fails a run. It is opt-in on purpose: on a repository that
121
+ *writes about* paths it produced 4085 candidates out of 9536 literals, nearly all illustrative —
122
+ listing that by default would be ArkGate's inability to resolve a string presented as a fact about
123
+ your code, the same defect class as the coverage budget. The count is printed either way, so
124
+ opting out of the list is never opting out of knowing. Anchoring is conservative: only a rename
125
+ whose source really is gone and that has exactly one destination may anchor, ambiguous sources are
126
+ counted and reported, and a candidate is drift only when the **full** extracted token fails to
127
+ resolve — through the bare path, the usual source extensions and `index.*`, so an extensionless
128
+ reference is not mistaken for a dead one. Prose punctuation is trimmed off the token and generated
129
+ files are never read. The scan reads every text format where a path is written by hand
130
+ (`.ts .tsx .mts .cts .js .jsx .mjs .cjs .css .scss .json .md`) — deliberately wider than the
131
+ TS/TSX gate the type-aware extractors use — and counts every file it refused, by reason. Three things must hold before a replacement is proposed: a rename explains the reference, the
132
+ destination itself resolves (otherwise the fix only moves the drift), and the destination is
133
+ path-shaped — a git path is raw bytes, and one carrying a quote or a newline would edit the program
134
+ instead of repairing a reference. With no usable base ref the run prints *anchored mode did not
135
+ run*, no tick at all, and **exit 2** — a green mark over a check that never happened is the same
136
+ false green this patch is about, and a pipeline reads the status, not the tick. The three exits are
137
+ `0` ran and clean, `1` drift remains, `2` could not run.
138
+ `--write` refuses rather than risk a file and names every refusal: a symlinked leaf or parent, a
139
+ hard link, content that is not valid UTF-8 (a whole-file rewrite would replace the byte with
140
+ U+FFFD), a token that moved since the scan, a path outside the root. It goes through one
141
+ `O_NOFOLLOW` descriptor so the name is never resolved twice. A replacement must still be a path:
142
+ a rename whose destination is the literal's own directory would otherwise render as the bare `./`
143
+ and turn `require("./c")` into `require("./")`. The walk is bounded by a total byte budget as well as a file count (a per-file cap
144
+ times a file count is not a bound), it refuses symlinks, and `--write` re-checks containment
145
+ against the real path immediately before writing, so a link swapped in after the scan cannot carry
146
+ a write out of the tree.
147
+ - **`coverage.coverageRoots` (optional):** the project declares where its test runner actually
148
+ executes. Coverage evidence is a filesystem walk plus a text match — ArkGate never executes a
149
+ test and never reads a runner config — so a covering test in a folder no runner runs used to
150
+ certify an invariant exactly like one that runs, and `INVARIANT_UNCOVERED: 0` could be a false
151
+ green. With `coverageRoots` declared, a covering test found outside them raises the new advisory
152
+ `INVARIANT_COVERAGE_OUTSIDE_ROOTS` and blocks promotion of that invariant to `enforced`. Two
153
+ declarations compared against each other; nothing is executed. Absence stays silent: without a
154
+ declaration there is nothing to compare.
155
+ - **`coverage` config (optional):** `coverage.testGlobs` replaces the built-in test-name heuristic
156
+ and `coverage.maxFiles` sets the evidence file budget (default `400`). Both were already
157
+ implemented inside the loader but unreachable from `ark.config.json`; `ark-check`, doctor
158
+ (`rulesUnderContract`), and policy-delta now all pass them. Absence is silent.
159
+
160
+ ### Changed
161
+ - **`INVARIANT_UNCOVERED` says what it verified:** the old text ("not covered by a test title or
162
+ declared symbol") reads as *there is no test*, and its absence reads as *there is a test and it
163
+ runs* — neither is something a text match knows. The message now states the check it performed:
164
+ no scanned test names the invariant in a `describe`/`it` title and no declared symbol was found,
165
+ and ArkGate never executes tests.
166
+ - **`INVARIANT_UNCOVERED` carries numbers:** a budget-exhausted verdict reports files loaded, the
167
+ cap in force, tests retained, files discarded at the cap, and names `coverage.maxFiles` as the
168
+ knob that raises it. Tests dropped for naming no catalogued invariant are counted in the message
169
+ instead of vanishing.
170
+ - **No silent discards in the coverage scan:** oversize files (256KB cap), unreadable files
171
+ (permissions, broken symlinks) and directories past the walk depth limit (8) are counted by
172
+ reason and reported in `INVARIANT_UNCOVERED` alongside budget and no-mention drops. Symlinked
173
+ test files are now scanned instead of being skipped by the walk without a trace — but only when
174
+ the link resolves inside the project root. A link pointing outside is refused and counted: a file
175
+ that is not in this repo must never prove an invariant covered.
176
+
177
+ - **A green run names `--plan` when the design bets are still open.** `--plan` is built, it is
178
+ good, and it was invisible: a whole adopter session went by without opening it, because
179
+ `✔ Ark check passed` reads as *finished*. Clean import edges are not a settled design, and the
180
+ closing line was saying only the first half of what the run already knows. A passing human run
181
+ now prints one dim line underneath it — the smell count, the ids, and the command — whenever
182
+ deterministic design smells remain. It is report-only in the strictest sense: it can fire only on
183
+ a run that already passed, it adds no warning and never touches the exit code, and it stays silent
184
+ when the pass has nothing behind it (a line that always prints is a line nobody reads), on
185
+ `--changed`, where a partial scan would print one slice's count as if it were the tree's, and on
186
+ `--watch`, where it would reappear on every save. Its weakness test is `isDesignWeak` over the
187
+ **blocking** violation count, the same input doctor already uses, so the line and
188
+ `doctor.designFitness.designWeak` cannot disagree — non-blocking type-only placement debt is a
189
+ green run and still gets the pointer. And a baselined run is not called clean: with suppressions
190
+ in force the clause reads *"No blocking import-rule violations (N suppressed by baseline)"*,
191
+ because the summary line directly above already says a violation was frozen. `--json` is
192
+ unchanged.
193
+
194
+ ### Fixed
195
+ - **The Tier-2 rejection names the rule you wrote.** `sensor "no-anemic-model" is Tier-2
196
+ advisory-only and cannot be enforced` now reads `rule "types-only" uses sensor "no-anemic-model",
197
+ which is Tier-2 advisory-only and cannot be enforced (arkgate-check --sensors lists which sensors
198
+ can)`. Naming only the vocabulary id left the author hunting for which of their rules it meant,
199
+ at one full run per guess.
200
+ - **An analysis that covers zero files refuses instead of passing** (new
201
+ `ANALYSIS_COVERS_NO_FILES`). Every rule is vacuously satisfied on an empty set, so a green over
202
+ zero governed files certifies nothing while reading exactly like a green over a governed tree —
203
+ the one false green CI trusts. Measured: pointing `--config` at a copy of the contract outside
204
+ the tree moved the effective root to the copy's directory, every layer pattern matched nothing,
205
+ and `ark-check` printed 30 advisory warnings, a closing `✔ Ark check passed`, and exit 0; under
206
+ `--strict` it exited 1 for an unrelated reason (*"Ark gates are not installed"*) that sent the
207
+ user to `ark init` and never mentioned the empty analysis. The refusal is checked before
208
+ `--require-gates`, so the real reason wins. It separates ArkGate's limitation from a fact about
209
+ the repo: it fires when source exists under the analyzed root and the contract governs none of
210
+ it, or when the analyzed root is not the root that was asked for — and stays silent on a
211
+ genuinely greenfield repo, where `--init` is designed to land a contract before the code. The
212
+ report modes are untouched: `--plan`, `--coverage` and `--doctor` still describe an empty scope
213
+ (`empty-scope`) rather than refusing, because they are how the refusal gets fixed — so do not
214
+ gate CI on a report mode. "Is there source here" is answered by a probe the contract cannot
215
+ steer: it ignores the contract's own `exclude` (otherwise `exclude: ["**"]` buys the green back),
216
+ skips dot-directories, never follows a symlink (an unrelated out-of-root link must not turn a
217
+ diagnostic into a crash), skips `*.config.*` so a polyglot repo whose only TS is `vite.config.ts`
218
+ is greenfield rather than a mismatch, and stops at 200 files — the message says *at least N* when
219
+ it did. The whole check is evaluated lazily, so the cheap exits (a `--changed` run whose diff
220
+ touches no product path, `--require-gates` with the gates present) still pay nothing for a walk
221
+ they never needed.
222
+ - **The production diagnostic-id fixture is pinned in both directions.**
223
+ `tests/fixtures/diagnostic-catalog/production-rule-ids.json` had two tests over it and both
224
+ asserted fixture ⊆ catalog, so a production-emitted id added to the catalog and forgotten in the
225
+ fixture could not fail anything. `ARKORDER_XI_FIELD_WRITE` (emitted since 4.8.3) sat in exactly
226
+ that hole, and `ARKRULE_INVARIANT` with it. Two assertions added — every catalogued id must be
227
+ listed, and every `ARKORDER_RULE_IDS` / `ARKRUN_RULE_IDS` sensor id must be listed — and both
228
+ missing ids are now in the fixture.
229
+ - **`npm test` no longer dirties the tree.** `tests/unit/static-check/q05AiVelocity.test.ts`
230
+ spawned `eval/ai-velocity-run.mjs` with `cwd: REPO`, which rewrote the **tracked**
231
+ `eval/ai-velocity-report.json` on every run — its `generatedAt` is fresh each time, and that
232
+ pure-timestamp diff has already ridden into two commits. The harness now takes `--report` (and
233
+ `--baseline`) so the test writes into a temp dir and removes it in a `finally`, matching the
234
+ sibling test in the same file, and it now asserts both the tracked report **and** the tracked
235
+ baseline are byte-identical after the run. Seeding the baseline is now `--write-baseline` only:
236
+ it used to be written on any run where the file was missing, which is the same class of surprise
237
+ one level down. `--report` and `--baseline` must differ (aliased, the report was read back as its
238
+ own baseline and the run printed PASS without producing one) and neither may name a directory.
239
+ Default paths are unchanged for `npm run eval:ai-velocity`.
240
+ - **`coverage.maxFiles` is bounded.** It had no upper limit, and a schema `maximum` would
241
+ have been silently ignored (the config validator implements no such keyword), so any
242
+ integer became a memory budget. Clamped to 20000.
243
+ - **Files read vs files retained.** `stats.filesLoaded` counted retained files only, while
244
+ the budget diagnostic named `coverage.maxFiles` as the knob — implying it bounds I/O. It
245
+ bounds retention: a test is read before it can be judged for naming an invariant. The
246
+ scan now counts `filesRead` and the diagnostic says which of the two the cap bounds.
247
+ - **Depth-limited directories are counted once.** `'.'` overlaps `tests/` and `src/` in the
248
+ walk, so one deep subtree was reported once per overlapping root — and a directory
249
+ refused at depth from the far root was counted even when a nearer root walked it fine.
250
+ Only directories no walk ever entered are counted now.
251
+ - **Discards are counted per file, not per visit:** `coverage.testGlobs` widens the test walk to the
252
+ repo root, which overlaps the other walk roots. Each discarded file was counted — and read — once
253
+ per overlapping root, inflating every number in the diagnostic 2–3x for exactly the users who
254
+ opted into the new config.
255
+ - **The budget is reported exhausted only when it cost a file.** Landing exactly on the cap with
256
+ nothing dropped no longer tells the user to raise a cap that discarded nothing, and files past the
257
+ cap are no longer read before being discarded. Paths that were never coverage candidates
258
+ (directories, out-of-root paths) are no longer counted against the budget.
259
+ - **A git install no longer needs a build-allowlist entry — and no longer fails outright.**
260
+ `pnpm add git+https://github.com/pedroknigge/arkgate` did not install this package: it stopped
261
+ with `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`, because `prepack: npm run build` is a build script
262
+ and pnpm refuses to run one for a git-hosted package that is not in `allowBuilds`. Reproduced on
263
+ 4.8.3 verbatim. The only way through was adding `arkgate` to `allowBuilds` — an allowlist a real
264
+ adopter reserves for native binaries with written justification, for a script that shells out to
265
+ `npm` in a repo whose first rule forbids it. They shipped a `pnpm patch` instead, which is a fair
266
+ reading of the situation and the wrong thing to have to do. Adding the "obvious" `prepare` hook
267
+ makes it strictly worse (measured: same hard error). So the package now declares **no `prepack`,
268
+ no `prepare`, and no install script at all**; the publish path builds explicitly in
269
+ `scripts/release-npm.mjs`, with `prepublishOnly` as the backstop for a bare `npm publish` — pnpm
270
+ does not run `prepublishOnly` when it prepares a git dependency, so the backstop costs the
271
+ consumer nothing. `npm pack` no longer builds as a side effect; the three callers that relied on
272
+ that now build first, and `check:package-files` turns a missing `dist/` from a warning
273
+ into an error, because that is the failure this change makes reachable.
274
+ What a git install then gives you is stated rather than implied, in the new
275
+ [Installing from git](docs/package-surface.md#installing-from-git) section and in the README:
276
+ the `arkgate` / `arkgate-check` CLIs and the `arkgate/schema*` exports work, because `bin/` and
277
+ `schemas/` are committed — that is what the zero-build CLI is for. The library exports
278
+ (`arkgate`, `/eslint`, `/order`, `/runtime`, `/nestjs`) and `ark-mcp` do **not**, because they
279
+ resolve into `dist/`, which is a build output and is not committed. `ark-mcp` now says exactly
280
+ that instead of `Run "npm run build" first`, which is not advice a consumer inside `node_modules`
281
+ can act on. Shipping the library over the git path too would mean committing build output; that
282
+ is a separate decision and is not made here.
283
+
6
284
  ## 4.8.3 — 2026-08-30
7
285
 
8
286
  **Patch** over **4.8.2**. Persistence writes in a use-case skip the aggregate (`writes-via-aggregate`). ArkOrder **`xiKeys`** names the slow product decisions; a managed-layer Prisma/pg write of those keys is `ARKORDER_XI_FIELD_WRITE`. Dead sensors (`too-many-params`, `ingest-writes-xi`) now emit. No new skill names. Does not close `K01` / `Z09`. **No required config migration.**
9
287
 
10
- **Status: published** (on npm `latest`; see `docs/releases/4.8.3.md`).
288
+ **Status: published** (see `docs/releases/4.8.3.md`).
11
289
 
12
290
  ### Added
13
291
  - **`writes-via-aggregate`:** tier-1 structure sensor (ADR 0032). Direct evidence = persistence driver import **and** a write token (`.insert` / `.create` / `INSERT INTO` / …). Default advisory; promotable. Absence of the rule is silent.
package/README.md CHANGED
@@ -29,13 +29,13 @@ Works with Cursor, Claude, Codex, and Grok.
29
29
 
30
30
  </div>
31
31
 
32
- > **ArkGate 4.8.3** is on npm `latest`. Write. Check. Ship. Adopted = required GitHub
32
+ > **ArkGate 4.8.4** is on npm `latest`. Write. Check. Ship. Adopted = required GitHub
33
33
  > status running `arkgate-check --strict-merge`, or an explicit `advisory-only` stance.
34
34
  > Status is compact (`arkgate-check --doctor`; `--all` for Details). Optional **ArkRun**
35
35
  > (`arkgate/runtime`) is an in-memory runtime — not Postgres. Optional **ArkOrder**
36
36
  > (`arkgate/order`) stops the agent rewriting the few slow product decisions as CRUD.
37
37
  > `@arkgate/runtime` is deprecated.
38
- > [4.8.3](docs/releases/4.8.3.md) · [4.8.2](docs/releases/4.8.2.md) · [4.8.1](docs/releases/4.8.1.md) · [4.8.0](docs/releases/4.8.0.md) · [Docs hub](docs/README.md) · [Voice](docs/product-voice.md)
38
+ > [4.8.4](docs/releases/4.8.4.md) · [4.8.3](docs/releases/4.8.3.md) · [4.8.2](docs/releases/4.8.2.md) · [4.8.1](docs/releases/4.8.1.md) · [4.8.0](docs/releases/4.8.0.md) · [Docs hub](docs/README.md) · [Voice](docs/product-voice.md)
39
39
 
40
40
  ---
41
41
 
@@ -71,7 +71,11 @@ start → doctor → new files in the right folder
71
71
  Keep the rules file out of product PRs. Local check:
72
72
  `ark-check --changed --base origin/dev`. Changing the rules themselves uses `--contract-session`.
73
73
 
74
- Aliases `ark` / `ark-check` / `ark-mcp` still work. npm / pnpm / yarn. No install lifecycle scripts.
74
+ Aliases `ark` / `ark-check` / `ark-mcp` still work. npm / pnpm / yarn. No install lifecycle scripts
75
+ — and none on pack or prepare either, so `pnpm add git+https://github.com/pedroknigge/arkgate`
76
+ installs at a pinned commit with no `allowBuilds` entry. A git install gives you the CLIs and the
77
+ schemas; the library, MCP and ESLint entry points live in the built `dist/` and come from npm.
78
+ See [docs/package-surface.md](docs/package-surface.md#installing-from-git).
75
79
 
76
80
  ![Write gate: agent blocked, then self-corrects](docs/assets/ark-write-gate.svg)
77
81
 
@@ -230,9 +234,12 @@ npx arkgate status --json # session/project snapshot (identity, activat
230
234
  npx arkgate-check --doctor
231
235
  npx arkgate-check --plan
232
236
  npx arkgate-check --coverage
237
+ npx arkgate-check --path-drift --base-ref origin/main # stale paths in strings/comments after a rename
238
+ npx arkgate-check --sensors # which sensors can EVER be enforced, and which of your rules can be promoted
239
+ npx arkgate-check --promote # what enforcing each advisory rule would cost, from one run
233
240
  npx arkgate-check --strict-merge # CI / required status
234
- npx arkgate-check --install-agent-gates --tools claude,cursor,codex,grok
235
- # optional: refresh shared home skills (Claude/Grok/Codex; never downgrades)
241
+ npx arkgate-check --install-agent-gates --tools claude,cursor,codex,grok,antigravity
242
+ # optional: refresh shared home skills (Claude/Grok/Antigravity/Codex; never downgrades)
236
243
  # npx arkgate-check --install-agent-gates --skills-only --agent-homes --force
237
244
  # optional: same 13 skills via Agent Skills ecosystem (no new names)
238
245
  # npx skills add ./node_modules/arkgate/templates/agent-skills
@@ -280,7 +287,8 @@ In-memory. Not durable. Does not replace ArkRun.
280
287
 
281
288
  Copy [examples/arkorder-billing/](examples/arkorder-billing/) and rename the
282
289
  three keys. Compact starters leave it off. Details:
283
- [configuration](docs/configuration.md) · [package surface](docs/package-surface.md).
290
+ [ArkOrder](docs/arkorder.md) · [configuration](docs/configuration.md) ·
291
+ [package surface](docs/package-surface.md).
284
292
 
285
293
  ---
286
294
 
@@ -296,7 +304,9 @@ three keys. Compact starters leave it off. Details:
296
304
  | Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
297
305
  | Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
298
306
  | Security | [SECURITY.md](SECURITY.md) |
299
- | Current published (4.8.3 on npm `latest`) | [docs/releases/4.8.3.md](docs/releases/4.8.3.md) · [CHANGELOG](CHANGELOG.md) |
307
+ | Current tree (4.8.5 prepared) | [docs/releases/4.8.5.md](docs/releases/4.8.5.md) · [CHANGELOG](CHANGELOG.md) |
308
+ | Current published (4.8.4 on npm `latest`) | [docs/releases/4.8.4.md](docs/releases/4.8.4.md) |
309
+ | Prior published (4.8.3) | [docs/releases/4.8.3.md](docs/releases/4.8.3.md) |
300
310
  | Prior published (4.8.2) | [docs/releases/4.8.2.md](docs/releases/4.8.2.md) |
301
311
  | Prior published (4.8.1) | [docs/releases/4.8.1.md](docs/releases/4.8.1.md) |
302
312
  | Prior published (4.8.0) | [docs/releases/4.8.0.md](docs/releases/4.8.0.md) |