arkgate 4.8.14 → 4.8.15

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 (97) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/README.md +13 -5
  3. package/bin/ark-check-runtime.mjs +17 -49
  4. package/bin/ark-mcp-runtime.mjs +111 -2
  5. package/bin/ark-shared.mjs +140 -11
  6. package/bin/ark.mjs +7 -25
  7. package/bin/lib/adr-presence.mjs +97 -0
  8. package/bin/lib/agent-skills-package.mjs +179 -1
  9. package/bin/lib/analysis-completeness.mjs +38 -2
  10. package/bin/lib/analysis-engine.mjs +6 -6
  11. package/bin/lib/architecture-scan.mjs +26 -4
  12. package/bin/lib/ark-run-doctor.mjs +6 -0
  13. package/bin/lib/ark-run-report.mjs +6 -2
  14. package/bin/lib/arkrules-sensors.mjs +81 -0
  15. package/bin/lib/baseline-key.mjs +4 -1
  16. package/bin/lib/check-args.mjs +52 -2
  17. package/bin/lib/config-contract.mjs +64 -1
  18. package/bin/lib/diagnostic-catalog.mjs +3 -1
  19. package/bin/lib/doctor-advisories.mjs +94 -10
  20. package/bin/lib/doctor-human.mjs +18 -5
  21. package/bin/lib/doctor-next-actions.mjs +14 -0
  22. package/bin/lib/doctor-plan.mjs +32 -2
  23. package/bin/lib/enforcement-honesty.mjs +47 -2
  24. package/bin/lib/first-run-help.mjs +8 -1
  25. package/bin/lib/host-support-matrix.mjs +9 -1
  26. package/bin/lib/html-report-depth.mjs +2 -0
  27. package/bin/lib/html-report.mjs +13 -2
  28. package/bin/lib/import-resolve.mjs +74 -13
  29. package/bin/lib/improvement-compass-doctor.mjs +6 -1
  30. package/bin/lib/improvement-compass-map.mjs +3 -1
  31. package/bin/lib/layer-description.mjs +90 -8
  32. package/bin/lib/mcp-hook-payload.mjs +56 -0
  33. package/bin/lib/no-domain-frontend.mjs +91 -0
  34. package/bin/lib/presets.mjs +3 -4
  35. package/bin/lib/product-copy.mjs +15 -0
  36. package/bin/lib/projected-governed-coverage.mjs +114 -0
  37. package/bin/lib/recommend-cli.mjs +54 -0
  38. package/bin/lib/remediation.mjs +4 -0
  39. package/bin/lib/resolved-candidate-facts.mjs +83 -66
  40. package/bin/lib/rules-under-contract.mjs +117 -2
  41. package/bin/lib/snippet-analysis.mjs +14 -8
  42. package/bin/lib/start-preview.mjs +5 -2
  43. package/bin/lib/states-transitions-presence.mjs +212 -0
  44. package/bin/lib/status-command.mjs +2 -0
  45. package/bin/lib/status-transition-catalog.mjs +410 -0
  46. package/bin/lib/team-parliament-io.mjs +10 -0
  47. package/bin/lib/violations.mjs +13 -1
  48. package/bin/lib/write-path-capabilities.mjs +20 -5
  49. package/bin/lib/write-path-detect.mjs +27 -2
  50. package/dist/{configTypes-j7so8B4O.d.ts → configTypes-Dt3DpVbd.d.ts} +19 -0
  51. package/dist/{diagnosticCatalog-DVx_2RmF.d.ts → diagnosticCatalog-BEg85XlE.d.ts} +3 -3
  52. package/dist/eslint/index.cjs +4 -4
  53. package/dist/eslint/index.d.ts +1 -1
  54. package/dist/eslint/index.js +4 -4
  55. package/dist/index.cjs +31 -31
  56. package/dist/index.d.ts +70 -9
  57. package/dist/index.js +31 -31
  58. package/dist/nestjs/index.cjs +5 -5
  59. package/dist/nestjs/index.d.ts +3 -3
  60. package/dist/nestjs/index.js +5 -5
  61. package/dist/runtime/index.cjs +15 -15
  62. package/dist/runtime/index.d.ts +6 -6
  63. package/dist/runtime/index.js +15 -15
  64. package/dist/{types-Djbs3KjE.d.ts → types-CN9tVMPz.d.ts} +3 -1
  65. package/dist/{types-tGhZUiGX.d.ts → types-TBiv0WHL.d.ts} +1 -1
  66. package/docs/README.md +1 -1
  67. package/docs/agent-guide.md +6 -4
  68. package/docs/ai-gates.md +10 -3
  69. package/docs/brownfield-adoption.md +4 -1
  70. package/docs/configuration.md +55 -7
  71. package/docs/develop.md +1 -0
  72. package/docs/diagnostics.md +23 -3
  73. package/docs/enthusiast/how-to-agent-gates.md +5 -0
  74. package/docs/enthusiast/how-to-pick-shape.md +1 -1
  75. package/docs/package-surface.md +7 -5
  76. package/docs/use.md +5 -0
  77. package/package.json +1 -1
  78. package/schemas/ark.config.schema.json +23 -2
  79. package/server.json +2 -2
  80. package/templates/agent-skills/README.md +1 -1
  81. package/templates/agent-skills/ark-adopt/SKILL.md +82 -1
  82. package/templates/agent-skills/ark-autopilot/SKILL.md +6 -2
  83. package/templates/agent-skills/ark-coverage/SKILL.md +1 -1
  84. package/templates/agent-skills/ark-explain/SKILL.md +4 -2
  85. package/templates/agent-skills/ark-explore/SKILL.md +14 -1
  86. package/templates/agent-skills/ark-place/SKILL.md +38 -3
  87. package/templates/agent-skills/ark-runtime/SKILL.md +4 -2
  88. package/templates/agent-skills/ark-upgrade/SKILL.md +1 -1
  89. package/templates/arkrules/DomainModel.json +14 -1
  90. package/templates/skills/ark-adopt.md +82 -1
  91. package/templates/skills/ark-autopilot.md +6 -2
  92. package/templates/skills/ark-coverage.md +1 -1
  93. package/templates/skills/ark-explain.md +4 -2
  94. package/templates/skills/ark-explore.md +14 -1
  95. package/templates/skills/ark-place.md +38 -3
  96. package/templates/skills/ark-runtime.md +4 -2
  97. package/templates/skills/ark-upgrade.md +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,139 @@ in the immutable pre-2.0 archive linked below.
