arkgate 4.8.18 → 4.8.20

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 (74) hide show
  1. package/CHANGELOG.md +153 -0
  2. package/README.md +5 -6
  3. package/bin/ark-check-runtime.mjs +23 -0
  4. package/bin/ark-layer-match.mjs +141 -9
  5. package/bin/ark-mcp-runtime.mjs +3 -3
  6. package/bin/ark-shared.mjs +187 -16
  7. package/bin/ark.mjs +23 -19
  8. package/bin/lib/analysis-engine.mjs +6 -6
  9. package/bin/lib/ci-and-commands.mjs +2 -2
  10. package/bin/lib/config-contract.mjs +1 -0
  11. package/bin/lib/doctor-advisories.mjs +2 -1
  12. package/bin/lib/doctor-human.mjs +2 -0
  13. package/bin/lib/doctor-next-actions.mjs +35 -0
  14. package/bin/lib/doctor-plan.mjs +8 -8
  15. package/bin/lib/enforcement-honesty.mjs +30 -2
  16. package/bin/lib/gate-files.mjs +22 -1
  17. package/bin/lib/host-support-matrix.mjs +1 -1
  18. package/bin/lib/html-report-depth.mjs +6 -2
  19. package/bin/lib/install-migrate.mjs +3 -4
  20. package/bin/lib/invariant-coverage.mjs +44 -13
  21. package/bin/lib/managed-upgrade.mjs +30 -5
  22. package/bin/lib/mcp-adoption.mjs +4 -1
  23. package/bin/lib/package-manager.mjs +5 -2
  24. package/bin/lib/physical-cohesion.mjs +68 -51
  25. package/bin/lib/presets.mjs +14 -14
  26. package/bin/lib/reshape-decisions.mjs +2 -2
  27. package/bin/lib/rules-under-contract.mjs +40 -4
  28. package/bin/lib/scan-files.mjs +3 -26
  29. package/bin/lib/skill-install.mjs +3 -2
  30. package/bin/lib/start-install-recovery.mjs +156 -0
  31. package/bin/lib/start-preview.mjs +17 -16
  32. package/bin/lib/write-path-detect.mjs +3 -2
  33. package/dist/{configTypes-VD0qcubY.d.ts → configTypes-VVknqcTJ.d.ts} +2 -0
  34. package/dist/{diagnosticCatalog-BNxKdcN4.d.ts → diagnosticCatalog-BPYWNyJR.d.ts} +9 -3
  35. package/dist/eslint/index.cjs +4 -4
  36. package/dist/eslint/index.d.ts +12 -4
  37. package/dist/eslint/index.js +4 -4
  38. package/dist/index.cjs +18 -18
  39. package/dist/index.d.ts +8 -4
  40. package/dist/index.js +20 -20
  41. package/dist/nestjs/index.cjs +5 -5
  42. package/dist/nestjs/index.d.ts +3 -3
  43. package/dist/nestjs/index.js +5 -5
  44. package/dist/runtime/index.cjs +10 -10
  45. package/dist/runtime/index.d.ts +6 -6
  46. package/dist/runtime/index.js +10 -10
  47. package/dist/{types-BSzRy2X1.d.ts → types-Cs-46DRu.d.ts} +1 -1
  48. package/dist/{types-D5GT5ZT8.d.ts → types-p_fCp6P-.d.ts} +1 -1
  49. package/docs/README.md +1 -1
  50. package/docs/agent-guide.md +3 -1
  51. package/docs/ai-gates.md +7 -3
  52. package/docs/configuration.md +20 -5
  53. package/docs/develop.md +1 -2
  54. package/docs/package-surface.md +2 -2
  55. package/docs/use.md +1 -2
  56. package/package.json +3 -3
  57. package/schemas/ark.config.schema.json +6 -0
  58. package/server.json +2 -2
  59. package/templates/agent-skills/README.md +1 -1
  60. package/templates/agent-skills/ark-adopt/SKILL.md +15 -1
  61. package/templates/agent-skills/ark-autopilot/SKILL.md +2 -0
  62. package/templates/agent-skills/ark-coverage/SKILL.md +1 -1
  63. package/templates/agent-skills/ark-explain/SKILL.md +1 -1
  64. package/templates/agent-skills/ark-explore/SKILL.md +13 -1
  65. package/templates/agent-skills/ark-place/SKILL.md +2 -0
  66. package/templates/agent-skills/ark-upgrade/SKILL.md +5 -3
  67. package/templates/hooks/opencode-ark-write-gate.mjs +2 -2
  68. package/templates/skills/ark-adopt.md +15 -1
  69. package/templates/skills/ark-autopilot.md +2 -0
  70. package/templates/skills/ark-coverage.md +1 -1
  71. package/templates/skills/ark-explain.md +1 -1
  72. package/templates/skills/ark-explore.md +13 -1
  73. package/templates/skills/ark-place.md +2 -0
  74. package/templates/skills/ark-upgrade.md +5 -3
