arkgate 2.12.0 → 2.13.0

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 (70) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/README.md +55 -31
  3. package/bin/ark-check.mjs +95 -36
  4. package/bin/ark-mcp.mjs +11 -5
  5. package/bin/ark-shared.mjs +88 -56
  6. package/bin/ark.mjs +45 -10
  7. package/bin/lib/agent-gates.mjs +12 -0
  8. package/bin/lib/architecture-scan.mjs +8 -0
  9. package/bin/lib/ci-and-commands.mjs +9 -3
  10. package/bin/lib/codex-home.mjs +7 -0
  11. package/bin/lib/config-contract.mjs +331 -0
  12. package/bin/lib/doctor-plan.mjs +43 -16
  13. package/bin/lib/enforcement-profiles.mjs +97 -0
  14. package/bin/lib/host-support-matrix.mjs +77 -0
  15. package/bin/lib/install-migrate.mjs +45 -14
  16. package/bin/lib/mcp-adoption.mjs +35 -3
  17. package/bin/lib/open-html.mjs +75 -0
  18. package/bin/lib/presets.mjs +3 -2
  19. package/bin/lib/safety-diagnostics.mjs +31 -11
  20. package/bin/lib/skill-install.mjs +64 -0
  21. package/bin/lib/ts-resolve.mjs +2 -1
  22. package/bin/lib/weakest-link.mjs +417 -0
  23. package/bin/lib/write-path-capabilities.mjs +182 -0
  24. package/bin/lib/write-path-detect.mjs +62 -99
  25. package/dist/configContract-iBLxx5Tz.d.cts +53 -0
  26. package/dist/configContract-iBLxx5Tz.d.ts +53 -0
  27. package/dist/eslint/index.cjs +375 -13
  28. package/dist/eslint/index.cjs.map +1 -1
  29. package/dist/eslint/index.d.cts +30 -20
  30. package/dist/eslint/index.d.ts +30 -20
  31. package/dist/eslint/index.js +375 -13
  32. package/dist/eslint/index.js.map +1 -1
  33. package/dist/index.cjs +723 -61
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +95 -5
  36. package/dist/index.d.ts +95 -5
  37. package/dist/index.js +716 -61
  38. package/dist/index.js.map +1 -1
  39. package/dist/nestjs/index.cjs +150 -42
  40. package/dist/nestjs/index.cjs.map +1 -1
  41. package/dist/nestjs/index.d.cts +2 -1
  42. package/dist/nestjs/index.d.ts +2 -1
  43. package/dist/nestjs/index.js +150 -42
  44. package/dist/nestjs/index.js.map +1 -1
  45. package/dist/runtime/index.cjs +723 -61
  46. package/dist/runtime/index.cjs.map +1 -1
  47. package/dist/runtime/index.d.cts +3 -2
  48. package/dist/runtime/index.d.ts +3 -2
  49. package/dist/runtime/index.js +716 -61
  50. package/dist/runtime/index.js.map +1 -1
  51. package/dist/{types-BZ17b9i5.d.cts → types-BxBwnBpC.d.cts} +9 -36
  52. package/dist/{types-BZ17b9i5.d.ts → types-Wcs_l1_J.d.ts} +9 -36
  53. package/docs/agent-guide.md +32 -20
  54. package/docs/ai-gates.md +53 -18
  55. package/docs/configuration.md +97 -0
  56. package/docs/enthusiast/README.md +3 -3
  57. package/docs/enthusiast/how-to-agent-gates.md +7 -3
  58. package/docs/migrate-from-ark-runtime-kernel.md +3 -0
  59. package/docs/package-surface.md +14 -9
  60. package/docs/production-hardening.md +15 -2
  61. package/docs/threat-model.md +65 -0
  62. package/docs/typescript-support.md +3 -3
  63. package/package.json +15 -2
  64. package/schemas/ark.config.schema.json +750 -0
  65. package/server.json +2 -2
  66. package/templates/hooks/pre-commit-ark +37 -0
  67. package/templates/skills/ark-coverage.md +2 -2
  68. package/templates/skills/ark-runtime.md +8 -5
  69. package/templates/skills/ark-upgrade.md +36 -16
  70. package/tests/fixtures/ts-consumer/ark.config.json +2 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,89 @@ All notable changes to ArkGate (`arkgate`; formerly `ark-runtime-kernel`) are do
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 2.13.0 — 2026-07-11
8
+
9
+ - Added the stable, deterministic analysis IR and public in-memory API: `loadContract`,
10
+ `analyzeProject`, `analyzeChange`, and `explainViolation`.
11
+
12
+ ### Fixed
13
+
14
+ - **Temp-worktree release verification:** Codex multi-project fixtures now keep the simulated
15
+ primary project outside temp-root policy even when the repository itself is checked out under
16
+ `/tmp`, so the release confidence gate is reproducible without weakening fail-closed temp MCP
17
+ rewrites.
18
+ - **Test and Codex-home isolation:** Vitest now redirects `CODEX_HOME` to a disposable test home,
19
+ so direct helper calls and spawned CLIs cannot rewrite the developer's real Codex config. Temp
20
+ project installs also recognize an explicitly exported default `~/.codex` as the real home and
21
+ skip implicit MCP rewiring unless `--codex-home` is requested.
22
+ - **Workflow retry boundary:** `RetryPolicy` now retries only `step.execute` failures and
23
+ timeouts. A snapshot-store or completion-audit failure after a successful effect is terminal,
24
+ enters compensation, and never executes the completed effect again.
25
+ - **Scanner bypass corpus:** forbidden-global checks now use single-file TypeScript symbols, so
26
+ local `fetch` / `Date` bindings do not false-positive while ambient aliases and
27
+ `globalThis.Date.now()` remain violations. CLI, AICodeGate, and ESLint share the verdict.
28
+ TypeScript `import x = require('...')` now creates a dependency edge, and direct
29
+ `require(expr)` emits `DYNAMIC_REQUIRE_NOT_ALLOWLISTED` (strict profiles fail unless the file
30
+ is reviewed in `dynamicImportAllowlist`). The scan cache is versioned past the old semantics.
31
+
32
+ ### Added
33
+
34
+ - **Versioned configuration contract:** `ark.config.json` now carries `$schema` and
35
+ `schemaVersion: "1.0"`. One canonical loader validates and migrates the contract for CLI, MCP,
36
+ and ESLint with path-specific diagnostics and a fail-closed unknown-key policy. The generated
37
+ JSON Schema ships at the stable `arkgate/schema` subpath; unversioned configs from the previous
38
+ major migrate deterministically in memory.
39
+ - **Active-host enforcement capabilities:** doctor and adoption checks now project
40
+ `hard-write`, `advisory-write`, `merge-gate`, and `repair-payload` from the active host only,
41
+ with evidence paths and a separate repo-wide inventory. Claude/Grok hooks can no longer make
42
+ Codex, Cursor, or an unknown host appear hard-enforced; human doctor output names the host and
43
+ separates advisory MCP checks from the shared CI check and its external required-status policy.
44
+ - **Host-compatible enforcement profiles:** generated CI now uses `--strict-merge`, while
45
+ `--strict` remains a compatibility alias; neither depends on an editor hook. The optional
46
+ `--require-write-hook <host>` check verifies Claude/Grok explicitly, reports Cursor/Codex as
47
+ advisory-write plus the shared CI check only, and makes `ark start` reject impossible,
48
+ mismatched, or preserved-incompatible requests before writing project files.
49
+ - **Executable regression confidence gate:** `npm run test:confidence` now combines the existing
50
+ broad Vitest coverage thresholds with real Stryker mutation testing over write-path detection,
51
+ dependency extraction, forbidden-global detection, baseline keys, and workflow retry logic.
52
+ CI and both npm release paths invoke the same gate; mutation score fails below 90%.
53
+ - **Q2 repair dogfood closed:** deny → `ARK_REPAIR_JSON`/`autoPatch` → host re-inject →
54
+ revalidation allow proven via shipped `bin/ark-mcp.mjs` (Claude/Grok hooks already
55
+ `--hook-repair`; `doctor.writePath.mode = repair`).
56
+ - **Q3 weakest-link sensors:** `bin/lib/weakest-link.mjs` + doctor adoption gaps
57
+ (`enforcement-ci-*`, config drift, pre-commit missing); maintained
58
+ `templates/hooks/pre-commit-ark`; optional `ARK_DOCTOR_GITHUB=1` branch-protection
59
+ report (honest unavailable / not-protected — never fake green).
60
+ - **Q5 scale bench:** `scripts/ark-scale-bench.mjs` / `npm run bench:scale` (real
61
+ ark-check cold/warm p50/p95 on generated trees).
62
+ - **Q6 module budgets:** `scripts/check-module-budgets.mjs` / `npm run check:module-budgets`.
63
+ - **Q8 fault-injection tests:** compensation failure audit, cancellation-ignoring timeout,
64
+ outbox retry attempts + clear (durability boundary).
65
+ - **Q9 threat model + package allowlist:** `docs/threat-model.md`,
66
+ `scripts/verify-package-files.mjs` / `npm run check:package-files`.
67
+
68
+ ### Changed
69
+
70
+ - **Product identity retained:** ArkGate, `arkgate`, the `arkgate*` / `ark*` commands,
71
+ `ark.config.json`, `ark://`, `ARK_*`, the existing GitHub repository, and `arkgate.online` remain
72
+ canonical. The unpublished local rename experiment was reversed before any external cutover.
73
+ - **Truthful host support matrix and runtime status:** one capability-backed matrix now drives
74
+ README and generated `AGENTS.md` guarantees for Claude, Grok, Cursor, and Codex. Doctor exposes
75
+ both the supported host profile and repository evidence; public docs distinguish hard local
76
+ hooks, advisory MCP, CI checks, and required-status merge blocking. The optional runtime/Nest
77
+ surface is explicitly experimental and is not required for architecture-gate adoption.
78
+ - **Active host vs deferred Codex on upgrade/doctor:** `/ark-upgrade` greens the
79
+ **session host** first; Codex `$CODEX_HOME` prompts/MCP multi-project debt is
80
+ **deferred** when the session host is **known and not Codex** (Grok/Claude/Cursor).
81
+ Unknown host (CI/plain shell) keeps original severity. Doctor marks deferred gaps
82
+ `deferred: true` (severity `info`), prefixes the message, and omits them from Top
83
+ actions. Temp/upgrade MCP `--root` stays urgent (fail-closed). New helpers:
84
+ `detectActiveAgentHost`, `codexConcernIsActive` (do not treat `CODEX_HOME` alone
85
+ as Codex). `ark-check` advisory for stale Codex-home skills notes the deferral.
86
+ Completion contract adds **Active host** / **Deferred hosts**.
87
+ - **ROADMAP Track Q:** Q2 `done`; Q3/Q5/Q6/Q8/Q9 `doing` with residual external/DoD
88
+ items listed; Q4/Q7/Q10 remain `todo` (no false complete).
89
+
7
90
  ## 2.12.0 — 2026-07-10
