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.
- package/CHANGELOG.md +59 -0
- package/README.md +53 -36
- package/bin/ark-check.mjs +72 -6
- package/bin/ark-mcp.mjs +108 -1
- package/bin/ark-shared.mjs +204 -149
- package/bin/ark.mjs +90 -25
- package/bin/lib/adapter-contract.mjs +93 -0
- package/bin/lib/agent-gates.mjs +1 -0
- package/bin/lib/analysis-engine.mjs +1171 -0
- package/bin/lib/architecture-scan.mjs +84 -135
- package/bin/lib/ci-and-commands.mjs +51 -15
- package/bin/lib/config-warnings.mjs +7 -205
- package/bin/lib/design-smells.mjs +434 -0
- package/bin/lib/doctor-plan.mjs +149 -16
- package/bin/lib/field-install.mjs +67 -10
- package/bin/lib/gate-files.mjs +42 -3
- package/bin/lib/graph-cycles.mjs +4 -54
- package/bin/lib/hook-templates.mjs +33 -1
- package/bin/lib/host-support-matrix.mjs +7 -1
- package/bin/lib/install-migrate.mjs +54 -16
- package/bin/lib/presets.mjs +42 -2
- package/bin/lib/safety-diagnostics.mjs +18 -17
- package/bin/lib/scan-files.mjs +12 -1
- package/bin/lib/skill-install.mjs +8 -1
- package/bin/lib/source-policy.mjs +36 -0
- package/bin/lib/start-preview.mjs +271 -0
- package/bin/lib/ts-resolve.mjs +11 -2
- package/bin/lib/write-path-capabilities.mjs +4 -0
- package/compat/nestjs.cjs +2 -0
- package/compat/nestjs.d.ts +2 -0
- package/compat/nestjs.js +1 -0
- package/compat/runtime.cjs +2 -0
- package/compat/runtime.d.ts +2 -0
- package/compat/runtime.js +1 -0
- package/dist/configContract-BxSIwVRo.d.cts +259 -0
- package/dist/configContract-BxSIwVRo.d.ts +259 -0
- package/dist/eslint/index.cjs +125 -48
- package/dist/eslint/index.d.cts +7 -1
- package/dist/eslint/index.d.ts +7 -1
- package/dist/eslint/index.js +125 -48
- package/dist/index.cjs +1248 -3302
- package/dist/index.d.cts +359 -483
- package/dist/index.d.ts +359 -483
- package/dist/index.js +1231 -3248
- package/docs/agent-guide.md +34 -16
- package/docs/ai-gates.md +30 -7
- package/docs/brownfield-adoption.md +52 -1
- package/docs/migrate-from-ark-runtime-kernel.md +2 -3
- package/docs/package-surface.md +10 -13
- package/docs/production-hardening.md +17 -4
- package/docs/typescript-support.md +27 -0
- package/package.json +33 -11
- package/schemas/ark.analysis-result.schema.json +91 -0
- package/server.json +2 -2
- package/templates/skills/ark-adopt.md +26 -3
- package/templates/skills/ark-architect.md +10 -2
- package/templates/skills/ark-autopilot.md +37 -20
- package/templates/skills/ark-contract.md +7 -0
- package/templates/skills/ark-coverage.md +44 -45
- package/templates/skills/ark-explain.md +8 -0
- package/templates/skills/ark-explore.md +117 -47
- package/templates/skills/ark-fix.md +22 -0
- package/templates/skills/ark-loop.md +15 -1
- package/templates/skills/ark-place.md +7 -0
- package/templates/skills/ark-think.md +24 -20
- package/dist/configContract-iBLxx5Tz.d.cts +0 -53
- package/dist/configContract-iBLxx5Tz.d.ts +0 -53
- package/dist/eslint/index.cjs.map +0 -1
- package/dist/eslint/index.js.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/nestjs/index.cjs +0 -2606
- package/dist/nestjs/index.cjs.map +0 -1
- package/dist/nestjs/index.d.cts +0 -23
- package/dist/nestjs/index.d.ts +0 -23
- package/dist/nestjs/index.js +0 -2582
- package/dist/nestjs/index.js.map +0 -1
- package/dist/runtime/index.cjs +0 -4014
- package/dist/runtime/index.cjs.map +0 -1
- package/dist/runtime/index.d.cts +0 -3
- package/dist/runtime/index.d.ts +0 -3
- package/dist/runtime/index.js +0 -3925
- package/dist/runtime/index.js.map +0 -1
- package/dist/types-BxBwnBpC.d.cts +0 -1041
- 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 ←
|
|
27
|
-
|
|
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.
|
|
40
|
-
|
|
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` →
|
|
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 |
|
|
73
|
-
| Tech lead on a messy monorepo |
|
|
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 #
|
|
114
|
-
#
|
|
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
|
|
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
|
-
**
|
|
154
|
-
|
|
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
|
|
169
|
-
| Empty greenfield shape/scaffold | `/ark-architect` |
|
|
170
|
-
|
|
|
171
|
-
|
|
|
172
|
-
| New file placement | `/ark-place` |
|
|
173
|
-
| Gate violation on a change | `/ark-fix` |
|
|
174
|
-
|
|
|
175
|
-
| Edit `ark.config.json` safely | `/ark-contract` |
|
|
176
|
-
| Plain-language tour
|
|
177
|
-
|
|
|
178
|
-
| Evaluate
|
|
179
|
-
| Bump ArkGate + refresh active host
|
|
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:**
|
|
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 (
|
|
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).
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
277
|
-
|
|
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:
|
|
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 (`
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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:
|
|
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
|
}
|