@@ -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-p_fCp6P-.js';
2
2
 
3
3
  /**
4
4
  * PolicyEngine
@@ -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-VVknqcTJ.js';
2
2
 
3
3
  /**
4
4
  * Policy types for the Ark kernel.
package/docs/README.md CHANGED
@@ -73,7 +73,7 @@ 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.18` on npm `latest`; does not close `K01`).
76
+ Current published: [CHANGELOG](../CHANGELOG.md) (`arkgate@4.8.20` on npm `latest`; does not close `K01`).
77
77
  Prior published: [releases/4.8.11.md](releases/4.8.11.md) (`arkgate@4.8.11`).
78
78
  Prior published: [releases/4.8.10.md](releases/4.8.10.md) (`arkgate@4.8.10`).
79
79
  Prior published: [releases/4.8.9.md](releases/4.8.9.md) (`arkgate@4.8.9`).
@@ -67,7 +67,9 @@ and trusted (Cursor: `Write` / `StrReplace` via `.cursor/hooks.json`). Codex CLI
67
67
  ChatGPT Desktop/App Server can hard-block a complete `apply_patch` through a trusted
68
68
  `.codex/hooks.json`; installed files stay unverified until a fresh covered invocation. Hosted
69
69
  tools, specialized opt-out paths, shell/direct writes, incomplete patch reconstruction, and
70
- OpenCode remain CI-backed or advisory. For every host, the repository-wide hard boundary is a
70
+ OpenCode remain CI-backed or advisory. Write-gate order: host pre-hook first when
71
+ the host has one; MCP `ark_prepare_write` is fallback if that hook is missing or
72
+ fail-open. For every host, the repository-wide hard boundary is a
71
73
  **required GitHub status context** that runs `arkgate-check --strict-merge` (alias
72
74
  `ark-check --strict-merge`) — the CLI name is not the status context name. Soft-write or
73
75
  unverified-hook evidence alone does not mean the project is unfinished. See
package/docs/ai-gates.md CHANGED
@@ -44,6 +44,10 @@ advisory-local / hard-CI split is a deliberate trade-off: local surfaces optimiz
44
44
  while a required merge status is the one boundary a repository can make every write path share.
45
45
  Prefer fail-closed honesty over fake hard guarantees on advisory hosts.
46
46
 
47
+ **Write-gate order:** install and trust the host pre-hook first (hard when the host
48
+ can do it). MCP `ark_prepare_write` is fallback when that path is missing or
49
+ fail-open. OpenCode is MCP-only. Required CI is always the merge line.
50
+
47
51
  Everything below uses the same `ark.config.json` as `arkgate-check` / `ark-check` (CI) — one
48
52
  rules file shared by every surface. From **4.0**, optional **ArkRules** (`arkRules` map +
49
53
  `arkrules/*.json`) ride the **same** write path, doctor, and CI adapter; absence of ArkRules
@@ -350,9 +354,9 @@ alwaysApply: true
350
354
  ---
351
355
 
352
356
  Before trusting Ark MCP evidence, call `ark_identity` with `project.expectedRoot`
353
- set to the exact project root. Then call `ark_manifest`. Before writing TypeScript,
354
- prefer MCP `ark_prepare_write` / `validate_code` when available; the project
355
- `.cursor/hooks.json` hard gate still blocks invalid Write/StrReplace.
357
+ set to the exact project root. Then call `ark_manifest`. The write gate is the project `.cursor/hooks.json` pre-hook when installed,
358
+ trusted, and `failClosed`. Call MCP `ark_prepare_write` / `validate_code` only
359
+ as fallback if that hook is missing or fail-open.
356
360
  ```
357
361
 
358
362
  Your repository backstop remains CI: `ark-check` fails its check on anything that slips
@@ -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
 
@@ -248,11 +251,20 @@ process module-capability family must be denied.
248
251
  Rule fields:
249
252
 
250
253
  - `from`, `to`, `allowed`, `message`, `peerIsolation`, `sliceFolders`, `sharedRoots`,
251
- `allowedCrossSlice`
254
+ `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. The slice id is those directories
266
+ (`lib/features/projects/rfi`). A file directly under `lib/features/projects/` stays
267
+ `lib/features/projects`. A path off that prefix (`src/app/api/projects/...`) is not that slice.
256
268
 
257
269
  #### Cross-layer slice walls (already in the engine)
258
270
 
@@ -306,8 +318,11 @@ is evidence:
306
318
  tree you never declared. Write a deeper or monorepo root out (`packages/web/src/ui`) or glob it
307
319
  (`packages/*/src/ui`). Matching is case-insensitive; a bare `*` or `**` is refused, because one
308
320
  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.
321
+ `features/auth/ui/form.tsx` stays `features/auth`. Shared roots are a sink: a slice may import
322
+ a shared root, and a shared root may import a slice unless the rule sets `sharedImportsSlice`
323
+ to `"deny"`. That hop is direct only — `allowedCrossSlice` does not excuse it. Doctor lists
324
+ shared-root → slice edges even when the flag is off, so a green run still says the wall is
325
+ direct-only.
311
326
  - `allowedCrossSlice` entries match a full slice id (`features/catalog`) or a bare slice name
312
327
  (`catalog`), and only in the direction written. The reverse edge still denies. A bare name
313
328
  matches that name under **any** slice folder, so in a repo with several slice parents
@@ -398,7 +413,7 @@ Each `arkrules/<Layer>.json` may declare:
398
413
  | Section | Purpose | Modes | What it really enforces |
399
414
  |---------|---------|--------|-------------------------|
400
415
  | `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. |