8
91
 
9
92
  ### Fixed
package/README.md CHANGED
@@ -43,15 +43,19 @@ commands above).
43
43
 
44
44
  ## What it is (30 seconds)
45
45
 
46
- **ArkGate** = a machine-readable architecture file (`ark.config.json`) enforced in two places
47
- you always care about:
46
+ **ArkGate** = a machine-readable architecture file (`ark.config.json`) enforced at CI, with
47
+ host-specific protection while an agent writes:
48
+
49
+ **Name note:** this is the TypeScript architecture-enforcement package published as `arkgate`.
50
+ It is not affiliated with the separate Archgate CLI project.
48
51
 
49
52
  | When | Tool |
50
53
  |------|------|
51
- | **While the AI writes** | `arkgate-mcp` write gate (blocks bad edits) |
52
- | **Before merge** | `arkgate-check` CI |
54
+ | **While the AI writes** | Hard PreToolUse block on Claude/Grok; advisory MCP on Cursor/Codex |
55
+ | **Before merge** | `arkgate-check` CI check; merge blocking requires it as a required status |
53
56
 
54
- Optional later: runtime kernel (`createArkKernel`) if you want event/intent governance.
57
+ Optional later: the **experimental** runtime kernel (`createArkKernel`) if you want to evaluate
58
+ event/intent governance. It is not required for gate adoption.
55
59
 
