arkgate 2.13.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +53 -36
  3. package/bin/ark-check.mjs +72 -6
  4. package/bin/ark-mcp.mjs +108 -1
  5. package/bin/ark-shared.mjs +204 -149
  6. package/bin/ark.mjs +90 -25
  7. package/bin/lib/adapter-contract.mjs +93 -0
  8. package/bin/lib/agent-gates.mjs +1 -0
  9. package/bin/lib/analysis-engine.mjs +1171 -0
  10. package/bin/lib/architecture-scan.mjs +84 -135
  11. package/bin/lib/ci-and-commands.mjs +51 -15
  12. package/bin/lib/config-warnings.mjs +7 -205
  13. package/bin/lib/design-smells.mjs +434 -0
  14. package/bin/lib/doctor-plan.mjs +149 -16
  15. package/bin/lib/field-install.mjs +67 -10
  16. package/bin/lib/gate-files.mjs +42 -3
  17. package/bin/lib/graph-cycles.mjs +4 -54
  18. package/bin/lib/hook-templates.mjs +33 -1
  19. package/bin/lib/host-support-matrix.mjs +7 -1
  20. package/bin/lib/install-migrate.mjs +54 -16
  21. package/bin/lib/presets.mjs +42 -2
  22. package/bin/lib/safety-diagnostics.mjs +18 -17
  23. package/bin/lib/scan-files.mjs +12 -1
  24. package/bin/lib/skill-install.mjs +8 -1
  25. package/bin/lib/source-policy.mjs +36 -0
  26. package/bin/lib/start-preview.mjs +271 -0
  27. package/bin/lib/ts-resolve.mjs +11 -2
  28. package/bin/lib/write-path-capabilities.mjs +4 -0
  29. package/compat/nestjs.cjs +2 -0
  30. package/compat/nestjs.d.ts +2 -0
  31. package/compat/nestjs.js +1 -0
  32. package/compat/runtime.cjs +2 -0
  33. package/compat/runtime.d.ts +2 -0
  34. package/compat/runtime.js +1 -0
  35. package/dist/configContract-BxSIwVRo.d.cts +259 -0
  36. package/dist/configContract-BxSIwVRo.d.ts +259 -0
  37. package/dist/eslint/index.cjs +125 -48
  38. package/dist/eslint/index.d.cts +7 -1
  39. package/dist/eslint/index.d.ts +7 -1
  40. package/dist/eslint/index.js +125 -48
  41. package/dist/index.cjs +1248 -3302
  42. package/dist/index.d.cts +359 -483
  43. package/dist/index.d.ts +359 -483
  44. package/dist/index.js +1231 -3248
  45. package/docs/agent-guide.md +34 -16
  46. package/docs/ai-gates.md +30 -7
  47. package/docs/brownfield-adoption.md +52 -1
  48. package/docs/migrate-from-ark-runtime-kernel.md +2 -3
  49. package/docs/package-surface.md +10 -13
  50. package/docs/production-hardening.md +17 -4
  51. package/docs/typescript-support.md +27 -0
  52. package/package.json +33 -11
  53. package/schemas/ark.analysis-result.schema.json +91 -0
  54. package/server.json +2 -2
  55. package/templates/skills/ark-adopt.md +26 -3
  56. package/templates/skills/ark-architect.md +10 -2
  57. package/templates/skills/ark-autopilot.md +37 -20
  58. package/templates/skills/ark-contract.md +7 -0
  59. package/templates/skills/ark-coverage.md +44 -45
  60. package/templates/skills/ark-explain.md +8 -0
  61. package/templates/skills/ark-explore.md +117 -47
  62. package/templates/skills/ark-fix.md +22 -0
  63. package/templates/skills/ark-loop.md +15 -1
  64. package/templates/skills/ark-place.md +7 -0
  65. package/templates/skills/ark-think.md +24 -20
  66. package/dist/configContract-iBLxx5Tz.d.cts +0 -53
  67. package/dist/configContract-iBLxx5Tz.d.ts +0 -53
  68. package/dist/eslint/index.cjs.map +0 -1
  69. package/dist/eslint/index.js.map +0 -1
  70. package/dist/index.cjs.map +0 -1
  71. package/dist/index.js.map +0 -1
  72. package/dist/nestjs/index.cjs +0 -2606
  73. package/dist/nestjs/index.cjs.map +0 -1
  74. package/dist/nestjs/index.d.cts +0 -23
  75. package/dist/nestjs/index.d.ts +0 -23
  76. package/dist/nestjs/index.js +0 -2582
  77. package/dist/nestjs/index.js.map +0 -1
  78. package/dist/runtime/index.cjs +0 -4014
  79. package/dist/runtime/index.cjs.map +0 -1
  80. package/dist/runtime/index.d.cts +0 -3
  81. package/dist/runtime/index.d.ts +0 -3
  82. package/dist/runtime/index.js +0 -3925
  83. package/dist/runtime/index.js.map +0 -1
  84. package/dist/types-BxBwnBpC.d.cts +0 -1041
  85. package/dist/types-Wcs_l1_J.d.ts +0 -1041