416
+ | `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`); imports and test calls do not count. A test title may also name the invariant id. 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
417
 
403
418
  **Reporting:** diagnostics carry `evidence.arkruleId` + `evidence.arkruleSource`. Label residual
404
419
  **`[Layer]`** vs **`[ArkRules]`** in agent output. Doctor / HTML: `rulesUnderContract` (catalog +
package/docs/develop.md CHANGED
@@ -35,12 +35,11 @@ ArkGate solves this by shifting the check to the exact moment of writing. By int
35
35
  ## Default integration
36
36
 
37
37
  ```bash
38
- npm install -D arkgate typescript
39
38
  npx arkgate start --apply
40
39
  npx --package=arkgate arkgate-check --doctor
41
40
  ```
42
41
 
43
- `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.
44
43
 
45
44
  Make the import-rules check a **required** merge **status context** (GitHub/GitLab/etc.). The CLI
46
45
  command is `arkgate-check --strict-merge` / `ark-check --strict-merge` — the hard boundary is
@@ -59,7 +59,7 @@ hardening guide remains repository-hosted rather than duplicated in the gate tar
59
59
  | **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
60
  | **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
61
  | **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. |
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[]`; 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
63
  | **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
64
  | **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
65
  | **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 +74,7 @@ hardening guide remains repository-hosted rather than duplicated in the gate tar
74
74
  | **`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
75
  | **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
76
  | **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. |
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. 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, and `pilotLoop` stays inactive. |
78
78
  | **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
79
  | **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
80
  | **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. |
package/docs/use.md CHANGED
@@ -24,14 +24,13 @@ For **anyone** shipping TypeScript with an AI coding agent.
24
24
  ## In one minute
25
25
 
26
26
  ```bash
27
- npm install -D arkgate typescript
28
27
  npx arkgate start # preview what will change
29
28
  npx arkgate start --apply # install compact config + host router + CI; merge write-path gates into existing AGENTS.md / .mcp.json
30
29
  npx --package=arkgate arkgate-check --doctor # status — one next step
31
30
  npx --package=arkgate arkgate-check --doctor --all # full details
32
31
  ```
33
32
 
34
- `arkgate-check` is a command in the `arkgate` package, not its own npm package. After a local install, `npx arkgate-check --doctor` also works.
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.
35
34
 
36
35
  `start --apply` refuses when projected governed coverage is below 50% or
37
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.18",
3
+ "version": "4.8.20",
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"
@@ -828,6 +828,12 @@
828
828
  }
829
829
  }
830
830
  }
831
+ },
832
+ "sharedImportsSlice": {
833
+ "type": "string",
834
+ "enum": [
835
+ "deny"
836
+ ]
831
837
  }
832
838
  }
833
839
  },
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.18",
9
+ "version": "4.8.20",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "arkgate",
14
- "version": "4.8.18",
14
+ "version": "4.8.20",
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.18**
12
+ Package version when last generated context: **arkgate@4.8.20**
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,17 @@ 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) and the slice id is the directories the stars bind
370
+ (`lib/features/projects/rfi`). Before trusting green, add one deliberate
371
+ feature-to-feature import and expect `cross-slice`.
358
372
  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
373
  Empty Domain/Persistence + I/O under Application → false-green.
360
374
  **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`.
@@ -110,6 +110,8 @@ 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
+ A cohesion residual starts one reshape judgment card, not the pattern-bet apply loop.
114
+ `pilotLoop` stays inactive (`not-design-weak`) unless the tree is design-weak.
113
115
 
114
116
 
115
117
 
@@ -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
 
@@ -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
 
@@ -53,6 +53,11 @@ one bounded decision needs 2–3 enforceable options.
53
53
  `coverage.coverageRoots` on the folder the test runner uses. Fail-closed.
54
54
  `testGlobs` alone is not enough. Silent when no invariant is enforced.
55
55
  Handoff `/ark-adopt` to write the roots. No new skill.
56
+ - When an invariant declares `coverage.symbol`, read the witness path
57
+ (`symbolEvidenceFile`) and the discard line on doctor and `--rules-inventory`,
58
+ including when the run is green. A declaration in a non-test file is not
59
+ “the tests pass” — ArkGate never runs tests. Depth and byte caps stay
60
+ internal; the counts are what you read.
56
61
  - A doctor green / Healthy line must name a file, config key, or test.
57
62
  Uncited green is a lie — treat it as unfinished. No `/ark-cite`.
58
63
  - Distinguish **missing** skills vs **stale** catalog. Installed ≠ stale.
@@ -345,12 +350,19 @@ targets as explicit adopter decisions, not residual to re-fight. Keep their mirr
345
350
  map. When the user accepts, defers, or rejects a live `reshapePilot.nextPilot`, record the card's
346
351
  exact `decisionTarget` with a reason (and optional `reviewBy`) in
347
352
  `.ark/reshape-decisions.json`. Never infer or suppress from free-form golden-pattern prose.
353
+ A proposed `to` stays under the source anchor (`concept/`) and is kept only when that path
354
+ stays in the same layer and the same slice id. If no governed destination keeps both,
355
+ `nextPilot` is null — do not invent a folder outside the contract. Success is a smaller
356
+ cluster with the layer and slice id unchanged and the card still proposed; a green gate is
357
+ not success. A cohesion residual (`physical-cohesion-residual`) starts that one reshape
358
+ judgment card. It does not start the pattern-bet apply loop — `pilotLoop` stays inactive
359
+ while design-weak is false.
348
360
 
349
361
  ### C. Coupling & debt (measure, don’t guess)
350
362
  Prefer evidence over aesthetics:
351
363
  - **Fan-in / importers** of hot modules (who imports the suspect file?).
352
364
  - **Export surface** (how many public symbols does the orchestration module re-export?).
353
- - Import cycles, cross-feature leaks (`peerIsolation` candidates).
365
+ - Import cycles, cross-feature leaks (`peerIsolation` candidates). A shared root that imports a slice is a direct-only bridge; doctor lists it. `sharedImportsSlice: "deny"` closes that one hop. It does not finish the wall.
354
366
  - Business rules in UI/hooks (`can*`, `calculate*`, policy constants) → Domain / intents.
355
367
  - Dead or aspirational globs; layers with files but no rules; **starter/preset rules weaker than sibling archetypes** (soft green).
356
368
  - LOC is a **hint** only — pair it with fan-in or export count before calling something a god module.
@@ -21,6 +21,8 @@ right house. Skills never enforce — CLI / hooks / CI do.
21
21
  ## Checklist
22
22
 
23
23
  - `filePath` is known before the call. Description alone is not a path.
24
+ - Host write gate: prefer the installed pre-hook. MCP `ark_prepare_write` is
25
+ fallback when that hook is missing or fail-open. Skills never enforce.
24
26
  - Golden pattern is load-bearing when present. Adopt generates it.
25
27
  - An included file that matches no layer is not a pass. The write gate denies it
26
28
  (`CONFIG_UNCLASSIFIED_FILES`). Place it in a layer folder, or send include/layer
@@ -259,9 +259,11 @@ ArkGate has **always-on Layers** plus opt-in extras. The user chooses extras; yo
259
259
  If already current (`ALREADY_CURRENT`), retain the read-only preview and still read `whatsNew`
260
260
  + `skillDrift`.
261
261
 
262
- For pnpm repositories with `minimumReleaseAge`, use the repository's existing
263
- trusted first-party exception mechanism when the new release is still cooling
264
- off, and prove `pnpm install --frozen-lockfile` succeeds.
262
+ For pnpm repositories with `minimumReleaseAge`, put the exception on
263
+ `minimumReleaseAgeExclude` in pnpm config or `pnpm-workspace.yaml` (a CLI
264
+ flag is not enough) when the new release is still cooling off, or keep using
265
+ `npx --package=arkgate@<pin>` until the package is mature. Prove
266
+ `pnpm install --frozen-lockfile` succeeds after a local add.
265
267
 
266
268
  4. **Apply only the reviewed candidate.** When there are no blocked assets, run
267
269
  the preview's **exact** `nextCommand` as emitted (JSON field / human “Apply the
@@ -8,8 +8,8 @@
8
8
  *
9
9
  * Honesty: OpenCode plugin hooks are **not** a complete hard write boundary
10
10
  * (subagent and alternate tool paths may bypass). Never treat this plugin as
11
- * Claude/Grok/Antigravity PreToolUse hard-write. Prefer MCP `ark_prepare_write`
12
- * + required merge status.
11
+ * Claude/Grok/Antigravity PreToolUse hard-write. This host has no hard pre-hook,
12
+ * so MCP `ark_prepare_write` is the local write fallback + required merge status.
13
13
  *
14
14
  * This file is a gallery template shipped with arkgate — not auto-installed.
15
15
  */
@@ -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,17 @@ 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) and the slice id is the directories the stars bind
370
+ (`lib/features/projects/rfi`). Before trusting green, add one deliberate
371
+ feature-to-feature import and expect `cross-slice`.
358
372
  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
