arkgate 4.8.19 → 4.8.21

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 (85) hide show
  1. package/CHANGELOG.md +220 -0
  2. package/README.md +107 -117
  3. package/bin/ark-check-runtime.mjs +26 -4
  4. package/bin/ark-layer-match.mjs +236 -12
  5. package/bin/ark.mjs +6 -2
  6. package/bin/lib/analysis-engine.mjs +13 -6
  7. package/bin/lib/config-contract.mjs +6 -0
  8. package/bin/lib/diagnostic-catalog.mjs +1 -0
  9. package/bin/lib/doctor-advisories.mjs +22 -1
  10. package/bin/lib/doctor-human.mjs +11 -9
  11. package/bin/lib/doctor-plan.mjs +17 -14
  12. package/bin/lib/enforcement-honesty.mjs +30 -2
  13. package/bin/lib/first-run-help.mjs +7 -1
  14. package/bin/lib/html-report-advisories.mjs +8 -0
  15. package/bin/lib/html-report-depth.mjs +12 -7
  16. package/bin/lib/invariant-coverage.mjs +406 -54
  17. package/bin/lib/managed-upgrade.mjs +59 -14
  18. package/bin/lib/package-manager.mjs +5 -2
  19. package/bin/lib/physical-cohesion.mjs +68 -51
  20. package/bin/lib/pilot-loop.mjs +183 -37
  21. package/bin/lib/reshape-decisions.mjs +2 -2
  22. package/bin/lib/rules-under-contract.mjs +40 -4
  23. package/bin/lib/start-install-recovery.mjs +156 -0
  24. package/bin/lib/start-preview.mjs +0 -54
  25. package/bin/lib/upgrade-behavior-changes.mjs +83 -0
  26. package/bin/lib/upgrade-command.mjs +69 -31
  27. package/bin/lib/upgrade-outcome.mjs +359 -0
  28. package/bin/lib/violations.mjs +109 -0
  29. package/dist/{configTypes-VD0qcubY.d.ts → configTypes-CTELo87-.d.ts} +9 -0
  30. package/dist/{diagnosticCatalog-D2v0ASN2.d.ts → diagnosticCatalog-B7BXlet6.d.ts} +79 -5
  31. package/dist/eslint/index.cjs +4 -4
  32. package/dist/eslint/index.d.ts +35 -4
  33. package/dist/eslint/index.js +4 -4
  34. package/dist/index.cjs +25 -18
  35. package/dist/index.d.ts +13 -4
  36. package/dist/index.js +27 -20
  37. package/dist/nestjs/index.cjs +9 -9
  38. package/dist/nestjs/index.d.ts +3 -3
  39. package/dist/nestjs/index.js +9 -9
  40. package/dist/runtime/index.cjs +18 -11
  41. package/dist/runtime/index.d.ts +6 -6
  42. package/dist/runtime/index.js +18 -11
  43. package/dist/{types-D5GT5ZT8.d.ts → types-fQ9-KrBj.d.ts} +1 -1
  44. package/dist/{types-BSzRy2X1.d.ts → types-yDNEQ603.d.ts} +1 -1
  45. package/docs/README.md +4 -3
  46. package/docs/agent-guide.md +15 -7
  47. package/docs/brownfield-adoption.md +4 -3
  48. package/docs/configuration.md +46 -8
  49. package/docs/develop.md +1 -1
  50. package/docs/diagnostics.md +12 -2
  51. package/docs/package-surface.md +4 -3
  52. package/docs/product-voice.md +11 -3
  53. package/docs/use.md +1 -1
  54. package/package.json +3 -3
  55. package/schemas/ark.config.schema.json +14 -0
  56. package/server.json +2 -2
  57. package/templates/agent-skills/README.md +1 -1
  58. package/templates/agent-skills/ark-adopt/SKILL.md +18 -1
  59. package/templates/agent-skills/ark-architect/SKILL.md +2 -0
  60. package/templates/agent-skills/ark-autopilot/SKILL.md +6 -2
  61. package/templates/agent-skills/ark-contract/SKILL.md +2 -0
  62. package/templates/agent-skills/ark-coverage/SKILL.md +3 -1
  63. package/templates/agent-skills/ark-explain/SKILL.md +3 -1
  64. package/templates/agent-skills/ark-explore/SKILL.md +15 -1
  65. package/templates/agent-skills/ark-fix/SKILL.md +2 -0
  66. package/templates/agent-skills/ark-loop/SKILL.md +2 -0
  67. package/templates/agent-skills/ark-order/SKILL.md +2 -0
  68. package/templates/agent-skills/ark-place/SKILL.md +2 -0
  69. package/templates/agent-skills/ark-runtime/SKILL.md +2 -0
  70. package/templates/agent-skills/ark-think/SKILL.md +2 -0
  71. package/templates/agent-skills/ark-upgrade/SKILL.md +25 -10
  72. package/templates/skills/ark-adopt.md +18 -1
  73. package/templates/skills/ark-architect.md +2 -0
  74. package/templates/skills/ark-autopilot.md +6 -2
  75. package/templates/skills/ark-contract.md +2 -0
  76. package/templates/skills/ark-coverage.md +3 -1
  77. package/templates/skills/ark-explain.md +3 -1
  78. package/templates/skills/ark-explore.md +15 -1
  79. package/templates/skills/ark-fix.md +2 -0
  80. package/templates/skills/ark-loop.md +2 -0
  81. package/templates/skills/ark-order.md +2 -0
  82. package/templates/skills/ark-place.md +2 -0
  83. package/templates/skills/ark-runtime.md +2 -0
  84. package/templates/skills/ark-think.md +2 -0
  85. package/templates/skills/ark-upgrade.md +25 -10
@@ -1,4 +1,4 @@
1
- import { a as ArkConfigRule, f as ArkConfigLayer, A as ArkConfig } from './configTypes-VD0qcubY.js';
1
+ import { a as ArkConfigRule, f as ArkConfigLayer, A as ArkConfig } from './configTypes-CTELo87-.js';
2
2
 
