arkgate 4.8.3 → 4.8.4

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 (54) hide show
  1. package/CHANGELOG.md +242 -0
  2. package/README.md +10 -3
  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/analysis-completeness.mjs +86 -0
  7. package/bin/lib/analysis-engine.mjs +5 -5
  8. package/bin/lib/architecture-scan.mjs +2 -0
  9. package/bin/lib/arkrules-contract.mjs +8 -1
  10. package/bin/lib/check-args.mjs +66 -0
  11. package/bin/lib/config-contract.mjs +26 -0
  12. package/bin/lib/design-smells.mjs +85 -0
  13. package/bin/lib/diagnostic-catalog.mjs +6 -1
  14. package/bin/lib/first-run-help.mjs +12 -0
  15. package/bin/lib/invariant-coverage-io.mjs +175 -19
  16. package/bin/lib/invariant-coverage.mjs +110 -7
  17. package/bin/lib/literal-path-drift-io.mjs +569 -0
  18. package/bin/lib/literal-path-drift.mjs +761 -0
  19. package/bin/lib/policy-delta-io.mjs +5 -0
  20. package/bin/lib/remediation.mjs +15 -0
  21. package/bin/lib/rules-under-contract.mjs +5 -0
  22. package/bin/lib/scan-files.mjs +54 -0
  23. package/bin/lib/sensor-promote-cli.mjs +372 -0
  24. package/bin/lib/sensor-promote-io.mjs +246 -0
  25. package/bin/lib/sensor-promotion.mjs +363 -0
  26. package/dist/{configTypes-dNJ2C0yx.d.ts → configTypes-dy5PfTqS.d.ts} +31 -0
  27. package/dist/{diagnosticCatalog-C5GgeyEE.d.ts → diagnosticCatalog-DgTs0abp.d.ts} +75 -7
  28. package/dist/eslint/index.cjs +6 -6
  29. package/dist/eslint/index.d.ts +34 -1
  30. package/dist/eslint/index.js +6 -6
  31. package/dist/index.cjs +32 -32
  32. package/dist/index.d.ts +65 -4
  33. package/dist/index.js +29 -29
  34. package/dist/nestjs/index.cjs +5 -5
  35. package/dist/nestjs/index.d.ts +3 -3
  36. package/dist/nestjs/index.js +5 -5
  37. package/dist/runtime/index.cjs +15 -15
  38. package/dist/runtime/index.d.ts +6 -6
  39. package/dist/runtime/index.js +15 -15
  40. package/dist/{types-dK24fDZa.d.ts → types-BuM8WNqe.d.ts} +1 -1
  41. package/dist/{types-DeK7SYGC.d.ts → types-D95drJ3_.d.ts} +1 -1
  42. package/docs/README.md +1 -1
  43. package/docs/agent-guide.md +182 -0
  44. package/docs/configuration.md +77 -1
  45. package/docs/develop.md +1 -0
  46. package/docs/diagnostics.md +70 -1
  47. package/docs/package-surface.md +32 -2
  48. package/package.json +2 -2
  49. package/schemas/ark.config.schema.json +63 -0
  50. package/server.json +3 -3
  51. package/templates/agent-skills/ark-adopt/SKILL.md +5 -0
  52. package/templates/agent-skills/ark-coverage/SKILL.md +1 -0
  53. package/templates/skills/ark-adopt.md +5 -0
  54. package/templates/skills/ark-coverage.md +1 -0