5
5
 
6
6
  ## Unreleased
7
7
 
8
+ ### Added
9
+ - Soft doctor residual when Domain is declared but empty and the UI holds the
10
+ rules (`noDomainFrontend`). Projects empty Domain + presentation share, or
11
+ the existing `domain-logic-in-ui` smell. Friendly next step: one Domain file
12
+ (`/ark-place`) then one small refactor. Not a fail. Silent when there is no
13
+ frontend, Domain already has files, or the UI bag is too thin. No new skill,
14
+ schema, smell id, or config key.
15
+ - Narrow opt-in status/transition catalog: when Domain-role code already
16
+ names a closed status/state vocabulary and a domain doc is in play without
17
+ a table (or one link), doctor JSON may add `statusTransitionCatalog` with
18
+ those names. Friendly next step. Not a fail. Silent when Domain has no
19
+ status vocabulary, or the domain doc is absent, or the map is already
20
+ present. No new skill, schema, or config key.
21
+ - Doctor residual when a product-domain doc is already in play
22
+ (`docs/domain.md` or a sibling) and there is no states → transitions table
23
+ (or one link) yet. Friendly next step. Not a fail. Silent when that doc is
24
+ absent. No new skill, schema, or status-field sensor.
25
+ - Soft ADR / decision-note presence when `--require-gates` (or
26
+ `--strict` / `--strict-merge` / doctor adopted-strict) is on and the tree
27
+ has no `docs/adr/` or `docs/decisions/` note yet. Doctor residual + a
28
+ friendly next step. Not a fail. Silent when require-gates is off. No new
29
+ skill, schema, or config key. Policy weaken still uses `--policy-ack`.
30
+ - Optional `layers[].owners` on each layer (GitHub handle or email, same
31
+ identity as `stewards`) and optional `requireLayerOwners`. Absence is
32
+ silent. When the require flag is on, doctor names the first house without
33
+ an owner, `--strict-config` refuses, and the write gate denies a write into
34
+ that house. Owners are stripped from `policyHash`; the require flag stays
35
+ in the hash. Invalid identity fails the schema. No new skill or
36
+ `schemaVersion` bump. Compact starters may omit.
37
+ - Optional `layers[].trustBoundary` on each layer: `public` | `auth` | `admin` |
38
+ `internal`. Who this folder is for. Schema validates the closed list; a wrong
39
+ value fails. Absence is silent — never a doctor residual, never
40
+ `--strict-config`, never a score. Place / prepare-write / MCP / doctor /
41
+ coverage / HTML show `trust: public` when present. Stripped from `policyHash`
42
+ like `stewards` and `layers[].description`. No import-rule matrix. Not host/CI
43
+ TLS. No new skill or `schemaVersion` bump. Compact starters may omit.
44
+ - Doctor residual when `arkRules` is on, Domain has code, and `invariants[]` is
45
+ empty (`INVARIANT_CATALOG_EMPTY`). Points at the file to fill. Advisory unless
46
+ a domain structure rule is already enforced — then `--strict-merge` can refuse.
47
+ No `arkRules` map, or no Domain files, stays silent. Domain starters ship two
48
+ short phrase templates so the shape is visible. No new skill, schema, or flag.
49
+
50
+ ### Changed
51
+ - Compact `--doctor` names ArkRules only when the `arkRules` map is on
52
+ (one breath + counts, not a score). Absence stays silent. Reuses
53
+ `rulesUnderContract` — no new schema, flag, or skill. `--doctor --all`
54
+ prints the same lines under **ArkRules (not a score)**.
55
+ - PR / CI skill gate: `npm run check:agent-skills` now fails closed when a
56
+ product plane (Layers, ArkRules, ArkRun, ArkOrder) or Contener · Guiar ·
57
+ Ordenar drops out of first-class skill bodies or living hubs. Reuses
58
+ `ARK_SKILL_CAPACITY` via Domain `validateSkillProductCapacity` — no new
59
+ schema or skill. Deny copy stays plain. Skills never enforce.
60
+ - Write-gate host parity: Cursor hard-write evidence now requires the host-native
61
+ `failClosed: true` flag (same idea as a file permission — if the checker cannot
62
+ run, the write must not land). A Write/StrReplace hook without that flag is
63
+ fail-open: doctor, `--require-write-hook`, and honesty JSON say so and do not
64
+ claim a hard block. Hosts without a native flag stay as before (deny when the
65
+ hook runs; hook-crash behavior is host-owned). Soft hosts stay advisory.
66
+ Required CI is still the shared merge line. No new skill, schema, or host.
67
+
68
+ ### Fixed
69
+ - Write hook and `ark-check` now agree on overlapping layer globs: the hook
70
+ probes the same specifier extensions as `ark-check` and classifies with
71
+ `layerForRelativePath` (explicit `money.ts` beats `src/lib/**`). Field
72
+ case: Persistence importing `@/lib/finance/money` is allowed when the
73
+ contract says DomainModel, not denied as ApplicationOrchestration
74
+ (issue [#237](https://github.com/pedroknigge/arkgate/issues/237)).
75
+ `LEXICAL_EVIDENCE_INCOMPLETE` no longer calls the hook the final verdict —
76
+ `ark-check` is the authority. No new schema, flag, or skill.
77
+ - An `include` that matches files but classifies none of them no longer prints
78
+ a green check. `ANALYSIS_COVERS_NO_FILES` already said include **and** layer;
79
+ the refusal now counts classified files, not only the include walk. The write
80
+ hook denies those same files (`CONFIG_UNCLASSIFIED_FILES`) so write and
81
+ `--strict-merge` agree. Partial unclassified stays a warning on a plain check.
82
+ `--plan` / `--coverage` / `--doctor` still diagnose. No new flag or schema.
83
+ - Library / package-monorepo `arkgate start` no longer writes Next App Router /
84
+ Pages API / `app/api` layer captions when the tree has no Next
85
+ (issue [#213](https://github.com/pedroknigge/arkgate/issues/213)). Next-flavored
86
+ doctor copy still appears when Next is detected. Same start command; no new flag.
87
+ - Cold `--doctor` no longer treats a full governed list as a `--changed` scope
88
+ (issue [#212](https://github.com/pedroknigge/arkgate/issues/212)). Full-tree
89
+ status uses the unbounded facts path; import-closure + a second
90
+ `resolveModuleName` pass stay on `--changed` only. Extra-plane extractors
91
+ (ArkRun / ArkOrder / class-shape) stay silent when that plane is off. Same
92
+ compact first screen. No new flag or cache.
93
+ - Fresh `arkgate start --apply` (and every other path that stamps a new
94
+ `ark.config.json`) now writes `$schema` at `arkgate@4` on unpkg, not the stale
95
+ `@2` pin (issue [#211](https://github.com/pedroknigge/arkgate/issues/211)).
96
+ Existing configs keep whatever URL they already have. Editor completion
97
+ matches the 4.x line you just installed.
98
+
99
+ ## 4.8.15 — 2026-09-13
100
+
101
+ **Patch** over **4.8.14**. Pre-adoption ship bar: opt-in `--local` /
102
+ `ARK_CHECK_LOCAL` cheap multi-worktree check
103
+ ([#251](https://github.com/pedroknigge/arkgate/pull/251)) and dogfood
104
+ honesty
105
+ ([#252](https://github.com/pedroknigge/arkgate/pull/252) / [#243](https://github.com/pedroknigge/arkgate/issues/243) /
106
+ [#246](https://github.com/pedroknigge/arkgate/issues/246) /
107
+ [#247](https://github.com/pedroknigge/arkgate/issues/247)).
108
+ **Write. Check. Ship.** **No required config migration.** No
109
+ `schemaVersion` bump. Does not close `K01` / `Z09`. This mother
110
+ `ark.config.json` still does **not** turn `arkOrder` on.
111
+
112
+ **Status: prepared** (npm `latest` remains **4.8.14** until Actions `publish-npm`
113
+ runs with `tag=v4.8.15` and `dry_run=false`).
114
+
115
+ ### Added
116
+ - Opt-in local / multi-worktree cheap check: `--local` or `ARK_CHECK_LOCAL=1`
117
+ reuses `--changed` (touched files + import closure) and stays per `--root`.
118
+ Explicit `--local --strict-merge` is refused so CI Contener stays fail-closed.
119
+ The env var is ignored under `--strict-merge` and under `--doctor` /
120
+ `--coverage` / `--plan` / `--report` / `--promote`. JSON may add `local`,
121
+ `scope: "changed"`, `analysisRoot`. Write hooks stay lexical. No new schema,
122
+ cache, skill, or second analysis engine. Design:
123
+ [docs/plans/local-check-worktree-dx/README.md](docs/plans/local-check-worktree-dx/README.md).
124
+
125
+ ### Fixed
126
+ - Publish confidence gate: retarget drifted critical mutation islands
127
+ (`config-loading`, `empty-analysis-refusal`, host fail-open / none-gap)
128
+ after owners + classified-file growth, and add tests that kill those
129
+ mutants. Threshold stays 90% with zero NoCoverage. Does not change
130
+ runtime behavior.
131
+ - Dogfood honesty: type-only `LAYER_IMPORT_VIOLATION` prints `⚠` (not `✖`)
132
+ when the check still exits 0 ([#243](https://github.com/pedroknigge/arkgate/issues/243));
133
+ one-minute `start --apply` copy and the start preview footer name the
134
+ deliberate lock flags (`--archetype`, `--preset`, `--force`,
135
+ `ark-check --recommend`) without softening the refuse gate
136
+ ([#246](https://github.com/pedroknigge/arkgate/issues/246)); start,
137
+ `--recommend`, and doctor share one `computeCoverage` definition of
138
+ projected governed coverage
139
+ ([#247](https://github.com/pedroknigge/arkgate/issues/247)).
140
+
8
141
  ## 4.8.14 — 2026-09-06
9
142
 
10
143
  **Patch** over **4.8.13**. Catch npm `latest` up to `main`: first-class
package/README.md CHANGED
@@ -39,7 +39,7 @@ Works with Cursor, Claude, Codex, and Grok.
39
39
 
40
40
  </div>
41
41
 
42
- > **ArkGate 4.8.14** is prepared on this tree; npm `latest` remains **4.8.13** until `publish-npm` for `v4.8.14`.
42
+ > **ArkGate 4.8.15** is prepared on this tree; npm `latest` remains **4.8.14** until `publish-npm` for `v4.8.15`.
43
43
  > Write. Check. Ship. Adopted = required GitHub
44
44
  > status running `arkgate-check --strict-merge`, or an explicit `advisory-only` stance.
45
45
  > Status is compact (`arkgate-check --doctor`; `--all` for Details). Optional **ArkRun**
@@ -47,7 +47,7 @@ Works with Cursor, Claude, Codex, and Grok.
47
47
  > (`arkgate/order`) stops rewriting a big product choice — like the billing plan —
48
48
  > as if it were a seat count. Change those choices through a valve, not a generic update.
49
49
  > `@arkgate/runtime` is deprecated.
50
- > [4.8.14 prepared](CHANGELOG.md) · [4.8.11 published](docs/releases/4.8.11.md) · [4.8.10](docs/releases/4.8.10.md) · [4.8.9](docs/releases/4.8.9.md) · [4.8.8](docs/releases/4.8.8.md) · [4.8.7](docs/releases/4.8.7.md) · [4.8.6](docs/releases/4.8.6.md) · [4.8.5](docs/releases/4.8.5.md) · [4.8.4](docs/releases/4.8.4.md) · [4.8.3](docs/releases/4.8.3.md) · [4.8.2](docs/releases/4.8.2.md) · [4.8.1](docs/releases/4.8.1.md) · [4.8.0](docs/releases/4.8.0.md) · [Docs hub](docs/README.md) · [Voice](docs/product-voice.md)
50
+ > [4.8.15 prepared](CHANGELOG.md) · [4.8.11 published](docs/releases/4.8.11.md) · [4.8.10](docs/releases/4.8.10.md) · [4.8.9](docs/releases/4.8.9.md) · [4.8.8](docs/releases/4.8.8.md) · [4.8.7](docs/releases/4.8.7.md) · [4.8.6](docs/releases/4.8.6.md) · [4.8.5](docs/releases/4.8.5.md) · [4.8.4](docs/releases/4.8.4.md) · [4.8.3](docs/releases/4.8.3.md) · [4.8.2](docs/releases/4.8.2.md) · [4.8.1](docs/releases/4.8.1.md) · [4.8.0](docs/releases/4.8.0.md) · [Docs hub](docs/README.md) · [Voice](docs/product-voice.md)
51
51
 
52
52
  ---
53
53
 
@@ -73,6 +73,11 @@ npx arkgate-check --doctor # status — one next step
73
73
  npx arkgate-check --doctor --all # full details
74
74
  ```
75
75
 
76
+ `start --apply` refuses when projected governed coverage is below 50% or
77
+ shape confidence is weak (below 0.6 with coverage under 80%). That lock is
78
+ deliberate. Lock the shape with `--archetype <id>`, `--preset <name>`, or
79
+ `--force`, or inspect ranked shapes with `npx arkgate-check --recommend`.
80
+
76
81
  That is the product. Stuck? Run status (`--doctor`) and do action **#1**.
77
82
 
78
83
  ```text
@@ -81,7 +86,8 @@ start → doctor → new files in the right folder
81
86
  ```
82
87
 
83
88
  Keep the rules file out of product PRs. Local check:
84
- `ark-check --changed --base origin/dev`. Changing the rules themselves uses `--contract-session`.
89
+ `ark-check --local --base origin/dev` (same as `--changed`; refused with
90
+ `--strict-merge`). Changing the rules themselves uses `--contract-session`.
85
91
 
86
92
  Aliases `ark` / `ark-check` / `ark-mcp` still work. npm / pnpm / yarn. No install lifecycle scripts
87
93
  — and none on pack or prepare either, so `pnpm add git+https://github.com/pedroknigge/arkgate`
@@ -188,7 +194,9 @@ Details: [docs/use.md](docs/use.md).
188
194
  **Read the CI column:** for every host, the repository-wide hard guarantee is a **required**
189
195
  GitHub **status context** that runs the CLI — not “CI file present,” and not the CLI binary name alone.
190
196
  Codex hard write covers only a complete local `apply_patch`; Cursor covers only listed
191
- `preToolUse` ops. In both cases the project hook must be installed + trusted, while shell/direct
197
+ `preToolUse` ops with `failClosed: true`. A Cursor hook without that flag is fail-open
198
+ (if the checker cannot run, the write still lands). In both cases the project hook must be
199
+ installed + trusted, while shell/direct
192
200
  filesystem writes, hosted or specialized opt-out paths, and human edits still rely on CI.
193
201
 
194
202
  This table describes the supported profile **after its files are installed and the host loads/trusts them**. A hard local boundary covers only the listed hook operations; alternate tools, direct filesystem writes, and human edits still rely on CI. MCP validation is advisory because the agent must call it. The CI check blocks a merge only when the repository makes that status required. Repair **envelopes** may be emitted without reinjection being guaranteed; silent auto-apply never happens. Run `arkgate-check --doctor` (or `ark-check --doctor`) for the evidence actually detected in the current repository.
@@ -334,7 +342,7 @@ Compact starters leave the extra off. Details: [ArkOrder](docs/arkorder.md).
334
342
  | Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
335
343
  | Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
336
344
  | Security | [SECURITY.md](SECURITY.md) |
337
- | Prepared (4.8.14; not published) | [CHANGELOG](CHANGELOG.md) |
345
+ | Prepared (4.8.15; not published) | [CHANGELOG](CHANGELOG.md) |
338
346
  | Current published (4.8.11 on npm `latest`) | [docs/releases/4.8.11.md](docs/releases/4.8.11.md) · [CHANGELOG](CHANGELOG.md) |
339
347
  | Prior published (4.8.10) | [docs/releases/4.8.10.md](docs/releases/4.8.10.md) · [CHANGELOG](CHANGELOG.md) |
340
348
  | Prior published (4.8.9) | [docs/releases/4.8.9.md](docs/releases/4.8.9.md) · [CHANGELOG](CHANGELOG.md) |
@@ -12,17 +12,13 @@ import {
12
12
  DEFAULT_RULES,
13
13
  applyFrameworkLayoutOverlays,
14
14
  arkCommand,
15
- ADOPTION_PLAN_FILENAME,
16
- buildArchitectureRecommendation,
17
15
  createElevenLayerConfig,
18
16
  enrichViolationWithFixClass,
19
17
  listPolicyPackIds,
20
18
  loadPolicyPackMeta,
21
- writeAdoptionPlan,
22
19
  detectWorkspaces,
23
20
  detectTsPackageRoots,
24
21
  resolveIncludeRoots,
25
- formatArchitectureRecommendationHuman,
26
22
  installDevHint,
27
23
  layerForFile,
28
24
  } from './ark-shared.mjs';
@@ -67,6 +63,7 @@ import {
67
63
  runCoverage,
68
64
  runPlan,
69
65
  runDoctor,
66
+ printAdrPresenceHint,
70
67
  } from './lib/doctor-plan.mjs';
71
68
  import { runRatchetCores } from './lib/core-ratchet.mjs';
72
69
  import {
@@ -113,7 +110,7 @@ import { runArchitectureScan } from './lib/architecture-scan.mjs';
113
110
  import {
114
111
  ANALYSIS_COMPLETENESS,
115
112
  analysisIncompleteStatement,
116
- emptyAnalysisRefusal,
113
+ emptyAnalysisRefusal, classifiedCountFromFiles,
117
114
  } from './lib/analysis-completeness.mjs';
118
115
  import { reportUnavailableAnalysis } from './lib/unavailable-analysis.mjs';
119
116
  import { validateHardWriteRequest } from './lib/enforcement-profiles.mjs';
@@ -137,7 +134,7 @@ import {
137
134
  resolveEffectiveProjectRoot,
138
135
  } from './lib/project-root.mjs';
139
136
  import { demoteArkRuleTeethUnderClassificationFloor } from './lib/rules-under-contract.mjs';
140
- import { parseArgs, resolveDesignDeltaBaseRef } from './lib/check-args.mjs';
137
+ import { localCheckEnvelope, parseArgs, resolveDesignDeltaBaseRef } from './lib/check-args.mjs';
141
138
  import { detectConfig, proposeForUncovered } from './lib/check-config-detect.mjs';
142
139
  import { runWatchMode } from './lib/check-watch.mjs';
143
140
 
@@ -1153,42 +1150,8 @@ async function main() {
1153
1150
  }
1154
1151
 
1155
1152
  if (args.recommend) {
1156
- try {
1157
- const recommendation = buildArchitectureRecommendation(args.root);
1158
- let planWritten;
1159
- if (args.writePlan) {
1160
- const result = writeAdoptionPlan(args.root, recommendation);
1161
- planWritten = result.path;
1162
- }
1163
- if (args.json) {
1164
- console.log(
1165
- JSON.stringify(
1166
- {
1167
- ...recommendation,
1168
- ...(planWritten
1169
- ? { adoptionPlanPath: path.relative(args.root, planWritten) || ADOPTION_PLAN_FILENAME }
1170
- : {}),
1171
- },
1172
- null,
1173
- 2
1174
- )
1175
- );
1176
- } else {
1177
- console.log(formatArchitectureRecommendationHuman(recommendation));
1178
- if (planWritten) {
1179
- console.log('');
1180
- console.log(`Wrote ${path.relative(args.root, planWritten) || ADOPTION_PLAN_FILENAME}`);
1181
- }
1182
- }
1183
- } catch (error) {
1184
- const message = error instanceof Error ? error.message : String(error);
1185
- if (args.json) {
1186
- console.log(JSON.stringify({ ok: false, error: message }, null, 2));
1187
- } else {
1188
- console.error(`ark-check --recommend failed: ${message}`);
1189
- }
1190
- process.exitCode = 2;
1191
- }
1153
+ const { runRecommend } = await import('./lib/recommend-cli.mjs');
1154
+ runRecommend(args);
1192
1155
  return;
1193
1156
  }
1194
1157
 
@@ -1209,9 +1172,11 @@ async function main() {
1209
1172
  let governedCache = null;
1210
1173
  const loadGovernedFiles = () => (governedCache ??= collectGovernedFiles(root, loadConfig()));
1211
1174
  const emptyAnalysisRefusalNow = () => {
1212
- const governedCount = loadGovernedFiles().length;
1175
+ const governedFiles = loadGovernedFiles();
1176
+ const governedCount = governedFiles.length;
1213
1177
  return emptyAnalysisRefusal({
1214
1178
  governedFileCount: governedCount,
1179
+ classifiedFileCount: classifiedCountFromFiles(governedFiles, loadConfig().layers, layerForFile, root),
1215
1180
  // Probed only when nothing is governed, and never through the contract's own
1216
1181
  // exclude: the config under suspicion must not get to answer the question about
1217
1182
  // itself (`exclude: ["**"]` would otherwise read as greenfield and pass).
@@ -1349,7 +1314,7 @@ async function main() {
1349
1314
  (compactHost
1350
1315
  ? `AGENTS.md, compact host registration (${compactHost})`
1351
1316
  : REQUIRED_GATE_FILES.join(', '))
1352
- );
1317
+ ); printAdrPresenceHint(args.root, (line) => console.log(line));
1353
1318
  }
1354
1319
  if (writeRequest?.host) {
1355
1320
  console.log(`Ark hard-write hook present for ${writeRequest.host}.`);
@@ -1384,7 +1349,7 @@ async function main() {
1384
1349
  JSON.stringify(
1385
1350
  {
1386
1351
  ok: preflight.halt.exitCode === 0,
1387
- ...(preflight.halt.cheap ? { cheap: true } : {}),
1352
+ ...(preflight.halt.cheap ? { cheap: true } : {}), ...localCheckEnvelope(args, root),
1388
1353
  teamParliament: preflight.halt.teamParliament,
1389
1354
  ...(policyDelta ? { policyDelta } : {}),
1390
1355
  },
@@ -1407,7 +1372,7 @@ async function main() {
1407
1372
  }
1408
1373
  const manifest = readManifest(root, args.manifest);
1409
1374
  const rules = manifest?.architecture?.rules ?? config.rules;
1410
- if (verdictPath && !args.changed) {
1375
+ if (verdictPath && !args.changed && !isMutatingCliCommand(args)) {
1411
1376
  const refusal = emptyAnalysisRefusalNow();
1412
1377
  if (refusal) {
1413
1378
  reportEmptyAnalysis(refusal);
@@ -1473,7 +1438,10 @@ async function main() {
1473
1438
  config,
1474
1439
  manifest,
1475
1440
  rules,
1476
- files,
1441
+ // Full-tree callers already have the governed list. Passing it as `files`
1442
+ // used to trigger the --changed scoped path (import-closure + a second
1443
+ // resolveModuleName pass) on every doctor/check (#212). Empty = unbounded.
1444
+ files: args.changed ? files : [],
1477
1445
  ts,
1478
1446
  args,
1479
1447
  });
@@ -1534,7 +1502,7 @@ async function main() {
1534
1502
  configWalkedUp: args.configWalkedUp === true,
1535
1503
  safety, designDelta,
1536
1504
  ts, parseHealth, completeness,
1537
- all: args.all === true,
1505
+ all: args.all === true, requireGates: args.requireGates === true,
1538
1506
  });
1539
1507
  if (designDelta) process.exitCode = !designDelta.complete ? 2 : designDelta.valid ? 0 : 1; return;
1540
1508
  }
@@ -1946,7 +1914,7 @@ async function main() {
1946
1914
  });
1947
1915
  console.log(JSON.stringify({
1948
1916
  ...adapterResult,
1949
- ok,
1917
+ ok, ...localCheckEnvelope(args, root),
1950
1918
  violations: activeViolations.map(enrichViolationWithFixClass),
1951
1919
  suppressedViolations: suppressed.length,
1952
1920
  staleBaselineKeys,
@@ -82,7 +82,10 @@ import {
82
82
  codexPatchWrites,
83
83
  proposedSource,
84
84
  emitHostAllow,
85
+ emitHostDeny,
85
86
  formatWriteGateDeny,
87
+ unclassifiedIncludedWriteDeny,
88
+ requiredOwnerWriteDeny,
86
89
  } from './lib/mcp-hook-payload.mjs';
87
90
  import {
88
91
  canonicalizeCandidateChanges,
@@ -257,6 +260,13 @@ function inferLayer(filePath, config, root) {
257
260
  return layerForFile(root, filePath, config.layers);
258
261
  }
259
262
 
263
+ /** Pattern match only — layer.exclude is an intentional ungoverned hole, not a miss. */
264
+ function layerPatternClaimsFile(filePath, config, root) {
265
+ if (!filePath) return false;
266
+ const layers = (config.layers ?? []).map((layer) => ({ ...layer, exclude: [] }));
267
+ return Boolean(layerForFile(root, filePath, layers));
268
+ }
269
+
260
270
  async function loadArk() {
261
271
  const url = new URL('../dist/index.js', import.meta.url);
262
272
  if (!fs.existsSync(url)) {
@@ -472,6 +482,55 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
472
482
  const governedWrites = canonicalSourceWrites.filter((change) =>
473
483
  isCandidateSourceInScope(config, change.path)
474
484
  );
485
+ const layerlessWrites = governedWrites.filter(
486
+ (change) =>
487
+ change.delete !== true &&
488
+ !inferLayer(change.path, config, args.root) &&
489
+ !layerPatternClaimsFile(change.path, config, args.root)
490
+ );
491
+ if (layerlessWrites.length > 0) {
492
+ const first = layerlessWrites[0];
493
+ const deny = unclassifiedIncludedWriteDeny(first.path);
494
+ const message = formatWriteGateDeny({
495
+ file: first.path,
496
+ reason: deny.message,
497
+ ruleId: deny.ruleId,
498
+ nextAction: deny.nextAction,
499
+ });
500
+ emitHostDeny(output, {
501
+ antigravityStyle,
502
+ cursorStyle,
503
+ grokStyle,
504
+ message,
505
+ file: first.path,
506
+ });
507
+ output.status(2);
508
+ return;
509
+ }
510
+ const unownedWrite = governedWrites.find((change) => {
511
+ if (change.delete === true) return false;
512
+ const house = inferLayer(change.path, config, args.root);
513
+ return Boolean(requiredOwnerWriteDeny(config, house, change.path));
514
+ });
515
+ if (unownedWrite) {
516
+ const house = inferLayer(unownedWrite.path, config, args.root);
517
+ const deny = requiredOwnerWriteDeny(config, house, unownedWrite.path);
518
+ const message = formatWriteGateDeny({
519
+ file: unownedWrite.path,
520
+ reason: deny.message,
521
+ ruleId: deny.ruleId,
522
+ nextAction: deny.nextAction,
523
+ });
524
+ emitHostDeny(output, {
525
+ antigravityStyle,
526
+ cursorStyle,
527
+ grokStyle,
528
+ message,
529
+ file: unownedWrite.path,
530
+ });
531
+ output.status(2);
532
+ return;
533
+ }
475
534
  const changes = governedWrites.map(({ path: relativePath, content, delete: deleted }) =>
476
535
  deleted ? { path: relativePath, delete: true } : { path: relativePath, content }
477
536
  );
@@ -608,6 +667,46 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
608
667
  }
609
668
 
610
669
  const layer = inferLayer(filePath, config, args.root);
670
+ if (
671
+ !layer &&
672
+ !layerPatternClaimsFile(filePath, config, args.root) &&
673
+ isCandidateSourceInScope(config, normalizedRel)
674
+ ) {
675
+ const deny = unclassifiedIncludedWriteDeny(normalizedRel);
676
+ const message = formatWriteGateDeny({
677
+ file: normalizedRel,
678
+ reason: deny.message,
679
+ ruleId: deny.ruleId,
680
+ nextAction: deny.nextAction,
681
+ });
682
+ emitHostDeny(output, {
683
+ antigravityStyle,
684
+ cursorStyle,
685
+ grokStyle,
686
+ message,
687
+ file: normalizedRel,
688
+ });
689
+ output.status(2);
690
+ return;
691
+ }
692
+ const ownerDeny = requiredOwnerWriteDeny(config, layer, normalizedRel);
693
+ if (ownerDeny) {
694
+ const message = formatWriteGateDeny({
695
+ file: normalizedRel,
696
+ reason: ownerDeny.message,
697
+ ruleId: ownerDeny.ruleId,
698
+ nextAction: ownerDeny.nextAction,
699
+ });
700
+ emitHostDeny(output, {
701
+ antigravityStyle,
702
+ cursorStyle,
703
+ grokStyle,
704
+ message,
705
+ file: normalizedRel,
706
+ });
707
+ output.status(2);
708
+ return;
709
+ }
611
710
  const validateOnce = (src) =>
612
711
  validateSnippetAnalysis({
613
712
  gate,
@@ -713,6 +812,10 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
713
812
  ruleId: firstDiagnostic?.ruleId,
714
813
  nextAction: firstDiagnostic?.nextAction,
715
814
  extraLines: [
815
+ ...((result.completenessReasons ?? [])
816
+ .filter((reason) => reason.code === 'LEXICAL_EVIDENCE_INCOMPLETE')
817
+ .map((reason) => reason.message)
818
+ .filter(Boolean)),
716
819
  ...adapterResult.diagnostics.slice(1).map(
717
820
  (diagnostic) =>
718
821
  `[${diagnostic.ruleId}] ${diagnostic.message}${diagnostic.location.line ? ` (line ${diagnostic.location.line})` : ''}`
@@ -952,7 +1055,9 @@ function createResidentDoctorSession(args, config, ts) {
952
1055
  config,
953
1056
  manifest: args.projectManifest,
954
1057
  rules,
955
- files: before.files,
1058
+ // Resident doctor is a full-tree snapshot. Do not pass the governed list
1059
+ // as a --changed envelope (#212).
1060
+ files: [],
956
1061
  ts,
957
1062
  args,
958
1063
  });
@@ -1927,6 +2032,8 @@ export async function runArkMcp({ hookInput } = {}) {
1927
2032
  'Returns layer, mayImport / mustNotImport, forbiddenGlobals, and goldenPattern ' +
1928
2033
  '(load-bearing for NEW code when .ark/golden-pattern.json exists — adopt generates it). ' +
1929
2034
  'When the matched layer has layers[].description, the JSON includes description; the field is omitted when absent. ' +
2035
+ 'When the matched layer has layers[].trustBoundary (public|auth|admin|internal), the JSON includes trustBoundary; omitted when absent. ' +
2036
+ 'When the matched layer has layers[].owners, the JSON includes owners; omitted when absent. ' +
1930
2037
  'Call BEFORE writing a new file. ' +
1931
2038
  'Prefer ark_prepare_write when you already have the source snippet (place+validate+autoPatch in one call).',
1932
2039
  inputSchema: {
@@ -1953,7 +2060,9 @@ export async function runArkMcp({ hookInput } = {}) {
1953
2060
  'Also returns the versioned new/worsened designDelta for the proposed full file. ' +
1954
2061
  'Composes ark_place + write-gate — call BEFORE Write/Edit when you have the snippet. ' +
1955
2062
  'When the matched layer has layers[].description, the JSON includes description; the field is omitted when absent. ' +
1956
- 'Returns { filePath, layer, description?, valid, violations?, autoPatch?, judgmentBrief?, contentHash, ... }.',
2063
+ 'When the matched layer has layers[].trustBoundary (public|auth|admin|internal), the JSON includes trustBoundary; omitted when absent. ' +
2064
+ 'When the matched layer has layers[].owners, the JSON includes owners; omitted when absent. ' +
2065
+ 'Returns { filePath, layer, description?, trustBoundary?, owners?, valid, violations?, autoPatch?, judgmentBrief?, contentHash, ... }.',
1957
2066
  inputSchema: {
1958
2067
  type: 'object',
1959
2068
  properties: {