3
3
  /**
4
4
  * Policy types for the Ark kernel.
@@ -1,4 +1,4 @@
1
- import { i as Policy, P as PolicyViolation, I as IntentName, j as IntentCreator, k as IntentRelationship, b as ArchitectureProfile, D as DomainEvent, E as EventMetadata, h as PolicyEnforcementMode, A as ArchitectureLayer, c as ArchitectureRule, d as ArkCheckConfig } from './types-D5GT5ZT8.js';
1
+ import { i as Policy, P as PolicyViolation, I as IntentName, j as IntentCreator, k as IntentRelationship, b as ArchitectureProfile, D as DomainEvent, E as EventMetadata, h as PolicyEnforcementMode, A as ArchitectureLayer, c as ArchitectureRule, d as ArkCheckConfig } from './types-fQ9-KrBj.js';
2
2
 
3
3
  /**
4
4
  * PolicyEngine
package/docs/README.md CHANGED
@@ -73,12 +73,13 @@ These are **not** the day-to-day product path. They stay in the repo for evidenc
73
73
  | Field adoption kit (scaffolding, not closed) | [field/](field/) |
74
74
  | Runtime hardening (experimental) | [production-hardening.md](production-hardening.md) |
75
75
 
76
- Current published: [CHANGELOG](../CHANGELOG.md) (`arkgate@4.8.19` on npm `latest`; does not close `K01`).
76
+ Current release: [CHANGELOG](../CHANGELOG.md) (`arkgate@4.8.21`; confirm npm with `npm view arkgate@4.8.21 version`; does not close `K01`).
77
+ Prior published: [CHANGELOG](../CHANGELOG.md) (`arkgate@4.8.20`). Plain-English history: [arkgate.online/changelog](https://www.arkgate.online/changelog/).
77
78
  Prior published: [releases/4.8.11.md](releases/4.8.11.md) (`arkgate@4.8.11`).
78
79
  Prior published: [releases/4.8.10.md](releases/4.8.10.md) (`arkgate@4.8.10`).
79
80
  Prior published: [releases/4.8.9.md](releases/4.8.9.md) (`arkgate@4.8.9`).
80
- Prior: [releases/4.8.6.md](releases/4.8.6.md) · [releases/4.8.5.md](releases/4.8.5.md) · [releases/4.8.4.md](releases/4.8.4.md) · [releases/4.8.3.md](releases/4.8.3.md) · [releases/4.8.2.md](releases/4.8.2.md) · [releases/4.8.1.md](releases/4.8.1.md) · [4.8.0](releases/4.8.0.md) · [4.7.6](releases/4.7.6.md) · [4.7.5](releases/4.7.5.md) · [4.7.4](releases/4.7.4.md) · [4.7.3](releases/4.7.3.md) · [4.7.2](releases/4.7.2.md) · [4.7.1](releases/4.7.1.md) · [4.7.0](releases/4.7.0.md) · [4.6.7](releases/4.6.7.md) · [4.6.6](releases/4.6.6.md) · [4.6.5](releases/4.6.5.md) · [4.6.4](releases/4.6.4.md) · [4.6.3](releases/4.6.3.md) · [4.6.2](releases/4.6.2.md) · [4.6.1](releases/4.6.1.md) · [4.6.0](releases/4.6.0.md).
81
- Older notes: [releases/](releases/). Config: [configuration.md](configuration.md).
81
+ Prior: [releases/4.8.8.md](releases/4.8.8.md) · [releases/4.8.7.md](releases/4.8.7.md) · [releases/4.8.6.md](releases/4.8.6.md) · [releases/4.8.5.md](releases/4.8.5.md) · [releases/4.8.4.md](releases/4.8.4.md) · [releases/4.8.3.md](releases/4.8.3.md) · [releases/4.8.2.md](releases/4.8.2.md) · [releases/4.8.1.md](releases/4.8.1.md) · [4.8.0](releases/4.8.0.md) · [4.7.6](releases/4.7.6.md) · [4.7.5](releases/4.7.5.md) · [4.7.4](releases/4.7.4.md) · [4.7.3](releases/4.7.3.md) · [4.7.2](releases/4.7.2.md) · [4.7.1](releases/4.7.1.md) · [4.7.0](releases/4.7.0.md) · [4.6.7](releases/4.6.7.md) · [4.6.6](releases/4.6.6.md) · [4.6.5](releases/4.6.5.md) · [4.6.4](releases/4.6.4.md) · [4.6.3](releases/4.6.3.md) · [4.6.2](releases/4.6.2.md) · [4.6.1](releases/4.6.1.md) · [4.6.0](releases/4.6.0.md).
82
+ Older notes: [releases/4.5.7.md](releases/4.5.7.md) · [releases/4.5.6.md](releases/4.5.6.md) · [releases/4.5.5.md](releases/4.5.5.md) · [releases/](releases/). Config: [configuration.md](configuration.md).
82
83
 
83
84
  ---
84
85
 
@@ -161,6 +161,8 @@ npx arkgate upgrade --apply # registry-aware package step, then re-previ
161
161
  npx arkgate upgrade --apply --no-install --plan-digest sha256:… --refresh-skills
162
162
  ```
163
163
 
164
+ `upgrade --apply` exits `0` when the managed files were applied and the architecture check is green, `3` when they were applied but the check is red, `2` when a recorded file still needs consent, and `1` on error (including a stale plan digest). Scripts comparing the status against `1` must switch to non-zero. JSON adds `outcome` and `postUpgrade` (`verdict`, `failing[{ruleId,count,sample}]`, `behaviorChanges`). `behaviorChanges` is the shipped per-version table, the same sentences as the changelog migration notes. A missing gitignored managed file is `absent-local` and is recreated without `--accept-conflicts`. Every blocked asset has `reason` and `nextCommand`. If `postUpgrade.verdict` is `red`, stop: report `failing` and `behaviorChanges`, and do not edit tests, titles, or config in that turn.
165
+
164
166
  ### Two-axis done recipe
165
167
 
166
168
  Architecture residual and feature residual are **separate axes**. Never collapse them:
@@ -361,13 +363,16 @@ npx ark-check --doctor --fail-on-new-smells --base-ref origin/main --json # opt-
361
363
  (`clarify-for-ai`) — **one** Shape door: `/ark-explore` shape-focus → dual-plan B, then
362
364
  `/ark-autopilot` applies one pilot. Do not skill-shop coverage/think for the same residual.
363
365
 
364
- **Pilot loop (Q04):** when design-weak, `pilotLoop.nextPilot` is **one** extraction card
365
- (pilot target, move, success, kill-switch). Apply **that one pilot only**, then re-doctor.
366
+ **Pilot loop (Q04):** `pilotLoop` is active when a pilot candidate exists. Design-weak
367
+ pattern bets are one source. A proposed physical-cohesion reshape card is the other, even
368
+ when the tree is not design-weak. `pilotLoop.nextPilot` / `extractionCard` is **one**
369
+ extraction card (pilot target, move, success, kill-switch). Apply **that one pilot only**,
370
+ then re-doctor. Further candidates stay queued. Inactive reason is `no-pilot-candidates`.
366
371
  Success = reduced smell evidence on pilot paths; residual outside the pilot may remain.
367
372
  Never select seed/fixture/demo/migration/generated files as god-module pilots. A real UI business
368
373
  rule moves Domain → Application → UI; local permission/UI-state helpers are not selected by their
369
374
  `canEdit`-style name alone. Never multi-pilot batch; never mechanical-safe; never claim healthy
370
- finished while design-weak.
375
+ finished while `doctor.productHonesty.finished` is false.
371
376
 
372
377
  **AI-velocity evidence (Q05):** deterministic fixture bench (no live LLM) compares the same
373
378
  feature add on design-weak vs golden-path trees. Run `npm run eval:ai-velocity`; metric is
@@ -432,9 +437,11 @@ port. Advisory only — never blocks, never feeds `designFitness`; no strict mod
432
437
  clusters per anchor directory (concentration, not volume — dispersed hooks never fire) with
433
438
  fixed corpus-calibrated thresholds; anchors under `app/`/`pages/` are `fixedByConvention` and
434
439
  never move. `reshapePilot.nextPilot` is a **proposed** one-at-a-time card (`moveSample`,
435
- `movesTotal`, `successSignal`, `killSwitch`, `doNot[]`): run it only via `/ark-loop` through the
436
- write gate + atomic preflight; merges are `/ark-adopt` / `/ark-autopilot` judgment cards. `notAScore`, never a
437
- verdict/`designFitness` input; there is no apply path.
440
+ `movesTotal`, `successSignal`, `killSwitch`, `doNot[]`) and, when proposed, the single
441
+ `pilotLoop` extraction card if no design-weak pattern bet is ahead of it. Run it only via
442
+ `/ark-loop` through the write gate + atomic preflight; merges are `/ark-adopt` /
443
+ `/ark-autopilot` judgment cards. `notAScore`, never a verdict/`designFitness` input; there
444
+ is no apply path.
438
445
 
439
446
  **Reshape decision memory (Y01):** when the team accepts, defers, or rejects that target, record
440
447
  the explicit verdict in `.ark/reshape-decisions.json` using the card's exact
@@ -1135,7 +1142,8 @@ edges are not denied by that rule.
1135
1142
 
1136
1143
  - **Denied:** `src/features/auth/**` → `src/features/payments/**` (different slice id).
1137
1144
  - **Allowed:** same-slice imports when both paths classify; classic non-peerIsolation denies still apply across layers.
1138
- - **`sliceFolders`:** optional parent segments (default: inferred from layer globs).
1145
+ - **`sliceFolders`:** optional parent segments (default: inferred from layer globs). A bare name stays an unanchored one-segment match (`features` on `src/features/auth` is `features/auth`). A starred anchored pattern with `sliceIdentity` `path` (the default) includes the literal prefix plus the star bindings (`lib/features/*/*` → `lib/features/projects/rfi`), so parallel trees get different ids.
1146
+ - **`sliceIdentity`:** `path` (default, also when the key is absent) or `stars`. `path` keeps today's slice ids. `stars` names a starred prefix as the last literal plus the star bindings (`lib/features/*/*` and `lib/repositories/features/*/*` both yield `features/projects/rfi`). Doctor names both prefixes when they collapse onto one id. Additive — no migration.
1139
1147
  - **Fail-closed:** missing paths, empty/unresolvable slice folders, or unclassifiable either side → **deny** via peerIsolation (cannot prove same-slice).
1140
1148
  - **`sharedRoots`** (4.8.4): roots the repo declares shared on purpose (`["ui", "hooks", "lib/permissions"]`). A file under a declared shared root is evidence, not an unclassifiable path, so fail-closed stops firing on every shared file. **Anchored** — the root starts the path, optionally after one `src/` or `app/`; write deeper or monorepo roots out (`packages/web/src/ui`) or glob them, and a bare `*` / `**` is refused. A path that still resolves to a slice keeps its slice.
1141
1149
  - **`allowedCrossSlice`** (4.8.4): `[{ "from": "features/checkout", "to": "features/catalog" }]` — one directed slice→slice edge the repo declares on purpose. The reverse still denies.
@@ -228,12 +228,13 @@ ark-check --plan --json # patternBets[] with neverMechanicalSafe: true
228
228
 
229
229
  ### Pilot loop (Q04) — one pilot at a time → re-doctor
230
230
 
231
- When doctor/plan report **ENFORCE · design-weak**, JSON also includes **`pilotLoop`**:
231
+ When a pilot candidate exists, doctor/plan JSON includes **`pilotLoop`**:
232
232
 
233
233
  | Field | Meaning |
234
234
  |-------|---------|
235
- | `pilotLoop.active` | `true` when design-weak and at least one pattern bet exists |
236
- | `pilotLoop.nextPilot` | **One** extraction card (same fields as §6) ranked from `patternBets` |
235
+ | `pilotLoop.active` | `true` when a candidate exists: a design-weak pattern bet, or a proposed reshape card |
236
+ | `pilotLoop.nextPilot` / `extractionCard` | **One** extraction card (same fields as §6). Further candidates stay queued |
237
+ | `pilotLoop.reason` | `no-pilot-candidates` when the list is empty |
237
238
  | `pilotLoop.oneAtATime` | Always true — do not multi-pilot batch |
238
239
  | `pilotLoop.neverMechanicalSafe` | Always true — judgment only; re-doctor is the success sensor |
239
240
 
@@ -114,7 +114,10 @@ Top-level fields:
114
114
  Nothing is dropped in silence: files past the budget, files over the 256KB per-file cap,
115
115
  unreadable files or directories (permissions, broken symlinks), directories past the walk depth
116
116
  limit (8), symlinks whose target resolves outside the project root, and tests naming no
117
- catalogued invariant are each counted and named in the diagnostic. A symlinked test is read only
117
+ catalogued invariant are each counted and named in the diagnostic. Those same counts are printed
118
+ on a green doctor ArkRules line and on `--rules-inventory`, not only inside a failing
119
+ `INVARIANT_UNCOVERED` sentence. The walk-depth and per-file byte caps stay internal — they are
120
+ not config keys. A symlinked test is read only
118
121
  when its target is inside the root: a file that is not in this repo never proves an invariant
119
122
  covered.
120
123
 
@@ -247,18 +250,50 @@ process module-capability family must be denied.
247
250
 
248
251
  Rule fields:
249
252
 
250
- - `from`, `to`, `allowed`, `message`, `peerIsolation`, `sliceFolders`, `sharedRoots`,
251
- `allowedCrossSlice`
253
+ - `from`, `to`, `allowed`, `message`, `peerIsolation`, `sliceFolders`, `sliceIdentity`,
254
+ `sharedRoots`, `allowedCrossSlice`, `sharedImportsSlice`
252
255
  - `peerIsolation: true` + `allowed: false`: deny only when slice ids differ; same-slice allows
253
256
  when both paths classify. Applies to **any** declared `from`→`to` pair, not only self-edges.
254
257
  Missing paths, empty slice folders, or unclassifiable slices **fail closed** (deny — cannot
255
258
  prove same-slice).
259
+ - `sliceFolders` is a bare name or a starred prefix. A bare name (`features`) stays an
260
+ unanchored one-segment match: `src/lib/features/projects/rfi/x.ts` with `["features"]` is
261
+ `features/projects`. The next segment is never a filename, so
262
+ `src/components/features/projects/rfi-status-pill.tsx` with `["projects"]` is not a slice.
263
+ A starred prefix (`lib/features/*/*`) is anchored like `sharedRoots` — it starts the
264
+ repo-relative path, or sits one segment in after `src/` or `app/`. Each `*` binds one
265
+ directory and never the filename. With `sliceIdentity` `path` (the default, also
266
+ when the key is absent) the slice id includes the literal prefix plus those
267
+ star bindings (`lib/features/projects/rfi`), so parallel trees get different ids.
268
+ A file directly under `lib/features/projects/` stays `lib/features/projects`. A path
269
+ off that prefix (`src/app/api/projects/...`) is not that slice.
270
+ - `sliceIdentity` is `path` or `stars`. Absent and `path` are the same: today's ids,
271
+ byte for byte, so baselines and `allowedCrossSlice` keep working with no migration.
272
+ `stars` names a starred prefix as the last literal plus the star bindings.
273
+ `lib/features/*/*` and `lib/repositories/features/*/*` both yield
274
+ `features/projects/rfi`, so one feature has one id across parallel trees. Bare names
275
+ (`features`) are unchanged. Doctor warns when two different prefixes bind as the
276
+ same id, and the warning names both paths (`admin/features/*` and `public/features/*`
277
+ both bind as `features/*`). That warning is advisory. Unrelated trees that share a
278
+ last folder name should stay on `path`.
279
+
280
+ ```jsonc
281
+ {
282
+ "from": "ApplicationOrchestration",
283
+ "to": "PersistenceAdapters",
284
+ "allowed": false,
285
+ "peerIsolation": true,
286
+ "sliceFolders": ["lib/features/*/*", "lib/repositories/features/*/*"],
287
+ "sliceIdentity": "stars"
288
+ }
289
+ ```
256
290
 
257
291
  #### Cross-layer slice walls (already in the engine)
258
292
 
259
293
  A slice wall on a cross-layer edge is a `peerIsolation` rule on that `from`/`to` pair
260
- (`allowed: false`). There is no slice-wide engine mode, no new config key, and no new skill
261
- name. `findDeniedEdgeDecision` already applies `peerIsolation` to any declared pair
294
+ (`allowed: false`). There is no slice-wide engine mode and no new skill name.
295
+ `sliceIdentity` only changes how a starred prefix is named. `findDeniedEdgeDecision`
296
+ already applies `peerIsolation` to any declared pair
262
297
  (same-layer or cross-layer) — locked on `EdgeRule` and `findDeniedEdgeDecision` in
263
298
  [`src/domain/layerMatch.ts`](../src/domain/layerMatch.ts). There is no dedicated ADR to add.
264
299
 
@@ -306,8 +341,11 @@ is evidence:
306
341
  tree you never declared. Write a deeper or monorepo root out (`packages/web/src/ui`) or glob it
307
342
  (`packages/*/src/ui`). Matching is case-insensitive; a bare `*` or `**` is refused, because one
308
343
  character must not disable fail-closed. A path that still resolves to a slice keeps its slice —
309
- `features/auth/ui/form.tsx` stays `features/auth` — so a shared root can never launder a real
310
- cross-slice edge.
344
+ `features/auth/ui/form.tsx` stays `features/auth`. Shared roots are a sink: a slice may import
345
+ a shared root, and a shared root may import a slice unless the rule sets `sharedImportsSlice`
346
+ to `"deny"`. That hop is direct only — `allowedCrossSlice` does not excuse it. A green
347
+ `ark-check` prints one line per layer edge (the count and three examples) and says the
348
+ wall is direct-only. Every edge stays in `ark-check --json` and `ark-check --doctor`.
311
349
  - `allowedCrossSlice` entries match a full slice id (`features/catalog`) or a bare slice name
312
350
  (`catalog`), and only in the direction written. The reverse edge still denies. A bare name
313
351
  matches that name under **any** slice folder, so in a repo with several slice parents
@@ -398,7 +436,7 @@ Each `arkrules/<Layer>.json` may declare:
398
436
  | Section | Purpose | Modes | What it really enforces |
399
437
  |---------|---------|--------|-------------------------|
400
438
  | `structure[]` | Closed sensor ids (e.g. `orchestration-only`, `thin-adapter`, `writes-via-aggregate`, `aggregate-private-state`, `always-valid-factory`, `domain-event-on-mutation`, `no-anemic-model`) | `advisory` (default) or `enforced` | **Heuristics of module shape** — not proof that logic was extracted to Domain. `writes-via-aggregate` is driver-import + write-token in the declaring layer (ADR 0032). Tier-2 sensors (`no-anemic-model`) stay advisory-only (cannot promote to enforced). |
401
- | `invariants[]` | Stable ids + description + `coverage` (`test` / `symbol`) + optional `appliesTo` globs | `advisory` or `enforced` | **Named policy + evidence** (symbol in source and/or test title/content). Does **not** execute business logic at check time and does **not** replace behavior/property tests. |
439
+ | `invariants[]` | Stable ids + description + `coverage` (`test` / `symbol`) + optional `appliesTo` globs | `advisory` or `enforced` | **Named policy + evidence.** `coverage.symbol` means a declaration of that identifier in a non-test file (witness path `symbolEvidenceFile`; additive `shape`: `function`, `class`, `const`, `method`, `type`, `interface`, or `enum`). Imports and test calls do not count. A `describe` / `it` / `test` / `context` title may also name the invariant id. A comment, string, or other bare mention does not. Does **not** execute tests or business logic and does **not** replace behavior/property tests. Doctor and `--rules-inventory` print the witness path and discard counts on green runs too. A declaration match is not “the tests pass.” |
402
440
 
403
441
  **Reporting:** diagnostics carry `evidence.arkruleId` + `evidence.arkruleSource`. Label residual
404
442
  **`[Layer]`** vs **`[ArkRules]`** in agent output. Doctor / HTML: `rulesUnderContract` (catalog +
package/docs/develop.md CHANGED
@@ -39,7 +39,7 @@ npx arkgate start --apply
39
39
  npx --package=arkgate arkgate-check --doctor
40
40
  ```