package/CHANGELOG.md CHANGED
@@ -3,6 +3,248 @@
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.4 — 2026-08-30
7
+
8
+ **Status: pending publish** (see `docs/releases/4.8.4.md`).
9
+
10
+ **Patch** over **4.8.3**. Invariant coverage stops hiding its own limits: the scan budget and the
11
+ test globs are config, every discarded file is counted in the diagnostic, and coverage stops
12
+ implying it knows a test runs. New `--path-drift` sees the class the gate is structurally blind to:
13
+ repo paths living in strings, comments and docstrings that a rename left behind.
14
+ **No required config migration.**
15
+
16
+ ### Added
17
+ - **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.
18
+ - 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.
19
+ - **`arkgate-check --sensors` and `arkgate-check --promote` — promotability, before you pay for a run.**
20
+ Moving a rule from `advisory` to `enforced` was discovered by trial: edit the ArkRules JSON, wait
21
+ ~160s for a full run on a real repository, read the result, `git checkout` it back. Four attempts
22
+ before the map was clear, and one ended in a rejection naming a sensor id the author had never
23
+ typed — a rule called `types-only` refused with *"sensor `no-anemic-model` is Tier-2
24
+ advisory-only"*. On the field repository almost every declared rule turned out to be promotable
25
+ and nobody knew. Both surfaces are read-only by default and neither invents a second opinion: they
26
+ project the declarations the gate already reads.
27
+ **`--sensors [--json]`** lists all 22 sensors ArkGate ships across the three planes (ArkRules,
28
+ ArkRun, ArkOrder) with plane, tier and whether each can *ever* be enforced — 19 can, 3 never —
29
+ and it says **how**: only ArkRules is promoted per rule, ArkRun and ArkOrder are switched by the
30
+ plane-level `arkRun.mode` / `arkOrder.mode`, so a bare "can be enforced" would answer in a
31
+ currency this surface cannot spend. Tier-2 therefore shows up **before** the rule is written, not
32
+ after the run. Underneath it lists every rule the contract declares, each with its local id, its
33
+ sensor, its layer, the file it was declared in, its mode, and the reason it can or cannot be
34
+ promoted. Three blockers, each named: `tier-2-advisory-only` (`no-anemic-model`,
35
+ `arkrun-skip-resolve` — heuristics, advisory forever), `no-structure-teeth` (`invariant-coverage`
36
+ as a *structure* entry emits no coverage findings; only a zero-match `appliesTo` would still
37
+ fail), and `no-coverage-evidence`, whose wording is `canPromoteInvariant`'s own so this surface
38
+ can never promise a promotion the gate then refuses. No TypeScript, no analysis — the contract,
39
+ the ArkRules documents and the coverage evidence walk, which is a filesystem walk plus a text
40
+ match and never executes a test. Exit 0 on a report, **2** when the contract or its ArkRules
41
+ references will not load, or when the governed-file scan itself fails: reporting every invariant
42
+ as uncovered because ArkGate could not collect the inputs would be our limitation printed as a
43
+ fact about your tests.
44
+ **`--promote [<ruleId>] [--json] [--apply]`** adds the price. Advisory rules are already evaluated
45
+ on every run, so the findings each one produces are in the analysis that just happened, stamped
46
+ with the rule that produced them: **one** run prices **every** declared rule, which is the whole
47
+ difference from one run per attempt. Findings are keyed on `<sourceFile>#<ruleId>`, not the bare
48
+ id — ids are unique per ArkRules *document*, not across them, and the bare id pooled two rules'
49
+ findings and reported each one's as the other's. **A price the run could not measure is not
50
+ printed as a price:** an incomplete analysis, or a classification floor that demotes every
51
+ enforced ArkRules finding to a warning, is named above the numbers and drops `wouldBlock` to zero,
52
+ because promoting under the floor buys a label and not a tooth — the gate would still pass, which
53
+ is the exact false green this patch set exists to remove. Plan by default — there is no
54
+ `--dry-run` in `bin/` and this does not introduce one. `--apply` needs one named rule id
55
+ (`--promote <ruleId> --apply`, or `--promote=<ruleId>` for an id starting with `-`); it refuses a
56
+ bare `--promote --apply` rather than rewriting the contract in bulk, refuses an id declared in two
57
+ documents rather than silently writing the first, and refuses to make a contract change on a cost
58
+ this run did not measure. `--promote` cannot be combined with a mode that answers first
59
+ (`--sensors`, `--coverage`, `--plan`, `--doctor`, …), which used to print that report and exit 0
60
+ having written nothing, nor with a narrowed or baselined scope (`--changed`, `--against`,
61
+ `--baseline`), which would report an undercount as the price. The write binds to the rule that was
62
+ priced (a concurrent edit that changed its sensor is refused), writes every byte before it
63
+ truncates so a failed write cannot leave the project without a loadable contract, goes through one
64
+ `O_NOFOLLOW` descriptor with an nlink check and a realpath containment test, and names its
65
+ refusals: `symlink`, `hard-link`, `not-utf8`, `outside-root`, `short-write`. A document that was
66
+ already indented keeps its indentation and its trailing newline; a minified one comes back
67
+ pretty-printed, because the write is a JSON round-trip rather than a targeted text edit. Exits:
68
+ `0` preview or write succeeded, `1` unknown rule id or refused write, `2` bad arguments or the
69
+ ArkRules references would not load.
70
+ - **`arkgate-check --path-drift` — literal path drift (`LITERAL_PATH_DRIFT`, `LITERAL_PATH_UNRESOLVED`).**
71
+ A repo path written inside a string, a comment or a docstring is invisible to every tool in the
72
+ gate: `tsc` resolves imports, not strings, and ESLint does not either, so a rename compiles green
73
+ and the reference lies afterwards. Field data from a real 783-rename migration (49 source
74
+ directories gone) found the drift in **four** forms and every hand sweep covered one and let the
75
+ others through: the tsconfig alias, a relative literal, a path written without the `src/` prefix,
76
+ and prose — the largest class (24 references in 21 files) and the only one with no detector
77
+ anywhere, found in `.ts`, `.tsx` and a `.css`. Two modes, because they make different claims.
78
+ **Anchored** (default) matches against the rename set from `git diff --find-renames <base-ref>`
79
+ taken *against the working tree*, so a staged-but-uncommitted rename counts too; a finding carries a
80
+ replacement written in the author's own form — alias stays alias, relative is recomputed relative,
81
+ a prefix-less path keeps its coordinate space — the rewrite is mechanical and one-directional, so
82
+ `--write` applies it under the house plan-by-default convention, and a run exits 1 while anchored
83
+ drift remains. **Unanchored** (`--all`) lists literals that look like a repo path and do not
84
+ resolve; advisory, never written, never fails a run. It is opt-in on purpose: on a repository that
85
+ *writes about* paths it produced 4085 candidates out of 9536 literals, nearly all illustrative —
86
+ listing that by default would be ArkGate's inability to resolve a string presented as a fact about
87
+ your code, the same defect class as the coverage budget. The count is printed either way, so
88
+ opting out of the list is never opting out of knowing. Anchoring is conservative: only a rename
89
+ whose source really is gone and that has exactly one destination may anchor, ambiguous sources are
90
+ counted and reported, and a candidate is drift only when the **full** extracted token fails to
91
+ resolve — through the bare path, the usual source extensions and `index.*`, so an extensionless
92
+ reference is not mistaken for a dead one. Prose punctuation is trimmed off the token and generated
93
+ files are never read. The scan reads every text format where a path is written by hand
94
+ (`.ts .tsx .mts .cts .js .jsx .mjs .cjs .css .scss .json .md`) — deliberately wider than the
95
+ 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
96
+ destination itself resolves (otherwise the fix only moves the drift), and the destination is
97
+ path-shaped — a git path is raw bytes, and one carrying a quote or a newline would edit the program
98
+ instead of repairing a reference. With no usable base ref the run prints *anchored mode did not
99
+ run*, no tick at all, and **exit 2** — a green mark over a check that never happened is the same
100
+ false green this patch is about, and a pipeline reads the status, not the tick. The three exits are
101
+ `0` ran and clean, `1` drift remains, `2` could not run.
102
+ `--write` refuses rather than risk a file and names every refusal: a symlinked leaf or parent, a
103
+ hard link, content that is not valid UTF-8 (a whole-file rewrite would replace the byte with
104
+ U+FFFD), a token that moved since the scan, a path outside the root. It goes through one
105
+ `O_NOFOLLOW` descriptor so the name is never resolved twice. A replacement must still be a path:
106
+ a rename whose destination is the literal's own directory would otherwise render as the bare `./`
107
+ 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
108
+ times a file count is not a bound), it refuses symlinks, and `--write` re-checks containment
109
+ against the real path immediately before writing, so a link swapped in after the scan cannot carry
110
+ a write out of the tree.
111
+ - **`coverage.coverageRoots` (optional):** the project declares where its test runner actually
112
+ executes. Coverage evidence is a filesystem walk plus a text match — ArkGate never executes a
113
+ test and never reads a runner config — so a covering test in a folder no runner runs used to
114
+ certify an invariant exactly like one that runs, and `INVARIANT_UNCOVERED: 0` could be a false
115
+ green. With `coverageRoots` declared, a covering test found outside them raises the new advisory
116
+ `INVARIANT_COVERAGE_OUTSIDE_ROOTS` and blocks promotion of that invariant to `enforced`. Two
117
+ declarations compared against each other; nothing is executed. Absence stays silent: without a
118
+ declaration there is nothing to compare.
119
+ - **`coverage` config (optional):** `coverage.testGlobs` replaces the built-in test-name heuristic
120
+ and `coverage.maxFiles` sets the evidence file budget (default `400`). Both were already
121
+ implemented inside the loader but unreachable from `ark.config.json`; `ark-check`, doctor
122
+ (`rulesUnderContract`), and policy-delta now all pass them. Absence is silent.
123
+
124
+ ### Changed
125
+ - **`INVARIANT_UNCOVERED` says what it verified:** the old text ("not covered by a test title or
126
+ declared symbol") reads as *there is no test*, and its absence reads as *there is a test and it
127
+ runs* — neither is something a text match knows. The message now states the check it performed:
128
+ no scanned test names the invariant in a `describe`/`it` title and no declared symbol was found,
129
+ and ArkGate never executes tests.
130
+ - **`INVARIANT_UNCOVERED` carries numbers:** a budget-exhausted verdict reports files loaded, the
131
+ cap in force, tests retained, files discarded at the cap, and names `coverage.maxFiles` as the
132
+ knob that raises it. Tests dropped for naming no catalogued invariant are counted in the message
133
+ instead of vanishing.
134
+ - **No silent discards in the coverage scan:** oversize files (256KB cap), unreadable files
135
+ (permissions, broken symlinks) and directories past the walk depth limit (8) are counted by
136
+ reason and reported in `INVARIANT_UNCOVERED` alongside budget and no-mention drops. Symlinked
137
+ test files are now scanned instead of being skipped by the walk without a trace — but only when
138
+ the link resolves inside the project root. A link pointing outside is refused and counted: a file
139
+ that is not in this repo must never prove an invariant covered.
140
+
141
+ - **A green run names `--plan` when the design bets are still open.** `--plan` is built, it is
142
+ good, and it was invisible: a whole adopter session went by without opening it, because
143
+ `✔ Ark check passed` reads as *finished*. Clean import edges are not a settled design, and the
144
+ closing line was saying only the first half of what the run already knows. A passing human run
145
+ now prints one dim line underneath it — the smell count, the ids, and the command — whenever
146
+ deterministic design smells remain. It is report-only in the strictest sense: it can fire only on
147
+ a run that already passed, it adds no warning and never touches the exit code, and it stays silent
148
+ when the pass has nothing behind it (a line that always prints is a line nobody reads), on
149
+ `--changed`, where a partial scan would print one slice's count as if it were the tree's, and on
150
+ `--watch`, where it would reappear on every save. Its weakness test is `isDesignWeak` over the
151
+ **blocking** violation count, the same input doctor already uses, so the line and
152
+ `doctor.designFitness.designWeak` cannot disagree — non-blocking type-only placement debt is a
153
+ green run and still gets the pointer. And a baselined run is not called clean: with suppressions
154
+ in force the clause reads *"No blocking import-rule violations (N suppressed by baseline)"*,
155
+ because the summary line directly above already says a violation was frozen. `--json` is
156
+ unchanged.
157
+
158
+ ### Fixed
159
+ - **The Tier-2 rejection names the rule you wrote.** `sensor "no-anemic-model" is Tier-2
160
+ advisory-only and cannot be enforced` now reads `rule "types-only" uses sensor "no-anemic-model",
161
+ which is Tier-2 advisory-only and cannot be enforced (arkgate-check --sensors lists which sensors
162
+ can)`. Naming only the vocabulary id left the author hunting for which of their rules it meant,
163
+ at one full run per guess.
164
+ - **An analysis that covers zero files refuses instead of passing** (new
165
+ `ANALYSIS_COVERS_NO_FILES`). Every rule is vacuously satisfied on an empty set, so a green over
166
+ zero governed files certifies nothing while reading exactly like a green over a governed tree —
167
+ the one false green CI trusts. Measured: pointing `--config` at a copy of the contract outside
168
+ the tree moved the effective root to the copy's directory, every layer pattern matched nothing,
169
+ and `ark-check` printed 30 advisory warnings, a closing `✔ Ark check passed`, and exit 0; under
170
+ `--strict` it exited 1 for an unrelated reason (*"Ark gates are not installed"*) that sent the
171
+ user to `ark init` and never mentioned the empty analysis. The refusal is checked before
172
+ `--require-gates`, so the real reason wins. It separates ArkGate's limitation from a fact about
173
+ the repo: it fires when source exists under the analyzed root and the contract governs none of
174
+ it, or when the analyzed root is not the root that was asked for — and stays silent on a
175
+ genuinely greenfield repo, where `--init` is designed to land a contract before the code. The
176
+ report modes are untouched: `--plan`, `--coverage` and `--doctor` still describe an empty scope
177
+ (`empty-scope`) rather than refusing, because they are how the refusal gets fixed — so do not
178
+ gate CI on a report mode. "Is there source here" is answered by a probe the contract cannot
179
+ steer: it ignores the contract's own `exclude` (otherwise `exclude: ["**"]` buys the green back),
180
+ skips dot-directories, never follows a symlink (an unrelated out-of-root link must not turn a
181
+ diagnostic into a crash), skips `*.config.*` so a polyglot repo whose only TS is `vite.config.ts`
182
+ is greenfield rather than a mismatch, and stops at 200 files — the message says *at least N* when
183
+ it did. The whole check is evaluated lazily, so the cheap exits (a `--changed` run whose diff
184
+ touches no product path, `--require-gates` with the gates present) still pay nothing for a walk
185
+ they never needed.
186
+ - **The production diagnostic-id fixture is pinned in both directions.**
187
+ `tests/fixtures/diagnostic-catalog/production-rule-ids.json` had two tests over it and both
188
+ asserted fixture ⊆ catalog, so a production-emitted id added to the catalog and forgotten in the
189
+ fixture could not fail anything. `ARKORDER_XI_FIELD_WRITE` (emitted since 4.8.3) sat in exactly
190
+ that hole, and `ARKRULE_INVARIANT` with it. Two assertions added — every catalogued id must be
191
+ listed, and every `ARKORDER_RULE_IDS` / `ARKRUN_RULE_IDS` sensor id must be listed — and both
192
+ missing ids are now in the fixture.
193
+ - **`npm test` no longer dirties the tree.** `tests/unit/static-check/q05AiVelocity.test.ts`
194
+ spawned `eval/ai-velocity-run.mjs` with `cwd: REPO`, which rewrote the **tracked**
195
+ `eval/ai-velocity-report.json` on every run — its `generatedAt` is fresh each time, and that
196
+ pure-timestamp diff has already ridden into two commits. The harness now takes `--report` (and
197
+ `--baseline`) so the test writes into a temp dir and removes it in a `finally`, matching the
198
+ sibling test in the same file, and it now asserts both the tracked report **and** the tracked
199
+ baseline are byte-identical after the run. Seeding the baseline is now `--write-baseline` only:
200
+ it used to be written on any run where the file was missing, which is the same class of surprise
201
+ one level down. `--report` and `--baseline` must differ (aliased, the report was read back as its
202
+ own baseline and the run printed PASS without producing one) and neither may name a directory.
203
+ Default paths are unchanged for `npm run eval:ai-velocity`.
204
+ - **`coverage.maxFiles` is bounded.** It had no upper limit, and a schema `maximum` would
205
+ have been silently ignored (the config validator implements no such keyword), so any
206
+ integer became a memory budget. Clamped to 20000.
207
+ - **Files read vs files retained.** `stats.filesLoaded` counted retained files only, while
208
+ the budget diagnostic named `coverage.maxFiles` as the knob — implying it bounds I/O. It
209
+ bounds retention: a test is read before it can be judged for naming an invariant. The
210
+ scan now counts `filesRead` and the diagnostic says which of the two the cap bounds.
211
+ - **Depth-limited directories are counted once.** `'.'` overlaps `tests/` and `src/` in the
212
+ walk, so one deep subtree was reported once per overlapping root — and a directory
213
+ refused at depth from the far root was counted even when a nearer root walked it fine.
214
+ Only directories no walk ever entered are counted now.
215
+ - **Discards are counted per file, not per visit:** `coverage.testGlobs` widens the test walk to the
216
+ repo root, which overlaps the other walk roots. Each discarded file was counted — and read — once
217
+ per overlapping root, inflating every number in the diagnostic 2–3x for exactly the users who
218
+ opted into the new config.
219
+ - **The budget is reported exhausted only when it cost a file.** Landing exactly on the cap with
220
+ nothing dropped no longer tells the user to raise a cap that discarded nothing, and files past the
221
+ cap are no longer read before being discarded. Paths that were never coverage candidates
222
+ (directories, out-of-root paths) are no longer counted against the budget.
223
+ - **A git install no longer needs a build-allowlist entry — and no longer fails outright.**
224
+ `pnpm add git+https://github.com/pedroknigge/arkgate` did not install this package: it stopped
225
+ with `ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`, because `prepack: npm run build` is a build script
226
+ and pnpm refuses to run one for a git-hosted package that is not in `allowBuilds`. Reproduced on
227
+ 4.8.3 verbatim. The only way through was adding `arkgate` to `allowBuilds` — an allowlist a real
228
+ adopter reserves for native binaries with written justification, for a script that shells out to
229
+ `npm` in a repo whose first rule forbids it. They shipped a `pnpm patch` instead, which is a fair
230
+ reading of the situation and the wrong thing to have to do. Adding the "obvious" `prepare` hook
231
+ makes it strictly worse (measured: same hard error). So the package now declares **no `prepack`,
232
+ no `prepare`, and no install script at all**; the publish path builds explicitly in
233
+ `scripts/release-npm.mjs`, with `prepublishOnly` as the backstop for a bare `npm publish` — pnpm
234
+ does not run `prepublishOnly` when it prepares a git dependency, so the backstop costs the
235
+ consumer nothing. `npm pack` no longer builds as a side effect; the three callers that relied on
236
+ that now build first, and `check:package-files` turns a missing `dist/` from a warning
237
+ into an error, because that is the failure this change makes reachable.
238
+ What a git install then gives you is stated rather than implied, in the new
239
+ [Installing from git](docs/package-surface.md#installing-from-git) section and in the README:
240
+ the `arkgate` / `arkgate-check` CLIs and the `arkgate/schema*` exports work, because `bin/` and
241
+ `schemas/` are committed — that is what the zero-build CLI is for. The library exports
242
+ (`arkgate`, `/eslint`, `/order`, `/runtime`, `/nestjs`) and `ark-mcp` do **not**, because they
243
+ resolve into `dist/`, which is a build output and is not committed. `ark-mcp` now says exactly
244
+ that instead of `Run "npm run build" first`, which is not advice a consumer inside `node_modules`
245
+ can act on. Shipping the library over the git path too would mean committing build output; that
246
+ is a separate decision and is not made here.
247
+
6
248
  ## 4.8.3 — 2026-08-30
7
249
 
8
250
  **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.**
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 the current tree; **4.8.3** 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,6 +234,9 @@ 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
241
  npx arkgate-check --install-agent-gates --tools claude,cursor,codex,grok
235
242
  # optional: refresh shared home skills (Claude/Grok/Codex; never downgrades)