arkgate 2.13.0 → 3.0.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.
- package/CHANGELOG.md +39 -0
- package/README.md +37 -22
- package/bin/ark-check.mjs +62 -4
- 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 +31 -0
- package/bin/lib/config-warnings.mjs +7 -205
- 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 +28 -16
- package/docs/ai-gates.md +30 -7
- package/docs/migrate-from-ark-runtime-kernel.md +2 -3
- package/docs/package-surface.md +8 -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-architect.md +3 -2
- 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,45 @@ 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 the prepared stable release.
|
|
8
|
+
|
|
9
|
+
## 3.0.0 — 2026-07-13
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Compact active-host onboarding:** `ark start` now asks for the active host on a TTY (or
|
|
14
|
+
detects it non-interactively), writes at most five project files / 25 KB, and uses one
|
|
15
|
+
package/MCP-backed router instead of copied per-host skill packs. It does not alter
|
|
16
|
+
`package.json` unless `--install` is explicit; host removal and re-addition are previewed,
|
|
17
|
+
safe, and reversible.
|
|
18
|
+
|
|
19
|
+
- **Canonical analysis engine bundle:** graph policy, cycle evaluation, and configuration
|
|
20
|
+
diagnostics now have one Kernel implementation shared by the library, CLI, and MCP. A documented
|
|
21
|
+
standalone CLI bundle preserves the package's self-hosted boundary and is protected by a CI drift
|
|
22
|
+
check and Kernel/bundle parity fixtures.
|
|
23
|
+
- **Symbol-aware semantic analysis:** one Kernel extractor now resolves forbidden ambient
|
|
24
|
+
capabilities through local symbols, aliases, `globalThis`, static keys, and destructuring, and
|
|
25
|
+
classifies TS/JS dependency forms across ESM, CommonJS, type-only, and unresolved dynamic edges.
|
|
26
|
+
CLI, safety diagnostics, and AICodeGate consume the same generated implementation. The supported
|
|
27
|
+
soundness envelope is documented and guarded by a labeled adversarial corpus plus TypeScript
|
|
28
|
+
5/6/7 and mutation matrices.
|
|
29
|
+
- **Versioned adapter parity:** CLI JSON, MCP structured results, write hooks, ESLint, and the
|
|
30
|
+
GitHub Action now expose the same `ark.analysis-result` v1 diagnostics. A generated JSON Schema,
|
|
31
|
+
committed compatibility fixture, exact golden corpus, and mandatory CI parity job prevent
|
|
32
|
+
adapter drift; source-policy decisions no longer live privately inside ESLint.
|
|
33
|
+
- **Runtime package isolation:** the next-major `arkgate` root now contains only gate APIs. The
|
|
34
|
+
optional runtime and NestJS adapter build independently as experimental `@arkgate/runtime`;
|
|
35
|
+
deprecated subpath shims contain no implementation. The non-atomic store is now presented as
|
|
36
|
+
`InMemoryEventBuffer`, with production recovery and durability requirements made explicit.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- **Deterministic offline setup tests:** `ark start` fixtures that do not exercise installation now
|
|
41
|
+
pass `--no-install`, preventing a published current version from turning unit tests into registry
|
|
42
|
+
installs.
|
|
43
|
+
- **Node 26 watch fallback:** `ark-check --watch` falls back to bounded polling when recursive
|
|
44
|
+
`fs.watch` fails asynchronously with `EMFILE`, instead of crashing the watcher process.
|
|
45
|
+
|
|
7
46
|
## 2.13.0 — 2026-07-11
|
|
8
47
|
|
|
9
48
|
- 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);
|
|
@@ -211,16 +219,18 @@ ark.config.json
|
|
|
211
219
|
[configuration and editor guide](docs/configuration.md).
|
|
212
220
|
- **Frameworks:** Nest / Next / express / library layouts get sensible globs on init so day-one coverage is real.
|
|
213
221
|
- **Brownfield:** baseline ratchet, refuse to freeze a wrong contract, `/ark-adopt` for mature trees.
|
|
214
|
-
- **Agents:**
|
|
222
|
+
- **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
223
|
- **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
224
|
- **Fail-closed CI (2.11):** `--strict-merge` combines config coverage, shared gate-file
|
|
217
225
|
presence, and bypass diagnostics for dynamic imports, TypeScript suppressions, explicit `any`
|
|
218
226
|
casts, InMemory runtime defaults, and disabled peer isolation. `--strict` is a compatibility
|
|
219
227
|
alias. Neither requires an editor hook; use `--require-write-hook claude|grok` when that local
|
|
220
228
|
guarantee is part of the check.
|
|
221
|
-
- **Trust / coverage (
|
|
229
|
+
- **Trust / coverage (3.0 release baseline):** package unit-test floors on the broad product surface
|
|
222
230
|
(statements/lines **≥80%**, branches/functions **≥85%**; enforcement-critical modules **≥95%**
|
|
223
|
-
branch).
|
|
231
|
+
branch). The V05 beta-exit audit passed with 12 pinned public adoptions, 97% median governed
|
|
232
|
+
coverage, and zero P0/P1 findings. Stable publication remains the signed-tag, GitHub Release,
|
|
233
|
+
and provenance-backed npm workflow documented in [the 3.0.0 release notes](docs/releases/3.0.0.md).
|
|
224
234
|
- **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
235
|
|
|
226
236
|
### Why not only ESLint / dependency-cruiser / Nx?
|
|
@@ -242,7 +252,11 @@ ark.config.json
|
|
|
242
252
|
## Common commands
|
|
243
253
|
|
|
244
254
|
```bash
|
|
245
|
-
npx arkgate start # guided
|
|
255
|
+
npx arkgate start # guided read-only preview
|
|
256
|
+
npx arkgate start --apply # apply the compact active-host setup (≤5 files)
|
|
257
|
+
npx arkgate start --tools codex --apply # select the host explicitly
|
|
258
|
+
npx arkgate start --install --apply # also add arkgate to package.json (explicit only)
|
|
259
|
+
npx arkgate start --remove-host codex # preview compact-host removal; add --apply to confirm
|
|
246
260
|
npx arkgate-check --doctor # health + Adoption gaps (not just fitness)
|
|
247
261
|
npx arkgate-check --doctor --json # machine-readable doctor.adoption
|
|
248
262
|
npx arkgate-check --strict # fail-closed CI + installed-gate/safety checks
|
|
@@ -266,21 +280,21 @@ CI (example):
|
|
|
266
280
|
|
|
267
281
|
Gates need **no app code changes**. The runtime API is currently **experimental** and is not a
|
|
268
282
|
production-readiness claim. If you want to evaluate runtime intent/event contracts, use the
|
|
269
|
-
|
|
283
|
+
separate experimental package:
|
|
270
284
|
|
|
271
285
|
```ts
|
|
272
|
-
import { createStrictArkKernelFromConfig } from 'arkgate/runtime';
|
|
286
|
+
import { createStrictArkKernelFromConfig } from '@arkgate/runtime';
|
|
273
287
|
// see docs/production-hardening.md and docs/package-surface.md
|
|
274
288
|
```
|
|
275
289
|
|
|
276
|
-
|
|
277
|
-
|
|
290
|
+
The stable `arkgate` package does not bundle runtime implementation. The deprecated
|
|
291
|
+
`arkgate/runtime` forwarding shim requires `@arkgate/runtime` and is removed in ArkGate 4.
|
|
278
292
|
|
|
279
|
-
NestJS:
|
|
293
|
+
NestJS: `@arkgate/runtime/nestjs` (optional peer `@nestjs/common`).
|
|
280
294
|
|
|
281
295
|
### Durability stance (built-in stores)
|
|
282
296
|
|
|
283
|
-
The kernel’s default stores (`
|
|
297
|
+
The kernel’s default stores (`InMemoryEventBuffer`, `InMemoryAuditStore`,
|
|
284
298
|
`InMemoryReadModelStore`, `InMemoryWorkflowStore`) are **reference in-memory only**:
|
|
285
299
|
fine for tests, demos, and single-process local work — they **do not** survive restarts
|
|
286
300
|
and are **not** production durability. Implement the store interfaces (or inject your own)
|
|
@@ -301,6 +315,7 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
|
|
|
301
315
|
| Agent / MCP tools | [docs/agent-guide.md](docs/agent-guide.md) |
|
|
302
316
|
| Demos | [docs/demos/](docs/demos/) |
|
|
303
317
|
| Examples | [examples/](examples/README.md) |
|
|
318
|
+
| Release 3.0.0 | [release notes and publication checklist](docs/releases/3.0.0.md) |
|
|
304
319
|
| Roadmap | [ROADMAP.md](ROADMAP.md) · [Changelog](CHANGELOG.md) |
|
|
305
320
|
|
|
306
321
|
---
|
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}.`);
|
|
@@ -1330,7 +1340,13 @@ async function main() {
|
|
|
1330
1340
|
}
|
|
1331
1341
|
|
|
1332
1342
|
if (args.json) {
|
|
1343
|
+
const adapterResult = createAdapterResult({
|
|
1344
|
+
valid: ok,
|
|
1345
|
+
violations: activeViolations.map(enrichViolationWithFixClass),
|
|
1346
|
+
warnings,
|
|
1347
|
+
});
|
|
1333
1348
|
console.log(JSON.stringify({
|
|
1349
|
+
...adapterResult,
|
|
1334
1350
|
ok,
|
|
1335
1351
|
violations: activeViolations.map(enrichViolationWithFixClass),
|
|
1336
1352
|
suppressedViolations: suppressed.length,
|
|
@@ -1489,9 +1505,13 @@ async function runWatchMode(args) {
|
|
|
1489
1505
|
const target = path.join(args.root, entry);
|
|
1490
1506
|
if (!fs.existsSync(target)) continue;
|
|
1491
1507
|
try {
|
|
1492
|
-
fs.watch(target, { recursive: true }, rerun);
|
|
1508
|
+
const watcher = fs.watch(target, { recursive: true }, rerun);
|
|
1509
|
+
watcher.on('error', () => {
|
|
1510
|
+
watcher.close();
|
|
1511
|
+
watchByPolling(target, rerun);
|
|
1512
|
+
});
|
|
1493
1513
|
} catch {
|
|
1494
|
-
|
|
1514
|
+
watchByPolling(target, rerun);
|
|
1495
1515
|
}
|
|
1496
1516
|
}
|
|
1497
1517
|
|
|
@@ -1499,7 +1519,45 @@ async function runWatchMode(args) {
|
|
|
1499
1519
|
await new Promise(() => {});
|
|
1500
1520
|
}
|
|
1501
1521
|
|
|
1522
|
+
function watchByPolling(target, onChange) {
|
|
1523
|
+
let previous = watchFingerprint(target);
|
|
1524
|
+
setInterval(() => {
|
|
1525
|
+
const current = watchFingerprint(target);
|
|
1526
|
+
if (current === previous) return;
|
|
1527
|
+
previous = current;
|
|
1528
|
+
onChange();
|
|
1529
|
+
}, 250);
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
function watchFingerprint(target) {
|
|
1533
|
+
const pending = [target];
|
|
1534
|
+
const entries = [];
|
|
1535
|
+
while (pending.length > 0) {
|
|
1536
|
+
const current = pending.pop();
|
|
1537
|
+
let stat;
|
|
1538
|
+
try {
|
|
1539
|
+
stat = fs.statSync(current);
|
|
1540
|
+
} catch {
|
|
1541
|
+
continue;
|
|
1542
|
+
}
|
|
1543
|
+
entries.push(`${current}:${stat.mtimeMs}:${stat.size}`);
|
|
1544
|
+
if (!stat.isDirectory()) continue;
|
|
1545
|
+
try {
|
|
1546
|
+
for (const name of fs.readdirSync(current)) pending.push(path.join(current, name));
|
|
1547
|
+
} catch {
|
|
1548
|
+
// A concurrent delete is represented by the next fingerprint.
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
return entries.sort().join('|');
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1502
1554
|
main().catch((error) => {
|
|
1503
|
-
console.error(
|
|
1555
|
+
console.error(
|
|
1556
|
+
process.env.ARK_DEBUG_STACK === '1' && error instanceof Error
|
|
1557
|
+
? error.stack
|
|
1558
|
+
: error instanceof Error
|
|
1559
|
+
? error.message
|
|
1560
|
+
: String(error)
|
|
1561
|
+
);
|
|
1504
1562
|
process.exitCode = 2;
|
|
1505
1563
|
});
|
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
|
}
|