373
  Empty Domain/Persistence + I/O under Application → false-green.
360
374
  **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`.
@@ -110,6 +110,8 @@ 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
+ A cohesion residual starts one reshape judgment card, not the pattern-bet apply loop.
114
+ `pilotLoop` stays inactive (`not-design-weak`) unless the tree is design-weak.
113
115
 
114
116
 
115
117
 
@@ -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
 
@@ -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
 
@@ -53,6 +53,11 @@ one bounded decision needs 2–3 enforceable options.
53
53
  `coverage.coverageRoots` on the folder the test runner uses. Fail-closed.
54
54
  `testGlobs` alone is not enough. Silent when no invariant is enforced.
55
55
  Handoff `/ark-adopt` to write the roots. No new skill.
56
+ - When an invariant declares `coverage.symbol`, read the witness path
57
+ (`symbolEvidenceFile`) and the discard line on doctor and `--rules-inventory`,
58
+ including when the run is green. A declaration in a non-test file is not
59
+ “the tests pass” — ArkGate never runs tests. Depth and byte caps stay
60
+ internal; the counts are what you read.
56
61
  - A doctor green / Healthy line must name a file, config key, or test.
57
62
  Uncited green is a lie — treat it as unfinished. No `/ark-cite`.
58
63
  - Distinguish **missing** skills vs **stale** catalog. Installed ≠ stale.
@@ -345,12 +350,19 @@ targets as explicit adopter decisions, not residual to re-fight. Keep their mirr
345
350
  map. When the user accepts, defers, or rejects a live `reshapePilot.nextPilot`, record the card's
346
351
  exact `decisionTarget` with a reason (and optional `reviewBy`) in
347
352
  `.ark/reshape-decisions.json`. Never infer or suppress from free-form golden-pattern prose.
353
+ A proposed `to` stays under the source anchor (`concept/`) and is kept only when that path
354
+ stays in the same layer and the same slice id. If no governed destination keeps both,
355
+ `nextPilot` is null — do not invent a folder outside the contract. Success is a smaller
356
+ cluster with the layer and slice id unchanged and the card still proposed; a green gate is
357
+ not success. A cohesion residual (`physical-cohesion-residual`) starts that one reshape
358
+ judgment card. It does not start the pattern-bet apply loop — `pilotLoop` stays inactive
359
+ while design-weak is false.
348
360
 
349
361
  ### C. Coupling & debt (measure, don’t guess)
350
362
  Prefer evidence over aesthetics:
351
363
  - **Fan-in / importers** of hot modules (who imports the suspect file?).
352
364
  - **Export surface** (how many public symbols does the orchestration module re-export?).
353
- - Import cycles, cross-feature leaks (`peerIsolation` candidates).
365
+ - Import cycles, cross-feature leaks (`peerIsolation` candidates). A shared root that imports a slice is a direct-only bridge; doctor lists it. `sharedImportsSlice: "deny"` closes that one hop. It does not finish the wall.
354
366
  - Business rules in UI/hooks (`can*`, `calculate*`, policy constants) → Domain / intents.
355
367
  - Dead or aspirational globs; layers with files but no rules; **starter/preset rules weaker than sibling archetypes** (soft green).
356
368
  - LOC is a **hint** only — pair it with fan-in or export count before calling something a god module.
@@ -21,6 +21,8 @@ right house. Skills never enforce — CLI / hooks / CI do.
21
21
  ## Checklist
22
22
 
23
23
  - `filePath` is known before the call. Description alone is not a path.
24
+ - Host write gate: prefer the installed pre-hook. MCP `ark_prepare_write` is
25
+ fallback when that hook is missing or fail-open. Skills never enforce.
24
26
  - Golden pattern is load-bearing when present. Adopt generates it.
25
27
  - An included file that matches no layer is not a pass. The write gate denies it
26
28
  (`CONFIG_UNCLASSIFIED_FILES`). Place it in a layer folder, or send include/layer
@@ -259,9 +259,11 @@ ArkGate has **always-on Layers** plus opt-in extras. The user chooses extras; yo
259
259
  If already current (`ALREADY_CURRENT`), retain the read-only preview and still read `whatsNew`
260
260
  + `skillDrift`.
261
261
 
262
- For pnpm repositories with `minimumReleaseAge`, use the repository's existing
263
- trusted first-party exception mechanism when the new release is still cooling
264
- off, and prove `pnpm install --frozen-lockfile` succeeds.
262
+ For pnpm repositories with `minimumReleaseAge`, put the exception on
263
+ `minimumReleaseAgeExclude` in pnpm config or `pnpm-workspace.yaml` (a CLI
264
+ flag is not enough) when the new release is still cooling off, or keep using
265
+ `npx --package=arkgate@<pin>` until the package is mature. Prove
266
+ `pnpm install --frozen-lockfile` succeeds after a local add.
265
267
 
266
268
  4. **Apply only the reviewed candidate.** When there are no blocked assets, run
267
269
  the preview's **exact** `nextCommand` as emitted (JSON field / human “Apply the