56
60
  It is **not** a web framework, ORM, or job runner.
57
61
 
@@ -82,7 +86,7 @@ mode you configure:
82
86
  |-------|--------|-----------|
83
87
  | **Suggest** | New/thin project | Finish `start` + autopilot |
84
88
  | **Adapt** | Not fully protected yet | Keep autopilot / adopt until clean |
85
- | **Enforce** | Gates can honestly protect you | Build features; fix with `/ark-fix` if blocked |
89
+ | **Enforce** | Contract coverage is honest and checked edges are clean | Keep the host-appropriate write path; require the CI status if it must block merges |
86
90
 
87
91
  You **arrive** at Enforce. You never “turn on Enforce”.
88
92
 
@@ -143,7 +147,7 @@ npx arkgate-check --coverage
143
147
 
144
148
  ## Other skills (only when you need them)
145
149
 
146
- Install once: `npx arkgate-check --install-agent-gates`
150
+ Install once: `npx arkgate-check --install-agent-gates`
147
151
  (`--tools claude,cursor,codex,grok` to pick hosts.)
148
152
 
149
153
  **Default is always `/ark-autopilot`.** The rest are escapes, not a second curriculum.
@@ -155,8 +159,9 @@ otherwise they **fall back to sequential**.
155
159
 
156
160
  **Write path (Track W):** Prefer MCP **`ark_prepare_write`** when you have a snippet (place +
157
161
  constrain + validate + optional `autoPatch` + `judgmentBrief`). PreToolUse hooks with
158
- `--hook-repair` emit machine-readable repair payloads on deny (still hard block; never silent
159
- write). See [docs/ai-gates.md](docs/ai-gates.md).
162
+ `--hook-repair` on Claude/Grok emit machine-readable repair payloads on deny (still hard block;
163
+ never silent write). Cursor/Codex MCP calls remain advisory. See
164
+ [docs/ai-gates.md](docs/ai-gates.md).
160
165
 
161
166
  | Need | Skill |
162
167
  |------|--------|
@@ -170,11 +175,24 @@ write). See [docs/ai-gates.md](docs/ai-gates.md).
170
175
  | Edit `ark.config.json` safely | `/ark-contract` |
171
176
  | Plain-language tour of the report | `/ark-explain` |
172
177
  | Deep coverage + opportunities audit | `/ark-coverage` |
173
- | Migrate hand-rolled bus/outbox (TS) | `/ark-runtime` |
174
- | Bump ArkGate + refresh all agent hosts | `/ark-upgrade` |
178
+ | Evaluate the experimental runtime against hand-rolled bus/outbox (TS) | `/ark-runtime` |
179
+ | Bump ArkGate + refresh active host (defer Codex when not on Codex) | `/ark-upgrade` |
180
+
181
+ ### Host enforcement support
182
+
183
+ <!-- arkgate-host-support:start -->
184
+ | Host | Local write boundary | MCP validation | CI / merge path | Repair payload |
185
+ |------|----------------------|----------------|-----------------|----------------|
186
+ | Claude Code | Hard block for PreToolUse `Write` / `Edit` / `MultiEdit` | Advisory; the agent must call it | Available `arkgate-check --strict-merge` check | Emitted on hook deny; host must re-inject |
187
+ | Grok Build | Hard block for PreToolUse `write` / `search_replace` (plus aliases) | Advisory; the agent must call it | Available `arkgate-check --strict-merge` check | Emitted on hook deny; host must re-inject |
188
+ | Cursor | No hard hook; MCP/rules are advisory | Advisory; the agent must call it | Available `arkgate-check --strict-merge` check | No hard-boundary payload |
189
+ | OpenAI Codex | No hard hook; MCP/rules are advisory | Advisory; the agent must call it | Available `arkgate-check --strict-merge` check | No hard-boundary payload |
190
+
191
+ 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 payloads never write code silently: the host must re-inject the candidate and ArkGate revalidates it. Run `arkgate-check --doctor` for the evidence actually detected in the current repository.
192
+ <!-- arkgate-host-support:end -->
193
+
194
+ Detailed setup: [docs/ai-gates.md](docs/ai-gates.md).
175
195
 