package/CHANGELOG.md CHANGED
@@ -4,6 +4,65 @@ All notable changes to ArkGate (`arkgate`; formerly `ark-runtime-kernel`) are do
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ No changes are scheduled after 3.0.1.
8
+
9
+ ## 3.0.1 — 2026-07-13
10
+
11
+ ### Added
12
+
13
+ - **Design fitness (doctor):** deterministic `designSmells` and `designFitness` on
14
+ `ark-check --doctor --json` / human doctor. Edge-clean ENFORCE can report
15
+ **ENFORCE · design-weak** when lived design residual remains (e.g. facade SQL in routes,
16
+ handlers in persistence, god modules, domain logic in UI, soft contract, mixed patterns).
17
+ - **Plan pattern B:** `ark-check --plan --json` includes `patternBets[]` with pilot, success
18
+ signal, kill-switch, and `neverMechanicalSafe: true`. Never auto-applied by loop/autoPatch;
19
+ `goal.met` remains edge honesty only.
20
+ - **Skills (Phase P):** clearer When/not when routing; explore Shape ladder and dual-plan B;
21
+ coverage narrowed to Ark fitness; adopt/autopilot seed Shape residual; extraction-card
22
+ template in brownfield docs and skills.
23
+ - **Fixture:** `tests/fixtures/design-weak-enforce` for ENFORCE + design-weak honesty.
24
+
25
+ ### Fixed
26
+
27
+ - Module budget for `bin/lib/doctor-plan.mjs` raised to match the design-depth surface.
28
+
29
+ ## 3.0.0 — 2026-07-13
30
+
31
+ ### Added
32
+
33
+ - **Compact active-host onboarding:** `ark start` now asks for the active host on a TTY (or
34
+ detects it non-interactively), writes at most five project files / 25 KB, and uses one
35
+ package/MCP-backed router instead of copied per-host skill packs. It does not alter
36
+ `package.json` unless `--install` is explicit; host removal and re-addition are previewed,
37
+ safe, and reversible.
38
+
39
+ - **Canonical analysis engine bundle:** graph policy, cycle evaluation, and configuration
40
+ diagnostics now have one Kernel implementation shared by the library, CLI, and MCP. A documented
41
+ standalone CLI bundle preserves the package's self-hosted boundary and is protected by a CI drift
42
+ check and Kernel/bundle parity fixtures.
43
+ - **Symbol-aware semantic analysis:** one Kernel extractor now resolves forbidden ambient
44
+ capabilities through local symbols, aliases, `globalThis`, static keys, and destructuring, and
45
+ classifies TS/JS dependency forms across ESM, CommonJS, type-only, and unresolved dynamic edges.
46
+ CLI, safety diagnostics, and AICodeGate consume the same generated implementation. The supported
47
+ soundness envelope is documented and guarded by a labeled adversarial corpus plus TypeScript
48
+ 5/6/7 and mutation matrices.
49
+ - **Versioned adapter parity:** CLI JSON, MCP structured results, write hooks, ESLint, and the
50
+ GitHub Action now expose the same `ark.analysis-result` v1 diagnostics. A generated JSON Schema,
51
+ committed compatibility fixture, exact golden corpus, and mandatory CI parity job prevent
52
+ adapter drift; source-policy decisions no longer live privately inside ESLint.
53
+ - **Runtime package isolation:** the next-major `arkgate` root now contains only gate APIs. The
54
+ optional runtime and NestJS adapter build independently as experimental `@arkgate/runtime`;
55
+ deprecated subpath shims contain no implementation. The non-atomic store is now presented as
56
+ `InMemoryEventBuffer`, with production recovery and durability requirements made explicit.
57
+
58
+ ### Fixed
59
+
60
+ - **Deterministic offline setup tests:** `ark start` fixtures that do not exercise installation now
61
+ pass `--no-install`, preventing a published current version from turning unit tests into registry
62
+ installs.
63
+ - **Node 26 watch fallback:** `ark-check --watch` falls back to bounded polling when recursive
64
+ `fs.watch` fails asynchronously with `EMFILE`, instead of crashing the watcher process.
65
+
7
66
  ## 2.13.0 — 2026-07-11
8
67
 
9
68
  - Added the stable, deterministic analysis IR and public in-memory API: `loadContract`,
package/README.md CHANGED
@@ -16,6 +16,9 @@ and makes sure a “green” check means something real.
16
16
 
