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.
- package/CHANGELOG.md +133 -0
- package/README.md +13 -5
- package/bin/ark-check-runtime.mjs +17 -49
- package/bin/ark-mcp-runtime.mjs +111 -2
- package/bin/ark-shared.mjs +140 -11
- package/bin/ark.mjs +7 -25
- package/bin/lib/adr-presence.mjs +97 -0
- package/bin/lib/agent-skills-package.mjs +179 -1
- package/bin/lib/analysis-completeness.mjs +38 -2
- package/bin/lib/analysis-engine.mjs +6 -6
- package/bin/lib/architecture-scan.mjs +26 -4
- package/bin/lib/ark-run-doctor.mjs +6 -0
- package/bin/lib/ark-run-report.mjs +6 -2
- package/bin/lib/arkrules-sensors.mjs +81 -0
- package/bin/lib/baseline-key.mjs +4 -1
- package/bin/lib/check-args.mjs +52 -2
- package/bin/lib/config-contract.mjs +64 -1
- package/bin/lib/diagnostic-catalog.mjs +3 -1
- package/bin/lib/doctor-advisories.mjs +94 -10
- package/bin/lib/doctor-human.mjs +18 -5
- package/bin/lib/doctor-next-actions.mjs +14 -0
- package/bin/lib/doctor-plan.mjs +32 -2
- package/bin/lib/enforcement-honesty.mjs +47 -2
- package/bin/lib/first-run-help.mjs +8 -1
- package/bin/lib/host-support-matrix.mjs +9 -1
- package/bin/lib/html-report-depth.mjs +2 -0
- package/bin/lib/html-report.mjs +13 -2
- package/bin/lib/import-resolve.mjs +74 -13
- package/bin/lib/improvement-compass-doctor.mjs +6 -1
- package/bin/lib/improvement-compass-map.mjs +3 -1
- package/bin/lib/layer-description.mjs +90 -8
- package/bin/lib/mcp-hook-payload.mjs +56 -0
- package/bin/lib/no-domain-frontend.mjs +91 -0
- package/bin/lib/presets.mjs +3 -4
- package/bin/lib/product-copy.mjs +15 -0
- package/bin/lib/projected-governed-coverage.mjs +114 -0
- package/bin/lib/recommend-cli.mjs +54 -0
- package/bin/lib/remediation.mjs +4 -0
- package/bin/lib/resolved-candidate-facts.mjs +83 -66
- package/bin/lib/rules-under-contract.mjs +117 -2
- package/bin/lib/snippet-analysis.mjs +14 -8
- package/bin/lib/start-preview.mjs +5 -2
- package/bin/lib/states-transitions-presence.mjs +212 -0
- package/bin/lib/status-command.mjs +2 -0
- package/bin/lib/status-transition-catalog.mjs +410 -0
- package/bin/lib/team-parliament-io.mjs +10 -0
- package/bin/lib/violations.mjs +13 -1
- package/bin/lib/write-path-capabilities.mjs +20 -5
- package/bin/lib/write-path-detect.mjs +27 -2
- package/dist/{configTypes-j7so8B4O.d.ts → configTypes-Dt3DpVbd.d.ts} +19 -0
- package/dist/{diagnosticCatalog-DVx_2RmF.d.ts → diagnosticCatalog-BEg85XlE.d.ts} +3 -3
- package/dist/eslint/index.cjs +4 -4
- package/dist/eslint/index.d.ts +1 -1
- package/dist/eslint/index.js +4 -4
- package/dist/index.cjs +31 -31
- package/dist/index.d.ts +70 -9
- package/dist/index.js +31 -31
- package/dist/nestjs/index.cjs +5 -5
- package/dist/nestjs/index.d.ts +3 -3
- package/dist/nestjs/index.js +5 -5
- package/dist/runtime/index.cjs +15 -15
- package/dist/runtime/index.d.ts +6 -6
- package/dist/runtime/index.js +15 -15
- package/dist/{types-Djbs3KjE.d.ts → types-CN9tVMPz.d.ts} +3 -1
- package/dist/{types-tGhZUiGX.d.ts → types-TBiv0WHL.d.ts} +1 -1
- package/docs/README.md +1 -1
- package/docs/agent-guide.md +6 -4
- package/docs/ai-gates.md +10 -3
- package/docs/brownfield-adoption.md +4 -1
- package/docs/configuration.md +55 -7
- package/docs/develop.md +1 -0
- package/docs/diagnostics.md +23 -3
- package/docs/enthusiast/how-to-agent-gates.md +5 -0
- package/docs/enthusiast/how-to-pick-shape.md +1 -1
- package/docs/package-surface.md +7 -5
- package/docs/use.md +5 -0
- package/package.json +1 -1
- package/schemas/ark.config.schema.json +23 -2
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-adopt/SKILL.md +82 -1
- package/templates/agent-skills/ark-autopilot/SKILL.md +6 -2
- package/templates/agent-skills/ark-coverage/SKILL.md +1 -1
- package/templates/agent-skills/ark-explain/SKILL.md +4 -2
- package/templates/agent-skills/ark-explore/SKILL.md +14 -1
- package/templates/agent-skills/ark-place/SKILL.md +38 -3
- package/templates/agent-skills/ark-runtime/SKILL.md +4 -2
- package/templates/agent-skills/ark-upgrade/SKILL.md +1 -1
- package/templates/arkrules/DomainModel.json +14 -1
- package/templates/skills/ark-adopt.md +82 -1
- package/templates/skills/ark-autopilot.md +6 -2
- package/templates/skills/ark-coverage.md +1 -1
- package/templates/skills/ark-explain.md +4 -2
- package/templates/skills/ark-explore.md +14 -1
- package/templates/skills/ark-place.md +38 -3
- package/templates/skills/ark-runtime.md +4 -2
- 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.
|
|
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.
|
|
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 --
|
|
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
|
|
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.
|
|
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
|
-
|
|
1157
|
-
|
|
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
|
|
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,
|
package/bin/ark-mcp-runtime.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
'
|
|
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: {
|