176
- Hosts with full MCP/hooks: **Claude Code**, **Cursor**, **Codex**, **Grok Build**.
177
- More: [docs/ai-gates.md](docs/ai-gates.md). Health: **`npx arkgate-check --doctor`**.
178
196
  ---
179
197
 
180
198
  ## How it works (short)
@@ -182,19 +200,24 @@ More: [docs/ai-gates.md](docs/ai-gates.md). Health: **`npx arkgate-check --docto
182
200
  ```
183
201
  ark.config.json
184
202
 
185
- ├─► Write gate (arkgate-mcp) — agent PreToolUse / MCP tools
186
- ├─► CI gate (arkgate-check) PR / main
187
- └─► Runtime kernel (opt-in) — only if you call it
203
+ ├─► Write path (arkgate-mcp) — hard hook or advisory MCP, by host
204
+ ├─► CI check (arkgate-check) merge block only when status is required
205
+ └─► Runtime kernel experimental opt-in; gates do not need it
188
206
  ```
189
207
 
190
208
  - **Presets:** hexagonal, layered, feature-sliced, monorepo, ui-surface, vertical-slice, ddd-bounded-contexts (+ aliases clean-architecture / onion-architecture). Layers start optional; doctor suggests tightening populated cores. Cross-slice / cross-context bans use optional `peerIsolation` rules.
209
+ - **Versioned config:** generated contracts include `$schema` + `schemaVersion`; CLI, MCP, and
210
+ ESLint validate through the same loader. Unknown keys fail with their JSON path. See the
211
+ [configuration and editor guide](docs/configuration.md).
191
212
  - **Frameworks:** Nest / Next / express / library layouts get sensible globs on init so day-one coverage is real.
192
213
  - **Brownfield:** baseline ratchet, refuse to freeze a wrong contract, `/ark-adopt` for mature trees.
193
214
  - **Agents:** skills install into Claude / Cursor / Codex / Grok; `ark start` freezes **day-zero origin** under `.ark/reports/` **before** agent docs/CI templates.
194
215
  - **Write protocol (2.10 / Track W):** mechanical-safe **autoPatch** on the write gate (`import type`); MCP **`ark_prepare_write`** (place + validate + patch + judgmentBrief); opt-in hook **`--hook-repair`** (`ARK_REPAIR_JSON`); doctor **`writePath`** (repair vs reject-only); loop-cost eval (`npm run eval:loop-cost`). Port-proof inject is **judgment** (arity change), not silent auto-apply.
195
- - **Fail-closed CI (2.11):** `--strict` combines config coverage, required CI/MCP/write gates,
196
- PreToolUse hook presence, and bypass diagnostics for dynamic imports, TypeScript suppressions,
197
- explicit `any` casts, InMemory runtime defaults, and disabled peer isolation.
216
+ - **Fail-closed CI (2.11):** `--strict-merge` combines config coverage, shared gate-file
217
+ presence, and bypass diagnostics for dynamic imports, TypeScript suppressions, explicit `any`
218
+ casts, InMemory runtime defaults, and disabled peer isolation. `--strict` is a compatibility
219
+ alias. Neither requires an editor hook; use `--require-write-hook claude|grok` when that local
220
+ guarantee is part of the check.
198
221
  - **Trust / coverage (2.12):** package unit-test floors on the broad product surface
199
222
  (statements/lines **≥80%**, branches/functions **≥85%**; enforcement-critical modules **≥95%**
200
223
  branch). Explore dual-plan + day-zero origin first (see above). Roadmap next: Q2 repair dogfood matrix.
@@ -205,7 +228,7 @@ ark.config.json
205
228
  | | ArkGate | Typical boundary linter |
206
229
  |--|:---:|:---:|
207
230
  | CI import rules | ✅ | ✅ |
208
- | Block **AI writes** before they land | ✅ | ❌ |
231
+ | Hard-block supported-host AI writes before they land | ✅ (Claude/Grok hooks) | ❌ |
209
232
  | Contract agents can read (`ark://manifest`) | ✅ | ❌ |
210
233
  | Placement tools (`ark_place`, …) | ✅ | ❌ |
211
234
  | Honest governed % + adoption path | ✅ | ❌ |
@@ -225,7 +248,7 @@ npx arkgate-check --doctor --json # machine-readable doctor.adoption
225
248
  npx arkgate-check --strict # fail-closed CI + installed-gate/safety checks
226
249
  npx arkgate-check --plan # safe-to-auto-fix vs your call
227
250
  npx arkgate-check --coverage # Governed: N%
228
- npx arkgate-check --report ark-report.html # showcase HTML + Adoption card + origin/latest
251
+ npx arkgate-check --report ark-report.html # showcase HTML (opens in browser on local TTY; --no-open to skip)
229
252
  npx arkgate-check --baseline # only NEW violations fail
230
253
  npx arkgate upgrade # package + gates/skills + MCP/Codex normalize