17
17
  </div>
18
18
 
19
+ > **ArkGate 3.0** passed its independent beta-exit audit. The npm badge reports the version
20
+ > currently available in the registry.
21
+
19
22
  ---
20
23
 
21
24
  ## The only flow (humans and agents)
@@ -23,8 +26,10 @@ and makes sure a “green” check means something real.
23
26
  If you remember nothing else:
24
27
 
25
28
  ```text
26
- 1. npx arkgate start ← walk tree contract **day-zero origin** → gates
27
- 2. /ark-autopilot ← explore first, dual plan, safe fixes, leave gates on
29
+ 1. npx arkgate start ← read-only preview: files + commands + projected coverage
30
+ npx arkgate start --apply ← apply exactly the previewed mutations
31
+ 2. Compact router ← MCP/AGENTS routes place, validate, and architecture checks
32
+ /ark-autopilot ← optional full guided workflow after its skill pack is installed
28
33
  3. npx arkgate-check --doctor ← “where am I?” anytime (one status screen)
29
34
  ```
30
35
 
@@ -36,8 +41,8 @@ If you remember nothing else:
36
41
  | New ArkGate version | `/ark-upgrade` |
37
42
 
38
43
  **Everything else is optional.** You do not need to learn “modes”, 11 skills, or the runtime
39
- kernel to get value. Agents that are unsure should **only** run `/ark-autopilot` (or the three
40
- commands above).
44
+ kernel to get value. The compact router is enough for normal work; install `/ark-autopilot` only
45
+ when you want the full guided workflow.
41
46
 
42
47
  ---
43
48
 
@@ -65,12 +70,13 @@ It is **not** a web framework, ORM, or job runner.
65
70
 
66
71
  ## Who it’s for
67
72
 
68
- Same start for almost everyone: **`npx arkgate start` → `/ark-autopilot`**.
73
+ Same start for almost everyone: **`npx arkgate start` → compact router** (then
74
+ `/ark-autopilot` when you opt into the full skill pack).
69
75
 
70
76
  | You are… | Same start, then… |
71
77
  |----------|-------------------|
72
- | Builder with AI | Stay on autopilot until doctor is happy |
73
- | Tech lead on a messy monorepo | Autopilot (or deeper `/ark-adopt` if you want a focused brownfield pass) |
78
+ | Builder with AI | Use the compact router and doctor; add autopilot when you want guided remediation |
79
+ | Tech lead on a messy monorepo | Add autopilot (or deeper `/ark-adopt` for a focused brownfield pass) |
74
80
  | Power user | Same flow; use `ark-check --plan` / `--coverage` when you want the raw sensor |
75
81
 
76
82
  **Not for:** no TypeScript, “just one lint rule”, or looking for an app framework.
@@ -110,8 +116,9 @@ Full checklist (CI, MCP, Codex, imports): **[docs/migrate-from-ark-runtime-kerne
110
116
 
111
117
  ```bash
112
118
  npm install -D arkgate typescript
113
- npx arkgate start # contract → day-zero origin gates + plan
114
- # in agent:
119
+ npx arkgate start # read-only preview: exact mutations + projected coverage
120
+ npx arkgate start --apply # apply the compact contract → active-host router → CI plan
121
+ # optional, after installing the full skill pack:
115
122
  # /ark-autopilot
116
123
  npx arkgate-check --doctor # status light + next action
117
124
  ```
@@ -121,7 +128,7 @@ Aliases `ark` / `ark-check` / `ark-mcp` still work. **npm / pnpm / yarn**. No in
121
128
  <details>
122
129
  <summary>What <code>/ark-autopilot</code> does under the hood (optional detail)</summary>
123
130
 
124
- 1. Setup if needed (`ark start` — contract, then **day-zero origin**, then gates).
131
+ 1. Setup if needed (`ark start` — compact contract + active-host router + CI gate; create an HTML/origin report explicitly when you need one).
125
132
  2. **Explore pass** (decision-grade map of *this* product; field path when demos exist).
126
133
  3. **Dual plan:** **A** remediation from `--plan` (mechanical-safe only by default); **B** pattern/evolution bets (never auto-applied as mechanical-safe). Empty plan ≠ “healthy” without explore/B.
127
134
  4. Apply A → re-check; judgment only when you ask for full apply.
@@ -150,8 +157,9 @@ npx arkgate-check --coverage
150
157
  Install once: `npx arkgate-check --install-agent-gates`
151
158
  (`--tools claude,cursor,codex,grok` to pick hosts.)
152
159
 
153
- **Default is always `/ark-autopilot`.** The rest are escapes, not a second curriculum.
154
- Generated `AGENTS.md` includes a **skill routing table** (trigger → skill). Skills are
160
+ **The compact router is the default; `/ark-autopilot` is the full guided option.** The rest are
161
+ escapes, not a second curriculum. Full-install `AGENTS.md` includes a **skill routing table**
162
+ (trigger → skill). Skills are
155
163
  **dual-engine** (CLI sensor + read real source) and end with a fixed **completion contract**;
156
164
  critical handoffs say **STOP** and name the next skill (hosts must follow — markdown cannot chain calls).
157
165
  When the host allows it, skills may **fan out parallel subagents** (disjoint scopes);
@@ -163,20 +171,22 @@ constrain + validate + optional `autoPatch` + `judgmentBrief`). PreToolUse hooks
163
171
  never silent write). Cursor/Codex MCP calls remain advisory. See
