arkgate 4.8.2 → 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.
- package/CHANGELOG.md +257 -3
- package/README.md +47 -9
- package/bin/ark-check-runtime.mjs +340 -5
- package/bin/ark-layer-match.mjs +170 -13
- package/bin/ark-mcp-runtime.mjs +9 -2
- package/bin/lib/analysis-completeness.mjs +86 -0
- package/bin/lib/analysis-engine.mjs +6 -6
- package/bin/lib/architecture-scan.mjs +2 -0
- package/bin/lib/ark-order-facts.mjs +59 -0
- package/bin/lib/ark-order-sensors.mjs +31 -2
- package/bin/lib/arkrule-file-hints.mjs +6 -2
- package/bin/lib/arkrules-contract.mjs +9 -1
- package/bin/lib/arkrules-sensors.mjs +22 -2
- package/bin/lib/check-args.mjs +66 -0
- package/bin/lib/config-contract.mjs +26 -0
- package/bin/lib/config-extras.mjs +2 -0
- package/bin/lib/design-smells.mjs +85 -0
- package/bin/lib/diagnostic-catalog.mjs +8 -2
- package/bin/lib/first-run-help.mjs +12 -0
- package/bin/lib/invariant-coverage-io.mjs +175 -19
- package/bin/lib/invariant-coverage.mjs +110 -7
- package/bin/lib/literal-path-drift-io.mjs +569 -0
- package/bin/lib/literal-path-drift.mjs +761 -0
- package/bin/lib/policy-delta-io.mjs +5 -0
- package/bin/lib/remediation.mjs +24 -1
- package/bin/lib/resolved-candidate-facts.mjs +31 -0
- package/bin/lib/rules-under-contract.mjs +5 -0
- package/bin/lib/scan-files.mjs +54 -0
- package/bin/lib/sensor-promote-cli.mjs +372 -0
- package/bin/lib/sensor-promote-io.mjs +246 -0
- package/bin/lib/sensor-promotion.mjs +363 -0
- package/dist/{configTypes-BdCe_gvv.d.ts → configTypes-dy5PfTqS.d.ts} +36 -0
- package/dist/{diagnosticCatalog-CPzH-MLN.d.ts → diagnosticCatalog-DgTs0abp.d.ts} +169 -11
- package/dist/eslint/index.cjs +5 -5
- package/dist/eslint/index.d.ts +34 -1
- package/dist/eslint/index.js +5 -5
- package/dist/index.cjs +31 -31
- package/dist/index.d.ts +85 -7
- package/dist/index.js +31 -31
- package/dist/nestjs/index.cjs +5 -5
- package/dist/nestjs/index.d.ts +3 -3
- package/dist/nestjs/index.js +5 -5
- package/dist/runtime/index.cjs +13 -13
- package/dist/runtime/index.d.ts +6 -6
- package/dist/runtime/index.js +13 -13
- package/dist/{types-DCSlrRnV.d.ts → types-BuM8WNqe.d.ts} +1 -1
- package/dist/{types-C9KApBzX.d.ts → types-D95drJ3_.d.ts} +1 -1
- package/docs/README.md +4 -4
- package/docs/agent-guide.md +182 -0
- package/docs/configuration.md +89 -9
- package/docs/develop.md +24 -2
- package/docs/diagnostics.md +79 -1
- package/docs/enthusiast/README.md +6 -4
- package/docs/package-surface.md +36 -4
- package/docs/product-voice.md +15 -5
- package/docs/use.md +8 -5
- package/package.json +2 -2
- package/schemas/ark.arkrules.schema.json +1 -0
- package/schemas/ark.config.schema.json +72 -0
- package/schemas/ark.resolved-candidate-facts.schema.json +1 -1
- package/server.json +3 -3
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +13 -3
- package/templates/agent-skills/ark-autopilot/SKILL.md +1 -1
- package/templates/agent-skills/ark-contract/SKILL.md +4 -0
- package/templates/agent-skills/ark-coverage/SKILL.md +1 -0
- package/templates/agent-skills/ark-place/SKILL.md +6 -2
- package/templates/arkrules/ApplicationOrchestration.json +6 -0
- package/templates/skills/ark-adopt.md +13 -3
- package/templates/skills/ark-autopilot.md +1 -1
- package/templates/skills/ark-contract.md +4 -0
- package/templates/skills/ark-coverage.md +1 -0
- package/templates/skills/ark-place.md +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,13 +3,267 @@
|
|
|
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
|
+
|
|
248
|
+
## 4.8.3 — 2026-08-30
|
|
249
|
+
|
|
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.**
|
|
251
|
+
|
|
252
|
+
**Status: published** (on npm `latest`; see `docs/releases/4.8.3.md`).
|
|
253
|
+
|
|
254
|
+
### Added
|
|
255
|
+
- **`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.
|
|
256
|
+
- Application + vertical-slice Features starter rules ship the sensor advisory. `/ark-place` `/ark-adopt` `/ark-contract` name the skip. No `Externals/` / `admission.ts` folder religion.
|
|
257
|
+
- **`arkOrder.xiKeys`:** optional 3–5 slow names. Empty stays silent. A use-case that persists a named key is `ARKORDER_XI_FIELD_WRITE`. Copy billing, rename the keys. Membership ids are not keys.
|
|
258
|
+
- ArkOrder sensors `ARKORDER_TOO_MANY_PARAMS` and `ARKORDER_INGEST_WRITES_XI` now emit (they were catalog-only).
|
|
259
|
+
|
|
6
260
|
## 4.8.2 — 2026-08-30
|
|
7
261
|
|
|
8
262
|
**Patch** over **4.8.1**. Frozen 13 skills match four-plane honesty: ArkOrder on
|
|
9
263
|
adopt / place / autopilot; ArkRun `kernelRoots`; kernel import `arkgate/runtime`.
|
|
10
264
|
No `/ark-order` / `/ark-run`. Does not close `K01` / `Z09`. **No required config migration.**
|
|
11
265
|
|
|
12
|
-
**Status: published** (
|
|
266
|
+
**Status: published** (see `docs/releases/4.8.2.md`).
|
|
13
267
|
|
|
14
268
|
### Changed
|
|
15
269
|
- **Skills four-plane honesty:** existing 13 names teach Layers, ArkRules, ArkRun, and ArkOrder. Adopt (session-0: schema `1.3+`, `planeRoots`, `maxXiKeys`) and Autopilot (grind) name ArkOrder; Place hands ArkOrder grind to Autopilot. ArkRun `kernelRoots` is preferred (`compositionRoots` alias). Kernel import is `arkgate/runtime` (companion deprecated). Skills never enforce.
|
|
@@ -18,7 +272,7 @@ No `/ark-order` / `/ark-run`. Does not close `K01` / `Z09`. **No required config
|
|
|
18
272
|
|
|
19
273
|
**Patch** over **4.8.0**. ArkRules invariant coverage reads tests first and retains only files that mention a declared invariant id, so large repos no longer report `INVARIANT_UNCOVERED` / `never-had-tests` while covering tests sit on disk. Does not close `K01` / `Z09`. **No required config migration.**
|
|
20
274
|
|
|
21
|
-
**Status: published** (
|
|
275
|
+
**Status: published** (see `docs/releases/4.8.1.md`).
|
|
22
276
|
|
|
23
277
|
### Fixed
|
|
24
278
|
- **INVARIANT_UNCOVERED on large trees:** `loadInvariantCoverageInputs` spent `MAX_COVERAGE_FILES` (400) on production facts before walking tests. Any repo with more than 400 governed files got `testGlobsMissing: true` and a false *never-had-tests* claim. Tests walk first; with `invariantIds`, a test is retained only if it mentions a catalog id. Doctor and policy-delta use the same ids. When the file budget is exhausted, the diagnostic says so instead of claiming the suite never existed.
|
|
@@ -30,7 +284,7 @@ No `/ark-order` / `/ark-run`. Does not close `K01` / `Z09`. **No required config
|
|
|
30
284
|
the same tarball. `@arkgate/runtime` is **deprecated**. Does not close `K01` / `Z09`.
|
|
31
285
|
ArkRules unchanged.
|
|
32
286
|
|
|
33
|
-
**Status: published** (
|
|
287
|
+
**Status: published** (see `docs/releases/4.8.0.md`).
|
|
34
288
|
|
|
35
289
|
### Added
|
|
36
290
|
- **ArkOrder extra (`arkOrder`):** optional, silent when absent. Enforced skip: missing plane, Domain import of `arkgate/order`, generic `update` of ξ.
|
package/README.md
CHANGED
|
@@ -29,11 +29,13 @@ Works with Cursor, Claude, Codex, and Grok.
|
|
|
29
29
|
|
|
30
30
|
</div>
|
|
31
31
|
|
|
32
|
-
> **ArkGate 4.8.
|
|
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
|
-
> (`arkgate/runtime`) is an in-memory runtime — not Postgres.
|
|
36
|
-
>
|
|
35
|
+
> (`arkgate/runtime`) is an in-memory runtime — not Postgres. Optional **ArkOrder**
|
|
36
|
+
> (`arkgate/order`) stops the agent rewriting the few slow product decisions as CRUD.
|
|
37
|
+
> `@arkgate/runtime` is deprecated.
|
|
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)
|
|
37
39
|
|
|
38
40
|
---
|
|
39
41
|
|
|
@@ -69,7 +71,11 @@ start → doctor → new files in the right folder
|
|
|
69
71
|
Keep the rules file out of product PRs. Local check:
|
|
70
72
|
`ark-check --changed --base origin/dev`. Changing the rules themselves uses `--contract-session`.
|
|
71
73
|
|
|
72
|
-
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).
|
|
73
79
|
|
|
74
80
|

|
|
75
81
|
|
|
@@ -90,7 +96,7 @@ Aliases `ark` / `ark-check` / `ark-mcp` still work. npm / pnpm / yarn. No instal
|
|
|
90
96
|
|
|
91
97
|
When the agent writes a bad import, the write doesn’t land.
|
|
92
98
|
The same check fails the pull request. That is **ArkGate** — import rules, always on
|
|
93
|
-
once you adopt. The other
|
|
99
|
+
once you adopt. The other three are optional.
|
|
94
100
|
|
|
95
101
|
| | Role | When |
|
|
96
102
|
|--|------|------|
|
|
@@ -98,13 +104,17 @@ once you adopt. The other two are optional.
|
|
|
98
104
|
| **Before merge** | `arkgate-check` as a **required** CI status | Always (ArkGate) |
|
|
99
105
|
| **ArkRules** | Optional policies *inside* a layer | When you ask |
|
|
100
106
|
| **ArkRun** | Optional experimental runtime (`arkgate/runtime`) | Off unless you turn it on |
|
|
107
|
+
| **ArkOrder** | Stops the agent rewriting the few slow product decisions as CRUD (`arkgate/order`) | Off unless you turn it on |
|
|
101
108
|
|
|
102
|
-
Layers (who may import whom) always run. ArkRules and
|
|
103
|
-
verdict when absent. Label leftovers **`[Layer]`** vs **`[ArkRules]
|
|
109
|
+
Layers (who may import whom) always run. ArkRules, ArkRun, and ArkOrder change no
|
|
110
|
+
inter-layer verdict when absent. Label leftovers **`[Layer]`** vs **`[ArkRules]`** vs
|
|
111
|
+
**`[ArkRun]`** vs **`[ArkOrder]`**.
|
|
104
112
|
Details: [configuration](docs/configuration.md) · [use](docs/use.md).
|
|
105
113
|
|
|
106
114
|
**Not** an API Gateway, a folder linter, a web framework, ORM, or job runner.
|
|
107
|
-
ArkRun is in-memory — local and tests, not Postgres.
|
|
115
|
+
ArkRun is in-memory — local and tests, not Postgres. ArkOrder does not replace
|
|
116
|
+
import rules: layers can be green while the agent still PATCHes the billing plan
|
|
117
|
+
like a seat count.
|
|
108
118
|
|
|
109
119
|
**Name note:** npm package `arkgate` — not affiliated with the separate Archgate CLI project.
|
|
110
120
|
|
|
@@ -211,6 +221,7 @@ expectation.
|
|
|
211
221
|
| Placement + preflight for multi-file changes | ✅ | ❌ |
|
|
212
222
|
| Honest governed % + dual plan (edges vs shape) | ✅ | ❌ |
|
|
213
223
|
| Opt-in intra-layer ArkRules (structure + invariants) | ✅ | ❌ |
|
|
224
|
+
| Stops agents rewriting slow product decisions as CRUD (ArkOrder) | ✅ | ❌ |
|
|
214
225
|
| Incomplete analysis cannot look green | ✅ | varies |
|
|
215
226
|
|
|
216
227
|
---
|
|
@@ -223,6 +234,9 @@ npx arkgate status --json # session/project snapshot (identity, activat
|
|
|
223
234
|
npx arkgate-check --doctor
|
|
224
235
|
npx arkgate-check --plan
|
|
225
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
|
|
226
240
|
npx arkgate-check --strict-merge # CI / required status
|
|
227
241
|
npx arkgate-check --install-agent-gates --tools claude,cursor,codex,grok
|
|
228
242
|
# optional: refresh shared home skills (Claude/Grok/Codex; never downgrades)
|
|
@@ -254,6 +268,29 @@ interfaces for production. Details: [docs/production-hardening.md](docs/producti
|
|
|
254
268
|
|
|
255
269
|
---
|
|
256
270
|
|
|
271
|
+
## Optional ArkOrder
|
|
272
|
+
|
|
273
|
+
Layers stop a bad import. They do not stop a *legal* import that overwrites the
|
|
274
|
+
billing plan.
|
|
275
|
+
|
|
276
|
+
If the product can name a few slow decisions in an afternoon — plan, cycle,
|
|
277
|
+
tenancy; a clinical protocol; match rules — an agent will still ship one PUT
|
|
278
|
+
that changes them together with seats and invoices. The write gate stays green
|
|
279
|
+
because “what may be the plan” was never a rule.
|
|
280
|
+
|
|
281
|
+
**ArkOrder** (`arkgate/order`) is that rule. Off unless you add `arkOrder`.
|
|
282
|
+
Name the slow keys (`xiKeys`: plan, protocol, cost-code bound — not `projectId`).
|
|
283
|
+
Posting an invoice is absorbed. Changing plan is a new release, with a blast
|
|
284
|
+
radius. A generic `update` of the plan does not land. A use-case that PATCHes
|
|
285
|
+
those keys through Prisma is named. Same npm package.
|
|
286
|
+
In-memory. Not durable. Does not replace ArkRun.
|
|
287
|
+
|
|
288
|
+
Copy [examples/arkorder-billing/](examples/arkorder-billing/) and rename the
|
|
289
|
+
three keys. Compact starters leave it off. Details:
|
|
290
|
+
[configuration](docs/configuration.md) · [package surface](docs/package-surface.md).
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
257
294
|
## Documentation
|
|
258
295
|
|
|
259
296
|
| Audience | Link |
|
|
@@ -266,7 +303,8 @@ interfaces for production. Details: [docs/production-hardening.md](docs/producti
|
|
|
266
303
|
| Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
|
|
267
304
|
| Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
|
|
268
305
|
| Security | [SECURITY.md](SECURITY.md) |
|
|
269
|
-
| Current published (4.8.
|
|
306
|
+
| Current published (4.8.3 on npm `latest`) | [docs/releases/4.8.3.md](docs/releases/4.8.3.md) · [CHANGELOG](CHANGELOG.md) |
|
|
307
|
+
| Prior published (4.8.2) | [docs/releases/4.8.2.md](docs/releases/4.8.2.md) |
|
|
270
308
|
| Prior published (4.8.1) | [docs/releases/4.8.1.md](docs/releases/4.8.1.md) |
|
|
271
309
|
| Prior published (4.8.0) | [docs/releases/4.8.0.md](docs/releases/4.8.0.md) |
|
|
272
310
|
| Prior published (4.7.6) | [docs/releases/4.7.6.md](docs/releases/4.7.6.md) |
|