231
254
  ```
@@ -239,10 +262,11 @@ CI (example):
239
262
 
240
263
  ---
241
264
 
242
- ## Optional: runtime kernel
265
+ ## Optional experimental runtime kernel
243
266
 
244
- Gates need **no app code changes**. If you also want runtime intent/event contracts,
245
- use the **opt-in** subpath (preferred):
267
+ Gates need **no app code changes**. The runtime API is currently **experimental** and is not a
268
+ production-readiness claim. If you want to evaluate runtime intent/event contracts, use the
269
+ opt-in subpath (preferred):
246
270
 
247
271
  ```ts
248
272
  import { createStrictArkKernelFromConfig } from 'arkgate/runtime';
@@ -250,7 +274,7 @@ import { createStrictArkKernelFromConfig } from 'arkgate/runtime';
250
274
  ```
251
275
 
252
276
  Root `import { … } from 'arkgate'` still re-exports kernel symbols for compatibility
253
- in this major; prefer `arkgate/runtime` for new code.
277
+ in this major; use `arkgate/runtime` when evaluating the experimental surface.
254
278
 
255
279
  NestJS: `arkgate/nestjs` (optional peer `@nestjs/common`).
256
280
 
@@ -269,7 +293,7 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
269
293
  | Audience | Link |
270
294
  |----------|------|
271
295
  | New builders (plain language) | [docs/enthusiast/](docs/enthusiast/README.md) |
272
- | **Package surface (stable vs opt-in)** | [docs/package-surface.md](docs/package-surface.md) |
296
+ | **Package surface (stable vs experimental)** | [docs/package-surface.md](docs/package-surface.md) |
273
297
  | Wire Claude / Cursor / Codex / Grok + **ESLint (CI-parity)** | [docs/ai-gates.md](docs/ai-gates.md) |
274
298
  | **TypeScript 5 / 6 / 7 support** | [docs/typescript-support.md](docs/typescript-support.md) |
275
299
  | Migrate from `ark-runtime-kernel` | [docs/migrate-from-ark-runtime-kernel.md](docs/migrate-from-ark-runtime-kernel.md) |
@@ -290,11 +314,11 @@ npm run typecheck
290
314
  npm run check:architecture # Ark gates itself
291
315
  ```
292
316
 