164
172
  [docs/ai-gates.md](docs/ai-gates.md).
165
173
 
166
- | Need | Skill |
167
- |------|--------|
168
- | Only the apply loop (plan already exists) | `/ark-loop` |
169
- | Empty greenfield shape/scaffold | `/ark-architect` |
170
- | Deep brownfield / manifest mining alone | `/ark-adopt` |
171
- | Exploratory map of the real product (no apply) | `/ark-explore` |
172
- | New file placement | `/ark-place` |
173
- | Gate violation on a change | `/ark-fix` |
174
- | Design trade-offs within the contract (no package LLM) | `/ark-think` |
175
- | Edit `ark.config.json` safely | `/ark-contract` |
176
- | Plain-language tour of the report | `/ark-explain` |
177
- | Deep coverage + opportunities audit | `/ark-coverage` |
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` |
174
+ | Need | Skill | Not |
175
+ |------|--------|-----|
176
+ | Only the apply loop for plan **A** (edges) | `/ark-loop` | empty A + design residual → explore |
177
+ | Empty greenfield shape/scaffold | `/ark-architect` | brownfield → adopt |
178
+ | Brownfield contract match / baseline / manifest | `/ark-adopt` | map-only → explore |
179
+ | Map / dual-plan **seed** / spaghetti Shape plan (no apply) | `/ark-explore` | fitness-only → coverage |
180
+ | New file placement | `/ark-place` | — |
181
+ | Gate violation on a change | `/ark-fix` | bulk → loop/autopilot |
182
+ | One design decision (2–3 options) | `/ark-think` | full dual-plan → explore |
183
+ | Edit `ark.config.json` safely | `/ark-contract` | — |
184
+ | Plain-language tour / HTML report | `/ark-explain` | recon → explore |
185
+ | Ark **fitness** (governed%, gates, install gaps) | `/ark-coverage` | full recon → explore |
186
+ | Evaluate experimental runtime | `/ark-runtime` | — |
187
+ | Bump ArkGate + refresh active host | `/ark-upgrade` | — |
188
+
189
+ Brownfield phases: **Align** (honest contract) → **Stabilize** (real baseline) → **Shape** (golden pattern + pilot). ENFORCE with empty plan A can still be **design-weak** — that residual is explore/autopilot **B**, not “done.”
180
190
 
181
191
  ### Host enforcement support
182
192
 
@@ -211,16 +221,18 @@ ark.config.json
211
221
  [configuration and editor guide](docs/configuration.md).
212
222
  - **Frameworks:** Nest / Next / express / library layouts get sensible globs on init so day-one coverage is real.
213
223
  - **Brownfield:** baseline ratchet, refuse to freeze a wrong contract, `/ark-adopt` for mature trees.
214
- - **Agents:** skills install into Claude / Cursor / Codex / Grok; `ark start` freezes **day-zero origin** under `.ark/reports/` **before** agent docs/CI templates.
224
+ - **Agents:** `ark start` asks for (or detects) one active host and writes one compact router, not copied skill packs, in at most five project files and 25 KB. Use `ark-check --install-agent-gates --skills-only --tools <host>` later when you explicitly want the full `/ark-*` skill set. Reports are opt-in with `ark-check --report`.
215
225
  - **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.
216
226
  - **Fail-closed CI (2.11):** `--strict-merge` combines config coverage, shared gate-file
217
227
  presence, and bypass diagnostics for dynamic imports, TypeScript suppressions, explicit `any`
218
228
  casts, InMemory runtime defaults, and disabled peer isolation. `--strict` is a compatibility
219
229
  alias. Neither requires an editor hook; use `--require-write-hook claude|grok` when that local
220
230
  guarantee is part of the check.
221
- - **Trust / coverage (2.12):** package unit-test floors on the broad product surface
231
+ - **Trust / coverage (3.0 release baseline):** package unit-test floors on the broad product surface
222
232
  (statements/lines **≥80%**, branches/functions **≥85%**; enforcement-critical modules **≥95%**
223
- branch). Explore dual-plan + day-zero origin first (see above). Roadmap next: Q2 repair dogfood matrix.
233
+ branch). The V05 beta-exit audit passed with 12 pinned public adoptions, 97% median governed
234
+ coverage, and zero P0/P1 findings. Stable publication remains the signed-tag, GitHub Release,
235
+ and provenance-backed npm workflow documented in [the 3.0.0 release notes](docs/releases/3.0.0.md).
224
236
  - **TypeScript:** project compilers 5.x / 6.x / 7.x — gate falls back to a nested JS-API TypeScript when TS 7’s main export is version-only ([docs/typescript-support.md](docs/typescript-support.md)).
225
237
 
226
238
  ### Why not only ESLint / dependency-cruiser / Nx?
@@ -242,7 +254,11 @@ ark.config.json
242
254
  ## Common commands
243
255
 
244
256
  ```bash