41
41
 
42
- `start --apply` pins arkgate with this repo's package manager (`pnpm add -w` / `yarn add -W` at a workspace root). Do not run `npm install -D arkgate` at a `workspace:*` root — npm rejects that protocol. `arkgate-check` is a command in the `arkgate` package. After a local install, `npx arkgate-check --doctor` also works.
42
+ `start --apply` pins arkgate with this repo's package manager (`pnpm add -w` / `yarn add -W` at a workspace root). Do not run `npm install -D arkgate` at a `workspace:*` root — npm rejects that protocol. `arkgate-check` is a command in the `arkgate` package. After a local install, `npx arkgate-check --doctor` also works. If pnpm Age (`minimumReleaseAge`) blocks a brand-new release, keep using `npx --package=arkgate@<version>` — do not re-run the same add. Put arkgate on `minimumReleaseAgeExclude` in pnpm config or `pnpm-workspace.yaml` (a CLI flag is not enough), or wait until the package is mature.
43
43
 
44
44
  Make the import-rules check a **required** merge **status context** (GitHub/GitLab/etc.). The CLI
45
45
  command is `arkgate-check --strict-merge` / `ark-check --strict-merge` — the hard boundary is
@@ -101,6 +101,7 @@ Link form for agents: `docs/diagnostics.md#RULE_ID` (exact-case HTML anchors bel
101
101
  | [`CONFIG_AMBIGUOUS_LAYERS`](#CONFIG_AMBIGUOUS_LAYERS) | config | Ambiguous layer classification |
102
102
  | [`CONFIG_UNCLASSIFIED_FILES`](#CONFIG_UNCLASSIFIED_FILES) | config | Unclassified included files |
103
103
  | [`CONFIG_LAYER_MISSING_OWNER`](#CONFIG_LAYER_MISSING_OWNER) | config | Layer missing owner |
104
+ | [`CONFIG_SLICE_IDENTITY_COLLISION`](#CONFIG_SLICE_IDENTITY_COLLISION) | config | Starred slice prefixes share one id |
104
105
  | [`ARK_UNKNOWN`](#ARK_UNKNOWN) | meta | Unknown diagnostic |
105
106
 
106
107
  ## Layer and dependency graph
@@ -308,8 +309,8 @@ Link form for agents: `docs/diagnostics.md#RULE_ID` (exact-case HTML anchors bel
308
309
 
309
310
  **Invariant without coverage evidence**
310
311
 
311
- - **Why:** An ArkRules invariant is under contract but no covering test title or declared symbol evidence was found (or coverage is partial). Kind is `never-had-tests` (adopt residual) vs `tests-disappeared` (suite exists).
312
- - **Fix:** Add a test title or declared symbol covering the arkruleId, then preflight again. Treat never-had-tests as adopt residual; treat tests-disappeared as a regression. Missing test globs report partial — never fake green. When the message reports an exhausted file budget, raise `coverage.maxFiles` (or narrow `coverage.testGlobs`) in ark.config.json. The message also names every file the scan discarded and why (budget, per-file byte cap, unreadable, walk depth limit, symlink resolving outside the project root, no catalogued invariant named) — nothing is dropped in silence.
312
+ - **Why:** An ArkRules invariant is under contract but no covering test title or declared symbol evidence was found (or coverage is partial). A title is a `describe` / `it` / `test` / `context` string. A comment, string, test body, or import that only mentions the id does not count. `coverage.symbol` is a non-test declaration: `function`, `class`, `const`, `method`, `type`, `interface`, or `enum`. Kind is `never-had-tests` (adopt residual) vs `tests-disappeared` (suite exists). The message names what the scan found.
313
+ - **Fix:** Put the invariant id in a `describe` / `it` title, or declare the `coverage.symbol` name in a non-test file, then preflight again. A comment is not a title — move the id into the `it()` title. Treat never-had-tests as adopt residual; treat tests-disappeared as a regression. Missing test globs report partial — never fake green. When the message reports an exhausted file budget, raise `coverage.maxFiles` (or narrow `coverage.testGlobs`) in ark.config.json. The message also names every file the scan discarded and why (budget, per-file byte cap, unreadable, walk depth limit, symlink resolving outside the project root, no catalogued invariant named) — nothing is dropped in silence.
313
314
 
314
315
  <a id="INVARIANT_COVERAGE_OUTSIDE_ROOTS"></a>
315
316
 
@@ -894,6 +895,15 @@ never opting out of knowing.
894
895
  - **Why:** `requireLayerOwners` is on and this layer has no owners. Writes to that house fail closed, like a folder with no name on the door.
895
896
  - **Fix:** Add a GitHub handle or email to that layer’s owners in ark.config.json (`/ark-adopt`), then re-run. Reserved/allowEmpty houses may stay unnamed.
896
897
 
898
+ <a id="CONFIG_SLICE_IDENTITY_COLLISION"></a>
899
+
900
+ ### `CONFIG_SLICE_IDENTITY_COLLISION`
901
+
902
+ **Starred slice prefixes share one id**
903
+
904
+ - **Why:** `sliceIdentity` is `stars` and two different `sliceFolders` prefixes bind as the same slice id. Parallel trees of one feature do that on purpose; unrelated trees that share the last folder name merge by accident. The warning names both paths.
905
+ - **Fix:** Keep `stars` when those prefixes are one feature. Otherwise use `path`, or rename the last literal so the prefixes do not bind as the same id. Advisory — it does not fail the check.
906
+
897
907
  ## Meta
898
908
 
899
909
  <a id="ARK_UNKNOWN"></a>
@@ -48,18 +48,19 @@ hardening guide remains repository-hosted rather than duplicated in the gate tar
48
48
  | **ArkRun doctor / status / report (RN08)** | `ark-check --doctor --json` → `doctor.arkRun`; HTML `data-advisory="arkRun"`; `ark status --json` / MCP `ark_status` → `arkRun`; `rulesUnderContract.mergePlanes.arkRun` | Additive. Always **`notAScore: true`**. Residual is a finding-id count (status residual `null` = unknown, not green). Advisory and absence never arm extra merge teeth; enforced teeth follow the classified-layer floor. Never a score or LLM verdict. |
49
49
  | **Upgrade what’s new (4.5.6+)** | `ark upgrade --json` → `whatsNew` (+ human **Suggested improvements** block; also on preview). | Always **`notAScore: true`**, **`neverGateInput: true`**. Closed try/inspect list: deep-module coach, improvement compass, session/status honesty, two-axis done, self-service honesty, registry-aware upgrade, skill drift/refresh, multi-project MCP, Codex hard-write refresh/trust/restart/verify, stale MCP/global CLI recovery. Never invents residual or flips gates. |
50
50
  | **Field upgrade truth (4.5.6)** | `ark upgrade` registry-aware install; JSON `reasonCode` / `suggestedInstallCmd`; `skillDrift`; `--refresh-skills`; `postUpgradeChecks`; `hostSelection`. | No false-skip when registry ahead; offline honesty; customized skills preserved unless opt-in refresh; checks are advisory only. |
51
+ | **Upgrade outcome (4.8.21)** | `ark upgrade --apply` JSON → additive `outcome`, `postUpgrade` (`verdict`, `failing[{ruleId,count,sample}]`, `behaviorChanges`), and on each blocked asset `reason` + `nextCommand`. State `absent-local` recreates a recorded gitignored file. | Exit `0` applied and green, `3` applied but red, `2` blocked, `1` error. Scripts comparing against `1` must switch to non-zero. `behaviorChanges` is the shipped per-version table (same sentences as the changelog). JSON fields are additive. `/ark-upgrade` stops when verification is red and does not edit tests, titles, or config in that turn. |
51
52
  | **MCP process package honesty (4.5.6; fail-closed in 4.6.4)** | Every MCP tool context → `processPackage` (`processPackageMismatch` / `processStale`, versions, `nextAction`). | Multi-checkout users: restart MCP after pin bump. From 4.6.4, stale process context is non-authoritative; `ark_identity` remains diagnostic and project tools return `PROCESS_PACKAGE_STALE` until restart/retarget. Prefer project-local CLI until identity matched and versions align. |
52
53
  | **Doctor design fitness** | `ark-check --doctor --json` → `doctor.designFitness`, `doctor.designSmells[]` | Additive. Stable smell `id`s: `io-under-application`, `handler-in-persistence`, `god-module`, `domain-logic-in-ui`, `facade-sql-in-routes`, `mixed-pattern-cluster`, `soft-contract`. `handler-in-persistence` covers static ES imports/re-exports of framework HTTP surfaces (`next/server`), `defineRoute` calls, and existing handler bodies inside Persistence-role layers or specific persistence paths; `require()` and dynamic `import()` are outside this narrow advisory, and a generic `Infrastructure` role alone is not Persistence. Persistence candidates are filtered and sorted before the bounded content scan so large application prefixes cannot hide the advisory. The detector inspects the first 800 sorted Persistence candidates; later candidates are uninspected, so **absence of a smell is not full-tree proof** above that envelope (incomplete/`partial` analysis also never proves “no smells”). **4.2 feedback hardening:** mode labels preserve the observed SUGGEST/ADAPT/ENFORCE state; a local permission/UI-state `canEdit` name alone is not a domain smell; real UI business rules route Domain → Application → UI; seed/fixture/demo/migration/generated files are not god-module pilots. Each smell has `evidence[]`, `fix`, technical `message`, and plain-language **`outcome`**. Does **not** fail the gate by itself. |
53
54
  | **Post-green Shape door** | `doctor.postGreenPath`, `doctor.primaryNextAction`, `doctor.healthyFinishedForbidden` | Additive when `designFitness.designWeak`. Single Shape door (`id: clarify-for-ai`): explore shape-focus → dual-plan B → autopilot only with OK. Never empty plan A = healthy finished. A **passing** `ark-check` human run also prints one dim line naming `--plan`, the smell count and the smell ids. Its weakness test is `isDesignWeak` over the **blocking** violation count — the same input doctor uses, so the two surfaces cannot disagree about `designWeak`, and non-blocking type-only placement debt still gets the line. Report only: never a warning, never an exit-code change, silent when no smell remains, silent on `--changed` (a partial scan would report a slice as the tree) and on `--watch`. A baselined run is not called clean: the opening clause names the suppressed count instead. `--json` is unchanged. |
54
55
  | **Golden pattern (new code)** | Optional `.ark/golden-pattern.json`; doctor JSON `doctor.goldenPattern`; MCP `ark_place` / `ark_prepare_write` → `goldenPattern` | Additive, **advisory for NEW code only**. Required fields: `name`, `norm`; optional `newCodeHome`, `examplePath`, `schemaVersion`. **Absent is normal** (no claim). Never ENFORCE; never clears design-weak. Malformed → `invalid: true`, not silent guidance. |
55
56
  | **Plan pattern B (Shape bets)** | `ark-check --plan --json` → `plan.patternBets[]`, `plan.goal.designWeak` | Additive. Each bet: `id`, `smellId`, `pilot`, `evidence`, `successSignal`, `killSwitch`, **`neverMechanicalSafe: true`**, `class: "judgment"`. **Never** auto-applied by loop/autoPatch; not a `remediationKind` mechanical-safe. `goal.met` remains edge honesty only. |
56
- | **Pilot loop (one at a time)** | `plan.pilotLoop` / `doctor.pilotLoop` | Additive. When design-weak: `active`, `oneAtATime`, `neverMechanicalSafe`, **`nextPilot`** extraction-card fields (`pilotTarget`, `smellId`, `move`, `successSignal`, `killSwitch`, `doNot[]`). **One pilot → re-doctor**; never multi-pilot batch; never mechanical-safe. |
57
+ | **Pilot loop (one at a time)** | `plan.pilotLoop` / `doctor.pilotLoop` | Additive. Active when at least one pilot candidate exists: a design-weak pattern bet, or a proposed physical-cohesion reshape card. Inactive reason is `no-pilot-candidates`. `oneAtATime`, `neverMechanicalSafe`, **one** `nextPilot` / `extractionCard` (same extraction-card fields: `pilotTarget`, `smellId`, `move`, `successSignal`, `killSwitch`, `doNot[]`). Further candidates stay queued. **One pilot → re-doctor**; never multi-pilot batch; never mechanical-safe. Gate verdict unchanged. |
57
58
  | **AI-velocity eval (maintainer)** | Repository-only evidence: `npm run eval:ai-velocity` → `eval/ai-velocity-report.json` | Fixture-measured (no live LLM). Same feature scenario on design-weak vs golden-path arms; metric **`placementTurns`** (agent-equivalent). Golden must be strictly better. Method string lives next to the number. Does not weaken the gate. |
58
59
  | **Contract health** | `ark-check --doctor --json` → `doctor.contractHealth`; optional `.ark/contract-smell-acks.json` | Additive, **advisory only** — meta-lint of the contract itself (layer-name heuristics; imprecision costs a warning line, never a verdict); never changes the verdict, `designFitness`, or `patternBets`. Stable smell ids: `contract-bidirectional-allow`, `contract-peripheral-depends-core`, `contract-lateral-adapter-allow`, `contract-dead-rule`; each smell has `severity`, `evidence[]` (sorted, honest `…(+N more)` truncation), `fix`, `message`, plain-language `outcome`, and `acknowledgedEdges` (acks applied to that id). **X03/X06**: the lateral smell does not fire on an adapter reaching its **own family's infra base** — the target reads `<Family><InfraWords…>` (**every** remaining target token an infra word: `Infra(structure)`/`Base`/`Core`/`Shared`/`Common`/`Kernel`/`Platform`/`Foundation`) and the source carries the family token **anywhere** in its name (X06, field: `HoursPersistenceAdapters -> PersistenceInfrastructure` — mid-name families). `PaymentsCoreAdapters` is still a sibling; cross-family edges, non-infra siblings, and the reverse (base → member) still fire. Acknowledgments live in the bounded sidecar (`{ acks: [{ id, edge, reason, reviewBy? }] }`, ≤64 KB / ≤200 entries; bidirectional edges order-insensitive, exact two segments); `contractHealth.acknowledged` counts **applied** acks only (stale acks count 0). **X02 ack lifecycle**: optional `reviewBy` (`YYYY-MM-DD`, strict round-trip validation — `2026-02-30` is malformed) — past the date the ack **stops applying** and the smell returns with `(ack expired …)` annotated evidence; among dated entries a fresh re-ack wins over a dead one, but once ANY dated ack exists for an edge the dated entries govern — a leftover undated duplicate cannot resurrect an expired exception. `detectContractSmells` defaults `today` to the real clock (pass `null` to disable expiry); `analyzeContractSmells` stays pure (clock injected). `contractHealth.ackLifecycle` reports `{ undated, malformed, expiredCount, expired[], staleCount, stale[] (lists capped at 12) }`; undated acks apply (backward compatible) but surface in doctor, report, and the fossilization note even when every smell is suppressed. **X05**: an ack matching **no detected edge** (orphaned by a fixed contract or quieted heuristic, unknown id, or typo'd edge) is `stale` — it suppresses nothing and doctor/report list the exact entries to fix or delete, even at zero visible smells. Malformed `reviewBy` never applies (fail-loud, like a sloppy edge); non-string `reviewBy` → whole file `invalid`. **Absent is normal**; malformed file or edge grammar → ignored + `ackFile.invalid` where applicable, never silent suppression. |
59
60
  | **Effect capabilities** | Public root API: `analyzeProject(...).ir.capabilityUses`; the CLI/hook adapters add symbol-aware ambient evidence internally | Additive within IR `1.0`. Seven **closed** ids: `network`, `filesystem`, `clock`, `randomness`, `environment`, `process`, `persistence` (ADR 0009). `collectCapabilityUses` and the Domain vocabulary are internal implementation exports, **not** exports from `arkgate`; the related public low-level helper is `collectForbiddenCapabilityUses`. Direct evidence only — transitive inference never detects. The symbol-aware adapter path covers ambient globals (shadowing/type-only/globalThis-alias precision from the S05/C04 machinery) plus imports; the compiler-free IR engine carries **import-based** uses only (exact module or subpath match, never substring; textual `import type`/`export type` erasure and all-type named lists (`import { type A }`) are type-only there; mixed `{ type A, B }` stays a value import; template-literal bodies are skipped entirely (specifiers inside `${…}` are the symbol path's job); package `require(…)` counts as capability evidence only, while relative `require(…)` also emits a pure-path graph edge). **U04 walls are opt-in:** per-layer `capabilities: { deny: [...] }` or the dual-depth sugar `pure: true` (denies all seven); absence changes no verdict. `CAPABILITY_VIOLATION` is judgment-class (never mechanical-safe) with a port-injection `nextAction`; D7 dedup — evidence already owned by the layer's `forbiddenGlobals` reports only `FORBIDDEN_GLOBAL`. Y08 adds one deliberately narrow import dual: `forbiddenGlobals: ["process"]` owns exact value imports of `process` and `node:process`, but not subpaths or `child_process`; statement-level `import type` / `export type` remains erased on every path (pure-IR residual envelope: mixed `{ type A, B }`, default+named type lists, and comment-interrupted forms stay value imports; symbol path owns full precision). Atomic preflight blocks denied capabilities and that exact dual across a complete multi-file candidate (import-based on the pure path; other ambient evidence adds on the symbol-aware CLI/hook path). T01 policy-delta classifies the surface on **coverage atoms** (`ambient:<entry>` prefix-expanded, narrow `import-exact:<specifier>` duals, and `import:<capability>` for a complete wall): any lost atom is weakening (`fetch`→`XMLHttpRequest`, `Date`→`Date.now`, wall→fg all weaken; finding path `$.layers[name].capabilities`); fg → equivalent-or-stronger wall never needs an acknowledgment; unlowerable custom globals keep raw key comparison. |
60
61
  | **Ambient state (pure layers)** | `ark-check --doctor --json` → `doctor.ambientState`; optional `.ark/ambient-state-acks.json` | Additive, **advisory only and opt-in**: only layers declared `pure: true` are scanned; the MVP shape is module-scope `let`/`var`. Findings carry `file`/`line`/`name`/`kind` (sorted, capped with honest `truncated` count). Acknowledgments live in the bounded sidecar (`{ acks: [{ file, name, reason }] }`, ≤64 KB / ≤200 entries); `acknowledged` counts applied acks; malformed file suppresses nothing. When TypeScript is unavailable the sensor reports `available: false` instead of guessing. **No strict mode exists** — A5: strictness requires a completed corpus and an explicit later decision. |
61
62
  | **Parse health + analysis completeness** | `ark-check --doctor --json` → `doctor.parseHealth` + `doctor.completeness`; check JSON → `completeness`; report section `data-advisory="parseHealth"` | The resolved candidate facts contribute only `parseDiagnosticCount` per governed file (no raw diagnostics, second parser pass, or `tsc`). Z04's correctness path ignores legacy v9 caches and parses the complete candidate on every invocation; Z07 owns any future identity-keyed warm snapshot. Doctor remains diagnostic: parse health adds no architecture violation and does not change `designFitness` or `patternBets`. Verdict surfaces consume the evidence fail-closed: affected governed files mean `partial`, plan `goal.met: false`, normal JSON `valid:false`/`ok:false`, and strict merge exit `1`; the non-strict process exit remains advisory for compatibility. No usable host means `unavailable`, plan false, and CLI exit `2`. JSON reports `scannedFiles`, `affectedFiles`, `diagnosticCount`, deterministic top-12 `{ file, diagnosticCount }` entries, and honest `truncated`/`overflow`; missing/unsafe evidence never becomes a clean claim. |
62
- | **Physical cohesion + reshape pilot** | `ark-check --doctor --json` → `doctor.physicalCohesion` (`reshapePilot`, `reshapeDecisions`); optional `.ark/reshape-decisions.json`; report section `data-advisory="physicalCohesion"` | Additive, **advisory only** — `notAScore`; never feeds the verdict, `designFitness`, or `patternBets`. Signal is **concentration, not volume**: concept clusters per anchor directory (deterministic path/name tokenization; framework filenames like `route.ts` take the topmost meaningful path segment — ADR 0010 D2). Fixed corpus-calibrated thresholds (`maxCluster ≥ 40` OR ≥2 anchors ≥ 20, ADR 0010 D3); findings ranked and capped (top 5, honest `truncated`). Anchors under `app/`/`pages/` are `fixedByConvention` and never move (D7). `reshapePilot` is **proposed, never applied** (`neverMechanicalSafe`): one Q04-style pilot card at a time targeting the smallest convention-free anchor, with `moveSample`/`movesTotal`, `successSignal`, `killSwitch`, `doNot[]`; real moves run only through the write gate + atomic preflight via `/ark-loop`; merges are `/ark-architect` judgment cards, never a codemod (D6). **Y01 verdict memory:** bounded sidecar `{ schemaVersion?: "1", decisions: [{ concept, anchors, verdict: "accepted"|"deferred"|"rejected", reason, reviewBy? }] }` (≤64 KiB / ≤200 unique targets). Identity is concept + complete sorted anchor set, never counts/change-map evidence. Current rejected/deferred records suppress pilot pressure only; accepted keeps the existing path. Expired/malformed/stale/invalid records suppress nothing; lifecycle and decisions render in doctor/report. Explicit only — golden-pattern prose never infers a verdict. |
63
+ | **Physical cohesion + reshape pilot** | `ark-check --doctor --json` → `doctor.physicalCohesion` (`reshapePilot`, `reshapeDecisions`); optional `.ark/reshape-decisions.json`; report section `data-advisory="physicalCohesion"` | Additive, **advisory only** — `notAScore`; never feeds the verdict, `designFitness`, or `patternBets`. Signal is **concentration, not volume**: concept clusters per anchor directory (deterministic path/name tokenization; framework filenames like `route.ts` take the topmost meaningful path segment — ADR 0010 D2). Fixed corpus-calibrated thresholds (`maxCluster ≥ 40` OR ≥2 anchors ≥ 20, ADR 0010 D3); findings ranked and capped (top 5, honest `truncated`). Anchors under `app/`/`pages/` are `fixedByConvention` and never move (D7). `reshapePilot` is **proposed, never applied** (`neverMechanicalSafe`): one Q04-style pilot card at a time targeting the smallest convention-free anchor, with `moveSample`/`movesTotal`, `successSignal`, `killSwitch`, `doNot[]`; each `to` is `concept/` under the source anchor and is kept only when layer and slice id are unchanged — if every candidate fails, `nextPilot` is null; real moves run only through the write gate + atomic preflight via `/ark-loop`; merges are `/ark-architect` judgment cards, never a codemod (D6). **Y01 verdict memory:** bounded sidecar `{ schemaVersion?: "1", decisions: [{ concept, anchors, verdict: "accepted"|"deferred"|"rejected", reason, reviewBy? }] }` (≤64 KiB / ≤200 unique targets). Identity is concept + complete sorted anchor set, never counts/change-map evidence. Current rejected/deferred records suppress pilot pressure only; accepted keeps the existing path. Expired/malformed/stale/invalid records suppress nothing; lifecycle and decisions render in doctor/report. Explicit only — golden-pattern prose never infers a verdict. |
63
64
  | **Capability walls, every adapter** | CLI scan, pure IR engine, atomic preflight, `ark-mcp --hook` / MCP gate (`capabilityWalls`), ESLint `ark/no-denied-capabilities` | The same opt-in deny set enforces across every surface: hook/MCP and CLI cover ambient + import evidence (symbol-aware); the pure engine, preflight, and ESLint cover the import dimension (documented envelope). Dual depth everywhere: plain port hint (`FIX_HINTS`/`suggestion`) + stable JSON (`ruleId`, `capability`, `fixClass: inject-port`, deterministic `nextAction`). |
64
65
  | **Hook-path budgets (maintainer)** | Repository-only evidence: `npm run bench:hook-path`; `eval/performance/hook-budgets.v1.json`; CI job "Hook-path end-to-end budgets" | Measures the COMPLETE pre-tool paths as fresh child processes (hook cold/warm, doctor cold) at 1k/10k. D5 method locked: ceilings are Linux-baseline p95 + fixed headroom, set once per cycle, never ratcheted; scenarios without a recorded baseline stay in RECORDING mode and cannot fail CI. |
65
66
  | **Governance weight** | `ark-check --doctor --json` → `doctor.contractHealth.governanceWeight` | Additive, **advisory only** — raw facts (`declaredLayers`, `populatedLayers`, `governedFiles`, `rules`, `deniedEdges`, `allowedEdges`, `filesPerLayer`, `rulesPerLayer`) plus a fixed comparative band `weight: heavy | typical | light | unknown` and its fixed `note`. Fixed deterministic thresholds: **heavy** = fewer than 25 governed files per declared layer AND (6+ layers OR 4+ well-formed rules per layer); **light** = at most 2 layers over 150+ governed files; **unknown** = no layers or no governed files; everything else is **typical** (banding uses raw ratios; the reported ratios are rounded for display). `notAScore: true` is explicit: never a composite score, ranking, or gate input; the heavy note asks to justify NEW layers/rules and never suggests deleting working ones. Human doctor prints a line only for `heavy`/`light`. |
@@ -74,7 +75,7 @@ hardening guide remains repository-hosted rather than duplicated in the gate tar
74
75
  | **`arkgate/schema/project-identity`** or **`arkgate/schema/ark.project-identity.schema.json`** | MCP canonical project, contract, runtime, expectation, and binding envelope | Schema `1.0`. Initial `expectedRoot` must be the exact project root. A contained descendant can match only when `expectedProjectId` is also present and correct; id-only matching stays non-authoritative. Mismatch codes are `PROJECT_ROOT_MISMATCH`, `PROJECT_ID_MISMATCH`, and `INVALID_PROJECT_EXPECTATION`. |
75
76
  | **Package pin dual-truth (4.0)** | doctor JSON `packageVersionTruth`; upgrade JSON/human note when pin behind CLI | Additive, advisory. Surfaces after `upgrade --no-install` when managed CLI is ahead of package.json. |
76
77
  | **Managed upgrade self-service honesty (4.5 / DF05)** | `ark upgrade [--json]` → `selfService` (+ human “Self-service honesty” lines) | Additive, advisory. Answers without a maintainer: write-path activation labels per selected host (`hard`\|`advisory`\|`unavailable`) and customized content-identity preserve (`customizedPaths` / `customizedContentPreserved`). Soft hosts never hard; upgrade never invents `hardWriteActive` from disk alone. Always `notAScore: true`. Not a gate input; not part of `planDigest`. |
77
- | **Product honesty readiness split (4.1.1)** | doctor JSON `productHonesty` | Additive. `unfinished` / `headline` / `primaryNextAction` / `reasonIds` remain; EH adds `contractReadiness` (`ready`\|`partial`\|`not-ready`), `localWriteBoundary` (`advisory`\|`hard`\|`unverified`\|`unknown`), `architectureReasonIds`, `environmentResidualIds` / `environmentResiduals`. Soft-write hosts stay in evidence without alone forcing global **Not finished**. `notAScore: true` always. |
78
+ | **Product honesty readiness split (4.1.1)** | doctor JSON `productHonesty` | Additive. `unfinished` / `headline` / `primaryNextAction` / `reasonIds` remain; EH adds `contractReadiness` (`ready`\|`partial`\|`not-ready`), `localWriteBoundary` (`advisory`\|`hard`\|`unverified`\|`unknown`), `architectureReasonIds`, `environmentResidualIds` / `environmentResiduals`. Soft-write hosts stay in evidence without alone forcing global **Not finished**. `notAScore: true` always. A physical-cohesion residual that reshape decisions have not suppressed adds `physical-cohesion-residual`, which clears `elegant` and `finished` and sets the headline to residual remaining; the gate verdict, `designFitness`, `designWeak`, and `patternBets` stay unchanged. When that residual still proposes `reshapePilot.nextPilot`, `pilotLoop` is active with that one extraction card. |
78
79
  | **Policy transition analysis (3.1.0)** | `analyzePolicyDelta(...)`; MCP `ark_policy_delta`; CLI `--policy-base` / `--policy-base-ref` / `--policy-ack`; check JSON `policyDelta` | Additive schema `1.0`. Classifications and finding ids are deterministic. Weakening/judgment requires an acknowledgement bound to both policy hashes and the exact blocking finding set. A new layer or new allow edge is `judgment-required`. On the CLI/MCP policy-ack plane, `adrPath` must name a short note under `docs/adr/` or `docs/decisions/` (`policyDelta.adrNote`). Domain hash match stays I/O-free. |
79
80
  | **Team parliament (law vs feature)** | Optional `stewards` on `ark.config.json` (GitHub handle or email); CLI `--local` / `--changed` / `--against` / `--base` / `--contract-diff` / `--contract-session` / `--persona` / `--author`; check JSON `teamParliament`; `ark status --vs`; write-gate mixed-batch deny | Additive. Law files must not mix with product source. Loosen / baseline-grow are steward-only when `stewards` is set. `--against` ratchets vs the base-ref baseline. `--changed` scans touched sources plus their import closure. `--local` (or `ARK_CHECK_LOCAL=1`) is the same cheap path and stays per `--root`; it cannot combine with `--strict-merge`. Check JSON may add `local`, `scope: "changed"`, `analysisRoot`. `stewards`, `layers[].description`, `layers[].trustBoundary`, and `layers[].owners` are excluded from policy hash. `requireLayerOwners` stays in the hash. Identity is handle or email, not git `user.name`. No org plane. |
80
81
  | **Atomic change preflight (3.1.0)** | `preflightChange(...)`; CLI `ark preflight --changes <file> --json`; MCP `ark_prepare_change` | Additive schema `1.0`. One complete governed production-source `{path,content}` / `{path,delete:true}` batch; read-only; returns operation, content/tree/policy/compiler fingerprints and stable graph findings. MCP availability alone is advisory. |
@@ -66,9 +66,9 @@ Skills never enforce. ξ / Haken stay below the fold.
66
66
 
67
67
  ## Locked first-contact
68
68
 
69
- On the eight canonical public openings — `README.md`, `docs/use.md`,
70
- `docs/develop.md`, `docs/README.md`, `docs/enthusiast/README.md`,
71
- `docs/agent-guide.md`, `docs/ai-gates.md`, and `docs/package-surface.md` — use:
69
+ On the seven lane openings — `docs/use.md`, `docs/develop.md`, `docs/README.md`,
70
+ `docs/enthusiast/README.md`, `docs/agent-guide.md`, `docs/ai-gates.md`, and
71
+ `docs/package-surface.md` — use:
72
72
 
73
73
  1. **Verbs:** `Write. Check. Ship.`
74
74
  2. **Deny:** `When the agent writes a bad import, the write doesn’t land. The same check fails the pull request.`
@@ -77,6 +77,14 @@ On the eight canonical public openings — `README.md`, `docs/use.md`,
77
77
  5. **Caption (immediately after the four paragraphs):** `That is **Contener · Guiar · Ordenar**.`
78
78
  6. **Technical nouns (below the introduction):** ArkGate is import rules. ArkRules is optional policies. ArkRun is an optional experimental runtime. ArkOrder is the extra for the few big product choices (billing plan, not seat counts) — named when the consumer opts in, never as the first noun. No ξ / Haken / slaving on the first screen.
79
79
 
80
+ `README.md` is the first-contact module. Its first screen is four beats: what
81
+ (**Contener · Guiar · Ordenar**), why (design the boundary before you implement;
82
+ a green check is **needs a refactor** when leftover mess remains), one command
83
+ (`npx arkgate@4.8.21 start`), and one honest red result (fail-open named; required
84
+ CI is the hard line). The four paragraphs, the not-that line, the host matrix,
85
+ and the release index sit below `## Appendix`. The not-that line does not lead
86
+ the README.
87
+
80
88
  Do not lead with folders, `ark.config.json`, “contract”, “gate”, “house”, or “doctor”
81
89
  as the first noun. Historical: `If the AI writes an illegal import, the write is rejected`
82
90
  and `One architecture config. One check. One coach.` — never first.
package/docs/use.md CHANGED
@@ -30,7 +30,7 @@ npx --package=arkgate arkgate-check --doctor # status — one next step
30
30
  npx --package=arkgate arkgate-check --doctor --all # full details
31
31
  ```
32
32
 
33
- `start --apply` pins arkgate with this repo's package manager (`pnpm add -w` / `yarn add -W` at a workspace root). Do not run `npm install -D arkgate` at a `workspace:*` root — npm rejects that protocol. After a local install, `npx arkgate-check --doctor` also works. `arkgate-check` is a command in the `arkgate` package, not its own npm package.
33
+ `start --apply` pins arkgate with this repo's package manager (`pnpm add -w` / `yarn add -W` at a workspace root). Do not run `npm install -D arkgate` at a `workspace:*` root — npm rejects that protocol. After a local install, `npx arkgate-check --doctor` also works. `arkgate-check` is a command in the `arkgate` package, not its own npm package. If pnpm Age (`minimumReleaseAge`) blocks a brand-new release, keep using `npx --package=arkgate@<version>` — do not re-run the same add. Put arkgate on `minimumReleaseAgeExclude` in pnpm config or `pnpm-workspace.yaml` (a CLI flag is not enough), or wait until the package is mature.
34
34
 
35
35
  `start --apply` refuses when projected governed coverage is below 50% or
36
36
  shape confidence is weak (below 0.6 with coverage under 80%), or when the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkgate",
3
- "version": "4.8.19",
3
+ "version": "4.8.21",
4
4
  "description": "When the agent writes a bad import, the write doesn’t land. The same check fails the pull request.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -205,14 +205,14 @@
205
205
  "@stryker-mutator/core": "^9.6.1",
206
206
  "@stryker-mutator/vitest-runner": "^9.6.1",
207
207
  "@types/node": "^20.14.0",
208
- "@vitest/coverage-v8": "^3.2.6",
208
+ "@vitest/coverage-v8": "^4.1.11",
209
209
  "esbuild": "0.28.1",
210
210
  "fast-check": "^4.9.0",
211
211
  "reflect-metadata": "^0.2.2",
212
212
  "rxjs": "^7.8.2",
213
213
  "tsup": "^8.1.0",
214
214
  "typescript": "5.9.3",
215
- "vitest": "^3.2.6"
215
+ "vitest": "^4.1.11"
216
216
  },
217
217
  "engines": {
218
218
  "node": ">=18"
@@ -798,6 +798,14 @@
798
798
  "uniqueItems": true,
799
799
  "minItems": 1
800
800
  },
801
+ "sliceIdentity": {
802
+ "type": "string",
803
+ "enum": [
804
+ "path",
805
+ "stars"
806
+ ],
807
+ "description": "How a starred sliceFolders prefix is named. Absent and path keep today's ids (every literal and star binding). stars keeps the last literal plus the star bindings, so parallel trees share one feature id. Bare names are unchanged."
808
+ },
801
809
  "sharedRoots": {
802
810
  "type": "array",
803
811
  "items": {
@@ -828,6 +836,12 @@
828
836
  }
829
837
  }
830
838
  }
839
+ },
840
+ "sharedImportsSlice": {
841
+ "type": "string",
842
+ "enum": [
843
+ "deny"
844
+ ]
831
845
  }
832
846
  }
833
847
  },
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/pedroknigge/arkgate",
7
7
  "source": "github"
8
8
  },
9
- "version": "4.8.19",
9
+ "version": "4.8.21",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "arkgate",
14
- "version": "4.8.19",
14
+ "version": "4.8.21",
15
15
  "runtimeHint": "npx",
16
16
  "transport": {
17
17
  "type": "stdio"
@@ -9,7 +9,7 @@ closed `/ark-*` catalog shipped as flat templates for Ark install.
9
9
  **Closed catalog** (`ARK_SKILL_NAMES`) — first-class doors plus one-release
10
10
  redirect stubs. Add a name only with a live ROADMAP item.
11
11
 
12
- Package version when last generated context: **arkgate@4.8.19**
12
+ Package version when last generated context: **arkgate@4.8.21**
13
13
  Schema: agent-skills package contract `1.0`
14
14
 
15
15
  ## Skills (closed catalog)
@@ -63,6 +63,11 @@ has honest houses. Skills never enforce — CLI / hooks / CI do.
63
63
  `coverage.coverageRoots` to the folder the test runner uses. Fail-closed
64
64
  until that path exists. `coverage.testGlobs` alone is not enough. Silent
65
65
  when no invariant is enforced. No new skill.
66
+ - `coverage.symbol` means a declaration of that identifier in a non-test file.
67
+ Doctor and `--rules-inventory` print `symbolEvidenceFile` and the discard
68
+ counts even when uncovered is 0. Read that path. Imports and calls in tests
69
+ do not count. That declaration text is not “the tests pass” — ArkGate never
70
+ runs tests. No new config key.
66
71
  - CLI-first: if `arkgate-check` already resolved the root, do not wait on MCP.
67
72
  - Do not add `arkRun` or `arkOrder` unless the user wants that extra. When they do, write
68
73
  **advisory** extra in this turn (`arkRun` schema `1.2+`; `arkOrder` schema `1.3+`).
@@ -353,8 +358,18 @@ Ark protects the **boundary around** a framework, not its internals. Nest/DI pub
353
358
  The denial names its reason. `unclassifiable path` in bulk means shared code lives outside
354
359
  the slice folders — declare those roots (`sharedRoots`) on the rule instead of treating
355
360
  thousands of shared files as violations; `cross-slice edge a → b` is the real one, and a
356
- deliberate directed edge goes in `allowedCrossSlice`. Promoting the shared slice to its own
361
+ deliberate directed edge goes in `allowedCrossSlice`. Shared roots are a sink: a slice may
362
+ import them, and they may import a slice. Doctor lists that shared-root → slice hop even
363
+ when the check stays green, so the wall is direct-only. `sharedImportsSlice: "deny"` closes
364
+ that one hop; it does not finish the wall. Promoting the shared slice to its own
357
365
  layer is still the preferred fix.
366
+ `sliceFolders`: a bare name stays an unanchored one-segment match (`features` on
367
+ `src/lib/features/projects/rfi/x.ts` is `features/projects`); a filename is never the
368
+ child segment. A starred prefix (`lib/features/*/*`) is anchored like `sharedRoots`
369
+ (offset 0 or 1). The slice id includes the literal prefix plus the directories
370
+ the stars bind (`lib/features/projects/rfi`), so parallel trees get different ids
371
+ (tracked in #308). Before trusting green, add one deliberate
372
+ feature-to-feature import and expect `cross-slice`.
358
373
  If one edge dominates residual debt: **STOP — do not continue this skill as complete.** **STOP — concentrated edge:** rewrite `ark.config.json` **in this turn** with source evidence (do not freeze a wrong config or grind N freezes).
359
374
  Empty Domain/Persistence + I/O under Application → false-green.
360
375
  **STOP — do not continue this skill as complete.** **STOP — false-green:** fix the config **in this turn** before claiming ENFORCE. Do not claim goal.met / ENFORCE from type-only cleanup while doctor reports `contract-false-green-io-under-application`.
@@ -423,5 +438,7 @@ End with **exactly** these headings (markdown `###`):
423
438
  - **Handoff:** `/ark-…` / CLI / `none`
424
439
  - **Incomplete?** `no` | `yes — <what is missing>`
425
440
 
441
+ When `doctor.productHonesty.finished` is `false`, **Incomplete? no** is disallowed. Write `yes — <pilotLoop.extractionCard.move or residual>`.
442
+
426
443
  If a **STOP** handoff applies and you continued as if done, set **Incomplete?** to `yes`.
427
444
  **Skill incomplete if missing** any of the bullets above.
@@ -59,3 +59,5 @@ Skill incomplete if missing any field below.
59
59
  - **Compass:** top residual lenses | `n/a`
60
60
  - **Handoff:** `/ark-adopt` / `/ark-place` / `none`
61
61
  - **Incomplete?** `no` | `yes — <what is missing>`
62
+
63
+ When `doctor.productHonesty.finished` is `false`, **Incomplete? no** is disallowed. Write `yes — <pilotLoop.extractionCard.move or residual>`.
@@ -110,8 +110,10 @@ decision-grade explore pass **and** without opening violating files.
110
110
  layout — no pilot”; never reconstruct that dead card from `findings`. When the user accepts,
111
111
  defers, or rejects a live card, persist its exact `decisionTarget` with a non-empty reason and
112
112
  optional `reviewBy` in `.ark/reshape-decisions.json`; never infer a verdict from golden prose.
113
-
114
-
113
+ A cohesion residual starts **one** reshape judgment card on `pilotLoop.nextPilot`
114
+ (one pilot, then re-doctor). The loop is active when a pilot candidate exists,
115
+ including that reshape card when the tree is not design-weak. It does not start
116
+ a pattern-bet batch.
115
117
 
116
118
  ## MCP workspace binding (mandatory)
117
119
 
@@ -306,5 +308,7 @@ End with **exactly** these headings (markdown `###`):
306
308
  - **Handoff:** `/ark-…` / CLI / `none`
307
309
  - **Incomplete?** `no` | `yes — <what is missing>`
308
310
 
311
+ When `doctor.productHonesty.finished` is `false`, **Incomplete? no** is disallowed. Write `yes — <pilotLoop.extractionCard.move or residual>`.
312
+
309
313
  If a **STOP** handoff applies and you continued as if done, set **Incomplete?** to `yes`.
310
314
  **Skill incomplete if missing** any of the bullets above.
@@ -64,3 +64,5 @@ Skill incomplete if missing any field below.
64
64
  - **Compass:** top residual lenses | `n/a`
65
65
  - **Handoff:** `/ark-adopt` / `/ark-autopilot` / `/ark-order` / `none`
66
66
  - **Incomplete?** `no` | `yes — <what is missing>`
67
+
68
+ When `doctor.productHonesty.finished` is `false`, **Incomplete? no** is disallowed. Write `yes — <pilotLoop.extractionCard.move or residual>`.
@@ -96,7 +96,7 @@ ArkGate has **always-on Layers** plus opt-in extras. The user chooses extras; yo
96
96
  3. Absence of `arkRules` is **valid** — do not force ArkRules unless the user wants them or residual inventory clearly wants a pilot.
97
97
  4. Editing `arkrules/*` or promoting modes is **`/ark-adopt`**; fixing code under a structure sensor is **`/ark-autopilot`** (judgment, never invent mechanical-safe).
98
98
  5. CLI helpers: `ark-check --rules-inventory --json`, doctor JSON `rulesUnderContract`, sensors emit `ARKRULE_*` / `INVARIANT_UNCOVERED` / `INVARIANT_CATALOG_EMPTY` with `evidence.arkruleId`.
99
- 5b. Invariant coverage is a **text match, never an execution**: a file walk plus the invariant id in a `describe`/`it` title. Never report `INVARIANT_UNCOVERED: 0` as “the tests pass” or “the tests run” — ArkGate does not run tests and reads no runner config. When the project declares `coverage.coverageRoots`, a covering test found outside them raises the advisory `INVARIANT_COVERAGE_OUTSIDE_ROOTS`; without that declaration ArkGate makes no claim about where tests run. Adopted + catalogued invariants without `coverage.testGlobs` or `coverage.coverageRoots` fails closed (`INVARIANT_TESTS_PATH_MISSING`) — write the path (`/ark-adopt`). When any invariant is enforced, missing `coverage.coverageRoots` fails closed (`INVARIANT_COVERAGE_ROOTS_MISSING`) — write the runner roots (`/ark-adopt`). `testGlobs` alone is not enough.
99
+ 5b. Invariant coverage is a **text match, never an execution**: a file walk plus the invariant id in a `describe`/`it` title. `coverage.symbol` means a declaration of that identifier in a non-test file (`symbolEvidenceFile`); imports and test calls do not count. Doctor and `--rules-inventory` print that witness path and the scan discard counts on a green run too. Never report `INVARIANT_UNCOVERED: 0`, a witness path, or a discard line as “the tests pass” or “the tests run” — ArkGate does not run tests and reads no runner config. Declaration text is not “the tests pass.” When the project declares `coverage.coverageRoots`, a covering test found outside them raises the advisory `INVARIANT_COVERAGE_OUTSIDE_ROOTS`; without that declaration ArkGate makes no claim about where tests run. Adopted + catalogued invariants without `coverage.testGlobs` or `coverage.coverageRoots` fails closed (`INVARIANT_TESTS_PATH_MISSING`) — write the path (`/ark-adopt`). When any invariant is enforced, missing `coverage.coverageRoots` fails closed (`INVARIANT_COVERAGE_ROOTS_MISSING`) — write the runner roots (`/ark-adopt`). `testGlobs` alone is not enough.
100
100
  6. Extras silent when off. Doctor `arkRun` / `arkOrder` is `notAScore`. Do not force extras. Wire one kernel candidate via `/ark-runtime`; one order-plane candidate via `/ark-order`. Do not invent `/ark-run`.
101
101
 
102
102
 
@@ -200,5 +200,7 @@ End with **exactly** these headings (markdown `###`):
200
200
  - **Handoff:** `/ark-…` / CLI / `none`
201
201
  - **Incomplete?** `no` | `yes — <what is missing>`
202
202
 
203
+ When `doctor.productHonesty.finished` is `false`, **Incomplete? no** is disallowed. Write `yes — <pilotLoop.extractionCard.move or residual>`.
204
+
203
205
  If a **STOP** handoff applies and you continued as if done, set **Incomplete?** to `yes`.
204
206
  **Skill incomplete if missing** any of the bullets above.
@@ -188,7 +188,7 @@ else is judgment/deferred and must not be auto-applied.
188
188
  **Type-edge honesty:** type-only denied edges are **placement debt** (`typeOnly` +
189
189
  `failsStrict: false`) — prefer SharedTypes / owning layer; they do not fail merge like value
190
190
  edges. Doctor `productHonesty` and `violations.typeEdgePolicy` must not be sold as “all done”
191
- when residual signals remain.
191
+ when residual signals remain (`physical-cohesion-residual` included).
192
192
 
193
193
  ## Spoken / written explanation
194
194
 
@@ -244,6 +244,8 @@ End with **exactly** these headings (markdown `###`):
244
244
  - **Handoff:** `/ark-…` / CLI / `none`
245
245
  - **Incomplete?** `no` | `yes — <what is missing>`
246
246
 
247
+ When `doctor.productHonesty.finished` is `false`, **Incomplete? no** is disallowed. Write `yes — <pilotLoop.extractionCard.move or residual>`.
248
+
247
249
  Prefer **Incomplete?** `yes` if the showcase report was generated but the browser-open step was
248
250
  skipped without CI/headless/user-opt-out reason.
249
251