293
- **Website:** [arkgate.online](https://www.arkgate.online/)
294
- **npm:** [`arkgate`](https://www.npmjs.com/package/arkgate) · formerly `ark-runtime-kernel`
295
- **Product:** **ArkGate** — architecture co-pilot / gate for AI TypeScript (not a runtime kernel).
296
- CLI: `arkgate` · `arkgate-check` · `arkgate-mcp` (aliases `ark` / `ark-check` / `ark-mcp` still work for one major).
297
- MCP registry: `io.github.pedroknigge/arkgate`.
317
+ **Website:** [arkgate.online](https://www.arkgate.online/)
318
+ **npm:** [`arkgate`](https://www.npmjs.com/package/arkgate) · formerly `ark-runtime-kernel`
319
+ **Product:** **ArkGate** — architecture co-pilot / gate for AI TypeScript (not a runtime kernel).
320
+ CLI: `arkgate` · `arkgate-check` · `arkgate-mcp` (aliases `ark` / `ark-check` / `ark-mcp` still work for one major).
321
+ MCP registry: `io.github.pedroknigge/arkgate`.
298
322
  **Source:** [github.com/pedroknigge/arkgate](https://github.com/pedroknigge/arkgate)
299
323
 
300
324
  Node ≥ 18 · **MIT**.
package/bin/ark-check.mjs CHANGED
@@ -33,6 +33,7 @@ import {
33
33
  loadTypeScript,
34
34
  detectSkillGaps,
35
35
  detectCodexHomeGap,
36
+ detectActiveAgentHost,
36
37
  missingGates,
37
38
  staleRunnerGateFiles,
38
39
  brokenMcpGateFiles,
@@ -56,6 +57,7 @@ import {
56
57
  reportsDir,
57
58
  readJsonSafe,
58
59
  } from './lib/html-report.mjs';
60
+ import { shouldOpenHtmlReport, openHtmlInBrowser } from './lib/open-html.mjs';
59
61
  import {
60
62
  computeCoverage,
61
63
  runCoverage,
@@ -81,6 +83,7 @@ import {
81
83
  import {
82
84
  ARCHITECTURE_PRESETS,
83
85
  } from './lib/presets.mjs';
86
+ import { loadArkConfigContract, parseArkConfigJson } from './lib/config-contract.mjs';
84
87
 
85
88
  import {
86
89
  collectGovernedFiles,
@@ -91,6 +94,7 @@ import {
91
94
  configWarning,
92
95
  } from './lib/config-warnings.mjs';
93
96
  import { runArchitectureScan } from './lib/architecture-scan.mjs';
97
+ import { validateHardWriteRequest } from './lib/enforcement-profiles.mjs';
94
98
 
95
99
 
96
100
  function parseArgs(argv) {
@@ -101,9 +105,9 @@ function parseArgs(argv) {
101
105
  printConfig: undefined,
102
106
  tsconfig: undefined,
103
107
  json: false,
104
- strict: false,
105
108
  strictConfig: false,
106
109
  requireGates: false,
110
+ requireWriteHook: undefined,
107
111
  init: false,
108
112
  installAgentGates: false,
109
113
  tools: undefined,
@@ -122,6 +126,8 @@ function parseArgs(argv) {
122
126
  applyPolicyPack: undefined,
123
127
  watch: false,
124
128
  beginner: false,
129
+ openReport: false,
130
+ noOpenReport: false,
125
131
  version: false,
126
132
  help: false,
127
133
  };
@@ -135,13 +141,15 @@ function parseArgs(argv) {
135
141
  for (let i = 2; i < argv.length; i += 1) {
136
142
  const arg = argv[i];
137
143
  if (arg === '--json') args.json = true;
138
- else if (arg === '--strict') {
139
- args.strict = true;
144
+ else if (arg === '--strict' || arg === '--strict-merge') {
140
145
  args.strictConfig = true;
141
146
  args.requireGates = true;
142
147
  }
143
148
  else if (arg === '--strict-config') args.strictConfig = true;
144
149
  else if (arg === '--require-gates') args.requireGates = true;
150
+ else if (arg === '--require-write-hook') {
151
+ args.requireWriteHook = requireValue(arg, i++).trim().toLowerCase();
152
+ }
145
153
  else if (arg === '--init') args.init = true;
146
154
  else if (arg === '--preset') args.preset = requireValue(arg, i++);
147
155
  else if (arg === '--install-agent-gates') args.installAgentGates = true;
@@ -183,6 +191,8 @@ function parseArgs(argv) {
183
191
  }
184
192
  else if (arg === '--reset-origin') args.resetOrigin = true;
185
193
  else if (arg === '--no-archive') args.noArchive = true;
194
+ else if (arg === '--open') args.openReport = true;
195
+ else if (arg === '--no-open') args.noOpenReport = true;
186
196
  else if (arg === '--baseline' || arg === '--update-baseline') {
187
197
  if (arg === '--update-baseline') args.updateBaseline = true;
188
198
  // optional path value: consume the next arg only when it isn't another flag
@@ -214,7 +224,7 @@ function usage() {
214
224
  return [
215
225
  'Usage: arkgate-check | ark-check (identical bins; product name ArkGate)',
216
226
  ' ark-check --version',
217
- ' ark-check --root <project> --config <ark.config.json> [--manifest <ark.manifest.json>] [--tsconfig <tsconfig.json>] [--strict | --strict-config] [--require-gates] [--json] [--baseline [file]] [--report [file.html]] [--no-cache]',
227
+ ' ark-check --root <project> --config <ark.config.json> [--manifest <ark.manifest.json>] [--tsconfig <tsconfig.json>] [--strict-merge | --strict | --strict-config] [--require-gates] [--require-write-hook <host>] [--json] [--baseline [file]] [--report [file.html]] [--no-cache]',
218
228
  ' ark-check --coverage [--json] per-layer file counts + full unclassified list (report only, exit 0)',
219
229
  ' ark-check --plan [--json] classified remediation plan (mechanical-safe / judgment / deferred) + goal; report only',
220
230
  ' ark-check --recommend [--json] [--write-plan] application-shape plan; --write-plan emits ark-adoption-plan.json',
@@ -224,10 +234,11 @@ function usage() {
224
234
  ' ark-check --adopt-contract [--write] expand include + UI patterns from ungoverned dirs (contract adopt)',
225
235
  ' ark-check --ratchet-cores when raw graph is green (0 violations; baseline ignored), set optional:false on populated cores only (writes ark.config.json)',
226
236
  ' ark-check --watch re-run the check when governed files change (debounced)',
227
- ' ark-check --report [file.html] [--beginner] [--reset-origin] [--no-archive]',
237
+ ' ark-check --report [file.html] [--beginner] [--reset-origin] [--no-archive] [--open|--no-open]',
228
238
  ' HTML report + snapshots under .ark/reports/ (origin once, latest each run, history JSON)',
239
+ ' Best-effort open in browser (local TTY). No-op if open fails. --no-open / ARK_NO_OPEN_REPORT=1 to skip; --open forces open.',
229
240
  ' ark-check --init [--preset hexagonal|layered|feature-sliced|monorepo|ui-surface|vertical-slice|ddd-bounded-contexts|clean-architecture|onion-architecture] [--force]',
230
- ' ark-check --install-agent-gates [--tools claude,cursor,codex,grok] [--skills-only] [--codex-home] [--force]',
241
+ ' ark-check --install-agent-gates [--tools claude,cursor,codex,grok] [--require-write-hook <host>] [--skills-only] [--codex-home] [--force]',
231
242
  ' ark-check --update-baseline [file] freeze current violations (default .ark-baseline.json)',
232
243
  ' ark-check --print-config eleven-layer',
233
244
  '',
@@ -267,8 +278,12 @@ function usage() {
267
278
  '',
268
279
  'Config warnings are advisory by default and are included in JSON output.',
269
280
  'Use --strict-config to make config warnings fail the check.',
270
- 'Use --strict for the fail-closed CI profile: --strict-config + --require-gates',
271
- 'plus the security diagnostics surfaced by doctor.',
281
+ 'Use --strict-merge for the fail-closed CI profile: --strict-config + --require-gates',
282
+ 'plus the security diagnostics surfaced by doctor. --strict is a compatibility alias.',
283
+ 'This merge profile never depends on an editor/agent hook.',
284
+ 'Add --require-write-hook claude|grok to validate a hard local write boundary for that',
285
+ 'specific host. Cursor and Codex expose advisory MCP tools plus the shared CI check;',
286
+ 'merge blocking requires repository policy to make that status required.',
272
287
  '',
273
288
  '--require-gates fails the check when AGENTS.md, .mcp.json, or the generated CI',
274
289
  'workflow is missing, so "installed but never configured" is a red CI. Combine it',
@@ -276,7 +291,8 @@ function usage() {
276
291
  '',
277
292
  '--install-agent-gates writes AGENTS.md, .mcp.json, and the CI workflow for every',
278
293
  'project, plus tool-specific templates. Known tools: claude, cursor, codex, grok',
279
- '(full MCP/hook gates) and windsurf, cline, copilot, kiro, roo, continue, gemini',
294
+ '(Claude/Grok hard-write hooks; Cursor/Codex advisory MCP; shared CI check for all) and',
295
+ 'windsurf, cline, copilot, kiro, roo, continue, gemini',
280
296
  '(instruction-tier rule files derived from the same contract).',
281
297
  'It also installs the /ark-* skills shipped in templates/skills/ into each',
282
298
  'detected tool\'s command location (.claude/skills/, .cursor/commands/,',
@@ -290,7 +306,7 @@ function usage() {
290
306
  'Pass --tools to pick which tool configs to write; otherwise they are auto-detected',
291
307
  'from their config directories (.claude/, .cursor/, .codex/, .grok/, .windsurf/,',
292
308
  '.clinerules/, .kiro/, .roo/, .continue/, .gemini/; copilot is explicit-only).',
293
- 'claude+cursor+codex are written when nothing is detected.',
309
+ 'claude+cursor+codex+grok are written when nothing is detected.',
294
310
  '',
295
311
  'Generate a starter 11-layer config:',
296
312
  ' ark-check --print-config eleven-layer > ark.config.json',
@@ -305,25 +321,12 @@ function readConfig(root, configPath) {
305
321
  ? configPath
306
322
  : path.join(root, configPath);
307
323
  if (!fs.existsSync(fullPath)) {
308
- return {
309
- include: ['src'],
310
- layers: [],
311
- rules: DEFAULT_RULES,
312
- };
324
+ return loadArkConfigContract(
325
+ { include: ['src'], layers: [], rules: DEFAULT_RULES },
326
+ fullPath
327
+ ).config;
313
328
  }
314
- const raw = JSON.parse(fs.readFileSync(fullPath, 'utf8'));
315
- return {
316
- include: raw.include ?? ['src'],
317
- layers: raw.layers ?? [],
318
- rules: raw.rules ?? DEFAULT_RULES,
319
- ...(raw.exclude ? { exclude: raw.exclude } : {}),
320
- ...(raw.excludeGenerated !== undefined ? { excludeGenerated: raw.excludeGenerated } : {}),
321
- ...(raw.cyclePolicy ? { cyclePolicy: raw.cyclePolicy } : {}),
322
- ...(raw.dynamicImportAllowlist
323
- ? { dynamicImportAllowlist: raw.dynamicImportAllowlist }
324
- : {}),
325
- ...(raw.safety ? { safety: raw.safety } : {}),
326
- };
329
+ return parseArkConfigJson(fs.readFileSync(fullPath, 'utf8'), fullPath).config;
327
330
  }
328
331
 
329
332
  /**
@@ -976,16 +979,41 @@ async function main() {
976
979
  return;
977
980
  }
978
981
 
979
- if (args.requireGates) {
980
- const missing = missingGates(args.root);
981
- if (args.strict && !detectWritePathCapabilities(args.root).hookPresent) {
982
- missing.push('PreToolUse write hook');
982
+ if (args.requireGates || args.requireWriteHook) {
983
+ let writeRequest = null;
984
+ if (args.requireWriteHook) {
985
+ writeRequest = validateHardWriteRequest({
986
+ root: args.root,
987
+ host: args.requireWriteHook,
988
+ tools: [args.requireWriteHook],
989
+ force: true,
990
+ });
991
+ if (!writeRequest.ok) {
992
+ const payload = {
993
+ ok: false,
994
+ error: 'unsupported-enforcement-profile',
995
+ message: writeRequest.error,
996
+ };
997
+ if (args.json) console.log(JSON.stringify(payload, null, 2));
998
+ else console.error(writeRequest.error);
999
+ process.exitCode = 2;
1000
+ return;
1001
+ }
1002
+ }
1003
+
1004
+ const missing = args.requireGates ? missingGates(args.root) : [];
1005
+ if (
1006
+ writeRequest?.host &&
1007
+ !detectWritePathCapabilities(args.root, writeRequest.host).capabilities['hard-write']
1008
+ ) {
1009
+ missing.push(`${writeRequest.host} hard-write hook`);
983
1010
  }
984
1011
  if (missing.length > 0) {
985
1012
  const payload = {
986
1013
  ok: false,
987
1014
  error: 'missing-gates',
988
1015
  missing,
1016
+ ...(writeRequest?.host ? { writeHost: writeRequest.host } : {}),
989
1017
  };
990
1018
  if (args.json) {
991
1019
  console.log(JSON.stringify(payload, null, 2));
@@ -994,7 +1022,12 @@ async function main() {
994
1022
  for (const relativePath of missing) {
995
1023
  console.error(` - ${relativePath}`);
996
1024
  }
997
- console.error(`\nRun \`${arkCommand(args.root, 'ark', 'init')}\` (or \`ark-check --install-agent-gates\`) to configure enforcement.`);
1025
+ const installArgs = writeRequest?.host
1026
+ ? `--install-agent-gates --tools ${writeRequest.host} --require-write-hook ${writeRequest.host}`
1027
+ : '--install-agent-gates';
1028
+ console.error(
1029
+ `\nRun \`${arkCommand(args.root, 'ark', 'init')}\` (or \`${arkCommand(args.root, 'ark-check', installArgs)}\`) to configure enforcement.`
1030
+ );
998
1031
  }
999
1032
  process.exitCode = 1;
1000
1033
  return;
@@ -1004,7 +1037,12 @@ async function main() {
1004
1037
  // When --require-gates is the only intent (no config/architecture run needed),
1005
1038
  // callers still get a clear signal from the exit code and the human-mode line.
1006
1039
  if (!args.json) {
1007
- console.log('Ark gates present: ' + REQUIRED_GATE_FILES.join(', '));
1040
+ if (args.requireGates) {
1041
+ console.log('Ark gates present (merge profile): ' + REQUIRED_GATE_FILES.join(', '));
1042
+ }
1043
+ if (writeRequest?.host) {
1044
+ console.log(`Ark hard-write hook present for ${writeRequest.host}.`);
1045
+ }
1008
1046
  }
1009
1047
  }
1010
1048
 
@@ -1277,6 +1315,18 @@ async function main() {
1277
1315
  }
1278
1316
  }
1279
1317
  }
1318
+
1319
+ // Best-effort: open the report in the default browser. If it opens, fine;
1320
+ // if not (headless, no GUI, spawn error), do nothing — never fail the check.
1321
+ // Skipped in CI / Vitest / ARK_NO_OPEN_REPORT; --open / --no-open override.
1322
+ if (
1323
+ shouldOpenHtmlReport({
1324
+ force: Boolean(args.openReport),
1325
+ noOpen: Boolean(args.noOpenReport) || Boolean(args.json),
1326
+ })
1327
+ ) {
1328
+ openHtmlInBrowser(reportPath);
1329
+ }
1280
1330
  }
1281
1331
 
1282
1332
  if (args.json) {
@@ -1385,10 +1435,19 @@ async function main() {
1385
1435
  const parts = [];
1386
1436
  if (codexHomeGap.missing > 0) parts.push(`${codexHomeGap.missing} missing`);
1387
1437
  if (codexHomeGap.stale > 0) parts.push(`${codexHomeGap.stale} outdated`);
1438
+ // Advisory always; when session host is known and not Codex, say so so
1439
+ // /ark-upgrade does not chase home prompts as Incomplete.
1440
+ const activeHost = detectActiveAgentHost();
1441
+ const deferredNote =
1442
+ activeHost != null && activeHost !== 'codex'
1443
+ ? ' Deferred unless you use Codex — not a blocker for Grok/Claude/Cursor. '
1444
+ : ' ';
1388
1445
  console.log(
1389
1446
  color.dim(
1390
- `/ark-* skills in ${codexPromptsDir()} are behind this Ark (${parts.join(', ')}). ` +
1391
- `Codex loads them from there, not the repo. Refresh: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --codex-home --force')}`
1447
+ `/ark-* skills in ${codexPromptsDir()} are behind this Ark (${parts.join(', ')}).` +
1448
+ deferredNote +
1449
+ `Codex loads them from $CODEX_HOME/prompts, not the repo. ` +
1450
+ `When using Codex: ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --codex-home --force')}`
1392
1451
  )
1393
1452
  );
1394
1453
  }
package/bin/ark-mcp.mjs CHANGED
@@ -58,6 +58,7 @@ import {
58
58
  import { createImportTargetResolver } from './lib/import-resolve.mjs';
59
59
  import { validateWithAutoPatch, resolveImportFileAbs } from './lib/auto-patch.mjs';
60
60
  import { composePrepareWrite } from './lib/prepare-write.mjs';
61
+ import { loadArkConfigContract } from './lib/config-contract.mjs';
61
62
 
62
63
  const arkCheckBin = fileURLToPath(new URL('./ark-check.mjs', import.meta.url));
63
64
 
@@ -120,6 +121,11 @@ function readJson(file, { required } = {}) {
120
121
  }
121
122
  }
122
123
 
124
+ function readArkConfig(file, { required } = {}) {
125
+ const raw = readJson(file, { required });
126
+ return raw === undefined ? undefined : loadArkConfigContract(raw, file).config;
127
+ }
128
+
123
129
  function resolveInRoot(root, maybePath) {
124
130
  if (!maybePath) return undefined;
125
131
  return path.isAbsolute(maybePath) ? maybePath : path.join(root, maybePath);
@@ -460,11 +466,11 @@ async function main() {
460
466
  const ts = await loadOptionalTypeScript();
461
467
 
462
468
  const config =
463
- (configPath ? readJson(configPath, { required: args.configExplicit }) : undefined) ?? {
464
- include: ['src'],
465
- layers: [],
466
- rules: [],
467
- };
469
+ (configPath ? readArkConfig(configPath, { required: args.configExplicit }) : undefined) ??
470
+ loadArkConfigContract(
471
+ { include: ['src'], layers: [], rules: DEFAULT_RULES },
472
+ configPath ?? 'ark.config.json'
473
+ ).config;
468
474
  if (!config.layers || config.layers.length === 0) {
469
475
  process.stderr.write(
470
476
  '[ark-mcp] warning: no layers configured — file→layer inference from config patterns ' +