245
- npx arkgate start # guided setup: contract → origin → gates → plan
257
+ npx arkgate start # guided read-only preview
258
+ npx arkgate start --apply # apply the compact active-host setup (≤5 files)
259
+ npx arkgate start --tools codex --apply # select the host explicitly
260
+ npx arkgate start --install --apply # also add arkgate to package.json (explicit only)
261
+ npx arkgate start --remove-host codex # preview compact-host removal; add --apply to confirm
246
262
  npx arkgate-check --doctor # health + Adoption gaps (not just fitness)
247
263
  npx arkgate-check --doctor --json # machine-readable doctor.adoption
248
264
  npx arkgate-check --strict # fail-closed CI + installed-gate/safety checks
@@ -266,21 +282,21 @@ CI (example):
266
282
 
267
283
  Gates need **no app code changes**. The runtime API is currently **experimental** and is not a
268
284
  production-readiness claim. If you want to evaluate runtime intent/event contracts, use the
269
- opt-in subpath (preferred):
285
+ separate experimental package:
270
286
 
271
287
  ```ts
272
- import { createStrictArkKernelFromConfig } from 'arkgate/runtime';
288
+ import { createStrictArkKernelFromConfig } from '@arkgate/runtime';
273
289
  // see docs/production-hardening.md and docs/package-surface.md
274
290
  ```
275
291
 
276
- Root `import { } from 'arkgate'` still re-exports kernel symbols for compatibility
277
- in this major; use `arkgate/runtime` when evaluating the experimental surface.
292
+ The stable `arkgate` package does not bundle runtime implementation. The deprecated
293
+ `arkgate/runtime` forwarding shim requires `@arkgate/runtime` and is removed in ArkGate 4.
278
294
 
279
- NestJS: `arkgate/nestjs` (optional peer `@nestjs/common`).
295
+ NestJS: `@arkgate/runtime/nestjs` (optional peer `@nestjs/common`).
280
296
 
281
297
  ### Durability stance (built-in stores)
282
298
 
283
- The kernel’s default stores (`InMemoryOutboxStore`, `InMemoryAuditStore`,
299
+ The kernel’s default stores (`InMemoryEventBuffer`, `InMemoryAuditStore`,
284
300
  `InMemoryReadModelStore`, `InMemoryWorkflowStore`) are **reference in-memory only**:
285
301
  fine for tests, demos, and single-process local work — they **do not** survive restarts
286
302
  and are **not** production durability. Implement the store interfaces (or inject your own)
@@ -301,6 +317,7 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
301
317
  | Agent / MCP tools | [docs/agent-guide.md](docs/agent-guide.md) |
302
318
  | Demos | [docs/demos/](docs/demos/) |
303
319
  | Examples | [examples/](examples/README.md) |
320
+ | Release 3.0.0 | [release notes and publication checklist](docs/releases/3.0.0.md) |
304
321
  | Roadmap | [ROADMAP.md](ROADMAP.md) · [Changelog](CHANGELOG.md) |
305
322
 
306
323
  ---
package/bin/ark-check.mjs CHANGED
@@ -42,6 +42,7 @@ import {
42
42
  checkArchitectureScriptSnippet,
43
43
  arkCheckCommand,
44
44
  arkPackageVersion,
45
+ compactRouterHost,
45
46
  REQUIRED_GATE_FILES,
46
47
  codexPromptsDir,
47
48
  detectWritePathCapabilities,
@@ -84,6 +85,7 @@ import {
84
85
  ARCHITECTURE_PRESETS,
85
86
  } from './lib/presets.mjs';
86
87
  import { loadArkConfigContract, parseArkConfigJson } from './lib/config-contract.mjs';
88
+ import { createAdapterResult } from './lib/adapter-contract.mjs';
87
89
 
88
90
  import {
89
91
  collectGovernedFiles,
@@ -110,6 +112,7 @@ function parseArgs(argv) {
110
112
  requireWriteHook: undefined,
111
113
  init: false,
112
114
  installAgentGates: false,
115
+ compact: false,
113
116
  tools: undefined,
114
117
  force: false,
115
118
  skillsOnly: false,
@@ -153,6 +156,7 @@ function parseArgs(argv) {
153
156
  else if (arg === '--init') args.init = true;
154
157
  else if (arg === '--preset') args.preset = requireValue(arg, i++);
155
158
  else if (arg === '--install-agent-gates') args.installAgentGates = true;
159
+ else if (arg === '--compact') args.compact = true;
156
160
  else if (arg === '--tools') {
157
161
  // Consume the next arg only when it isn't another flag (same rule as --baseline),
158
162
  // so `--tools --force` can't silently eat --force as a "tool name".
@@ -1038,7 +1042,13 @@ async function main() {
1038
1042
  // callers still get a clear signal from the exit code and the human-mode line.
1039
1043
  if (!args.json) {
1040
1044
  if (args.requireGates) {
1041
- console.log('Ark gates present (merge profile): ' + REQUIRED_GATE_FILES.join(', '));
1045
+ const compactHost = compactRouterHost(args.root);
1046
+ console.log(
1047
+ 'Ark gates present (merge profile): ' +
1048
+ (compactHost
1049
+ ? `AGENTS.md, compact host registration (${compactHost})`
1050
+ : REQUIRED_GATE_FILES.join(', '))
1051
+ );
1042
1052
  }
1043
1053
  if (writeRequest?.host) {
1044
1054
  console.log(`Ark hard-write hook present for ${writeRequest.host}.`);
@@ -1073,7 +1083,11 @@ async function main() {
1073
1083
  )
1074
1084
  );
1075
1085
  }
1076
- runPlan(root, [], args.json, cov.governed.percent, cov.governed.totalFiles);
1086
+ runPlan(root, [], args.json, cov.governed.percent, cov.governed.totalFiles, {
1087
+ config,
1088
+ files,
1089
+ coverage: cov,
1090
+ });
1077
1091
  return;
1078
1092
  }
1079
1093
  console.error(
@@ -1204,7 +1218,11 @@ async function main() {
1204
1218
 
1205
1219
  if (args.plan) {
1206
1220
  const cov = computeCoverage(root, config, files, rules);
1207
- runPlan(root, activeViolations, args.json, cov.governed.percent, cov.governed.totalFiles);
1221
+ runPlan(root, activeViolations, args.json, cov.governed.percent, cov.governed.totalFiles, {
1222
+ config,
1223
+ files,
1224
+ coverage: cov,
1225
+ });
1208
1226
  return;
1209
1227
  }
1210
1228
 
@@ -1330,7 +1348,13 @@ async function main() {
1330
1348
  }
1331
1349
 
1332
1350
  if (args.json) {
1351
+ const adapterResult = createAdapterResult({
1352
+ valid: ok,
1353
+ violations: activeViolations.map(enrichViolationWithFixClass),
1354
+ warnings,
1355
+ });
1333
1356
  console.log(JSON.stringify({
1357
+ ...adapterResult,
1334
1358
  ok,
1335
1359
  violations: activeViolations.map(enrichViolationWithFixClass),
1336
1360
  suppressedViolations: suppressed.length,
@@ -1489,9 +1513,13 @@ async function runWatchMode(args) {
1489
1513
  const target = path.join(args.root, entry);
1490
1514
  if (!fs.existsSync(target)) continue;
1491
1515
  try {
1492
- fs.watch(target, { recursive: true }, rerun);
1516
+ const watcher = fs.watch(target, { recursive: true }, rerun);
1517
+ watcher.on('error', () => {
1518
+ watcher.close();
1519
+ watchByPolling(target, rerun);
1520
+ });
1493
1521
  } catch {
1494
- fs.watch(target, rerun);
1522
+ watchByPolling(target, rerun);
1495
1523
  }
1496
1524
  }
1497
1525
 
@@ -1499,7 +1527,45 @@ async function runWatchMode(args) {
1499
1527
  await new Promise(() => {});
1500
1528
  }
1501
1529
 
1530
+ function watchByPolling(target, onChange) {
1531
+ let previous = watchFingerprint(target);
1532
+ setInterval(() => {
1533
+ const current = watchFingerprint(target);
1534
+ if (current === previous) return;
1535
+ previous = current;
1536
+ onChange();
1537
+ }, 250);
1538
+ }
1539
+
1540
+ function watchFingerprint(target) {
1541
+ const pending = [target];
1542
+ const entries = [];
1543
+ while (pending.length > 0) {
1544
+ const current = pending.pop();
1545
+ let stat;
1546
+ try {
1547
+ stat = fs.statSync(current);
1548
+ } catch {
1549
+ continue;
1550
+ }
1551
+ entries.push(`${current}:${stat.mtimeMs}:${stat.size}`);
1552
+ if (!stat.isDirectory()) continue;
1553
+ try {
1554
+ for (const name of fs.readdirSync(current)) pending.push(path.join(current, name));
1555
+ } catch {
1556
+ // A concurrent delete is represented by the next fingerprint.
1557
+ }
1558
+ }
1559
+ return entries.sort().join('|');
1560
+ }
1561
+
1502
1562
  main().catch((error) => {
1503
- console.error(error instanceof Error ? error.message : String(error));
1563
+ console.error(
1564
+ process.env.ARK_DEBUG_STACK === '1' && error instanceof Error
1565
+ ? error.stack
1566
+ : error instanceof Error
1567
+ ? error.message
1568
+ : String(error)
1569
+ );
1504
1570
  process.exitCode = 2;
1505
1571
  });
package/bin/ark-mcp.mjs CHANGED
@@ -59,6 +59,7 @@ 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
61
  import { loadArkConfigContract } from './lib/config-contract.mjs';
62
+ import { ARK_ANALYSIS_RESULT_SCHEMA, createAdapterResult } from './lib/adapter-contract.mjs';
62
63
 
63
64
  const arkCheckBin = fileURLToPath(new URL('./ark-check.mjs', import.meta.url));
64
65
 
@@ -179,6 +180,8 @@ function normalizeHookPayload(payload) {
179
180
  Edit: 'Edit',
180
181
  search_replace: 'Edit',
181
182
  MultiEdit: 'MultiEdit',
183
+ ApplyPatch: 'ApplyPatch',
184
+ apply_patch: 'ApplyPatch',
182
185
  };
183
186
  const toolName = nameMap[rawName] ?? rawName;
184
187
  const filePath =
@@ -193,6 +196,70 @@ function normalizeHookPayload(payload) {
193
196
  };
194
197
  }
195
198
 
199
+ function applyCodexUpdatePatch(current, lines) {
200
+ let source = current.split('\n');
201
+ let cursor = 0;
202
+ const hunks = [];
203
+ let hunk = [];
204
+ for (const line of lines) {
205
+ if (line.startsWith('@@')) {
206
+ if (hunk.length > 0) hunks.push(hunk);
207
+ hunk = [];
208
+ } else if (/^[ +\-]/.test(line)) {
209
+ hunk.push(line);
210
+ }
211
+ }
212
+ if (hunk.length > 0) hunks.push(hunk);
213
+ for (const entries of hunks) {
214
+ const oldLines = entries.filter((line) => !line.startsWith('+')).map((line) => line.slice(1));
215
+ const newLines = entries.filter((line) => !line.startsWith('-')).map((line) => line.slice(1));
216
+ let found = -1;
217
+ for (let at = cursor; at <= source.length - oldLines.length; at += 1) {
218
+ if (oldLines.every((line, index) => source[at + index] === line)) {
219
+ found = at;
220
+ break;
221
+ }
222
+ }
223
+ if (found < 0) return null;
224
+ source.splice(found, oldLines.length, ...newLines);
225
+ cursor = found + newLines.length;
226
+ }
227
+ return source.join('\n');
228
+ }
229
+
230
+ function codexPatchWrites(patch, root) {
231
+ if (typeof patch !== 'string' || !patch.includes('*** Begin Patch')) return [];
232
+ const lines = patch.split('\n');
233
+ const writes = [];
234
+ for (let index = 0; index < lines.length; index += 1) {
235
+ const match = lines[index].match(/^\*\*\* (Add|Update|Delete) File: (.+)$/);
236
+ if (!match) continue;
237
+ const [, action, relativePath] = match;
238
+ const body = [];
239
+ for (index += 1; index < lines.length && !lines[index].startsWith('*** '); index += 1) {
240
+ body.push(lines[index]);
241
+ }
242
+ index -= 1;
243
+ if (action === 'Delete') continue;
244
+ const filePath = path.resolve(root, relativePath);
245
+ let content;
246
+ if (action === 'Add') {
247
+ content = body.filter((line) => line.startsWith('+')).map((line) => line.slice(1)).join('\n');
248
+ if (body.some((line) => line.startsWith('+'))) content += '\n';
249
+ } else {
250
+ let current;
251
+ try {
252
+ current = fs.readFileSync(filePath, 'utf8');
253
+ } catch {
254
+ continue;
255
+ }
256
+ content = applyCodexUpdatePatch(current, body);
257
+ }
258
+ if (typeof content === 'string') writes.push({ filePath, content });
259
+ }
260
+ return writes;
261
+ }
262
+
196
263
  /**
197
264
  * Compute the file content a Write/Edit/MultiEdit is about to produce. Edits are applied
198
265
  * to the CURRENT on-disk file so the gate judges the real post-edit state, not the edit
@@ -237,7 +304,27 @@ function runHook(gate, config, args, ts) {
237
304
  return;
238
305
  }
239
306
 
307
+ runHookPayload(payload, gate, config, args, ts);
308
+ }
309
+
310
+ function runHookPayload(payload, gate, config, args, ts) {
240
311
  const { toolName, toolInput, grokStyle } = normalizeHookPayload(payload);
312
+ if (toolName === 'ApplyPatch') {
313
+ const patch = toolInput.patch ?? toolInput.input ?? toolInput.content;
314
+ for (const write of codexPatchWrites(patch, args.root)) {
315
+ runHookPayload(
316
+ {
317
+ tool_name: 'Write',
318
+ tool_input: { file_path: write.filePath, content: write.content },
319
+ },
320
+ gate,
321
+ config,
322
+ args,
323
+ ts
324
+ );
325
+ }
326
+ return;
327
+ }
241
328
  const filePath = toolInput.file_path;
242
329
  if (!['Write', 'Edit', 'MultiEdit'].includes(toolName)) return;
243
330
  if (typeof filePath !== 'string' || !SOURCE_FILE.test(filePath) || filePath.endsWith('.d.ts')) {
@@ -298,6 +385,11 @@ function runHook(gate, config, args, ts) {
298
385
  return false;
299
386
  });
300
387
  if (newViolations.length === 0) return;
388
+ const normalizedRel = rel.split(path.sep).join('/');
389
+ const adapterResult = createAdapterResult({
390
+ valid: false,
391
+ violations: newViolations.map((violation) => ({ ...violation, file: normalizedRel })),
392
+ });
301
393
 
302
394
  const lines = newViolations.map(
303
395
  (violation) =>
@@ -339,9 +431,10 @@ function runHook(gate, config, args, ts) {
339
431
  if (repair) {
340
432
  // Structured envelope for any host that can re-inject. Never writes the file.
341
433
  const repairPayload = {
434
+ ...adapterResult,
342
435
  mode: 'repair',
343
436
  decision: 'deny',
344
- filePath: rel.split(path.sep).join('/'),
437
+ filePath: normalizedRel,
345
438
  ...(layer ? { layer } : {}),
346
439
  ...(autoPatch
347
440
  ? {
@@ -367,6 +460,7 @@ function runHook(gate, config, args, ts) {
367
460
  JSON.stringify({
368
461
  decision: 'deny',
369
462
  reason: message,
463
+ analysis: adapterResult,
370
464
  ...(repair && autoPatch ? { autoPatch } : {}),
371
465
  ...(repair ? { repair: true } : {}),
372
466
  }) + '\n'
@@ -616,6 +710,7 @@ async function main() {
616
710
  },
617
711
  required: ['source'],
618
712
  },
713
+ outputSchema: ARK_ANALYSIS_RESULT_SCHEMA,
619
714
  },
620
715
  {
621
716
  name: 'ark_check',
@@ -638,6 +733,7 @@ async function main() {
638
733
  },
639
734
  },
640
735
  },
736
+ outputSchema: ARK_ANALYSIS_RESULT_SCHEMA,
641
737
  },
642
738
  {
643
739
  name: 'ark_coverage',
@@ -826,12 +922,17 @@ async function main() {
826
922
  validate: validateOnce,
827
923
  resolveTargetAbs: resolveImportFileAbs,
828
924
  });
925
+ const adapterResult = createAdapterResult({
926
+ valid: result.valid,
927
+ violations: result.violations,
928
+ });
829
929
  return {
830
930
  content: [
831
931
  {
832
932
  type: 'text',
833
933
  text: JSON.stringify(
834
934
  {
935
+ ...adapterResult,
835
936
  valid: result.valid,
836
937
  violations: result.violations,
837
938
  ...(result.autoPatch ? { autoPatch: result.autoPatch } : {}),
@@ -842,6 +943,7 @@ async function main() {
842
943
  ),
843
944
  },
844
945
  ],
946
+ structuredContent: adapterResult,
845
947
  isError: !result.valid,
846
948
  };
847
949
  }
@@ -868,6 +970,11 @@ async function main() {
868
970
  }
869
971
  return {
870
972
  content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
973
+ structuredContent: {
974
+ schemaVersion: data.schemaVersion,
975
+ valid: data.valid,
976
+ diagnostics: data.diagnostics,
977
+ },
871
978
  isError: data.ok === false,
872
979
  };
873
980
  }