arkgate 4.5.6 → 4.6.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 +47 -1
- package/README.md +16 -14
- package/bin/ark-check-runtime.mjs +15 -5
- package/bin/ark-mcp-runtime.mjs +82 -61
- package/bin/ark.mjs +2 -2
- package/bin/lib/agent-gates.mjs +2 -0
- package/bin/lib/agent-homes.mjs +296 -0
- package/bin/lib/agent-projection.mjs +1 -1
- package/bin/lib/ci-and-commands.mjs +9 -8
- package/bin/lib/design-smells.mjs +3 -7
- package/bin/lib/doctor-plan.mjs +36 -12
- package/bin/lib/gate-files.mjs +1 -1
- package/bin/lib/golden-pattern.mjs +1 -1
- package/bin/lib/hook-templates.mjs +13 -11
- package/bin/lib/host-support-matrix.mjs +32 -12
- package/bin/lib/html-report-depth.mjs +7 -8
- package/bin/lib/html-report.mjs +2 -1
- package/bin/lib/install-migrate.mjs +36 -0
- package/bin/lib/managed-upgrade.mjs +6 -1
- package/bin/lib/mcp-adoption.mjs +6 -1
- package/bin/lib/mcp-process-package.mjs +95 -0
- package/bin/lib/post-green-path.mjs +3 -2
- package/bin/lib/product-copy.mjs +32 -0
- package/bin/lib/skill-write.mjs +1 -1
- package/bin/lib/start-preview.mjs +5 -1
- package/bin/lib/upgrade-whats-new.mjs +16 -0
- package/bin/lib/write-path-capabilities.mjs +62 -1
- package/dist/index.cjs +19 -19
- package/dist/index.d.ts +1 -1
- package/dist/index.js +22 -22
- package/docs/README.md +3 -2
- package/docs/agent-guide.md +18 -13
- package/docs/ai-gates.md +43 -14
- package/docs/develop.md +3 -3
- package/docs/enthusiast/how-to-agent-gates.md +4 -3
- package/docs/package-surface.md +3 -3
- package/docs/product-voice.md +80 -74
- package/docs/use.md +7 -5
- package/package.json +2 -2
- package/server.json +3 -3
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-autopilot/SKILL.md +1 -1
- package/templates/agent-skills/ark-explore/SKILL.md +5 -5
- package/templates/agent-skills/ark-upgrade/SKILL.md +2 -1
- package/templates/skills/ark-autopilot.md +1 -1
- package/templates/skills/ark-explore.md +5 -5
- package/templates/skills/ark-upgrade.md +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,52 @@ in the immutable pre-2.0 archive linked below.
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 4.6.0 — 2026-08-12
|
|
9
|
+
|
|
10
|
+
**Minor** over **4.5.7**. Understandable Ark: doctor, HTML, skills, and public docs use common
|
|
11
|
+
software words (import rules, leftover design work, pre-write block) while **ArkGate** and
|
|
12
|
+
**ArkRules** stay as product names. Shared Claude/Grok agent homes get the same monotonic
|
|
13
|
+
“always latest” floor Codex already had. **No required config migration.** JSON field names
|
|
14
|
+
and `ruleId`s stay stable. No new skill names, sensors, or scores.
|
|
15
|
+
|
|
16
|
+
**Status: prepared** (not yet on npm `latest`; see `docs/releases/4.6.0.md`).
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **Shared Claude/Grok home skills:** `--claude-home`, `--grok-home`, and `--agent-homes`
|
|
21
|
+
write monotonic home catalogs (Codex-parity lock + floor). Doctor reports stale
|
|
22
|
+
`~/.claude/skills` / `~/.grok/skills` when those catalogs exist. Temp/upgrade `--root`
|
|
23
|
+
never mutates default user homes.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **Human language:** doctor, HTML report, compact router, skills, and public lanes prefer
|
|
28
|
+
common terms. Leftover design work replaces “design-weak” in human copy; JSON `designWeak`
|
|
29
|
+
is unchanged.
|
|
30
|
+
|
|
31
|
+
## 4.5.7 — 2026-08-12
|
|
32
|
+
|
|
33
|
+
**Patch** over **4.5.6**. Cursor hard write path: project `.cursor/hooks.json` `preToolUse`
|
|
34
|
+
gate for `Write` / `StrReplace` (failClosed), host matrix + doctor honesty, payload mapping
|
|
35
|
+
(`contents` / `StrReplace`), install/merge without wiping sibling hooks. Repair envelope may
|
|
36
|
+
emit; Write `updated_input` reinjection is **not** claimed. Codex/OpenCode stay advisory at
|
|
37
|
+
write. Shell/Tab/human edits still rely on required CI. **No required config migration.**
|
|
38
|
+
|
|
39
|
+
**Status: published** (on npm `latest`; see `docs/releases/4.5.7.md`).
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- **Cursor hard write (CH01):** install writes `.cursor/hooks.json` with
|
|
44
|
+
`preToolUse` matcher `Write|StrReplace`, `failClosed: true`, `--hook --hook-repair`,
|
|
45
|
+
`--root-env CURSOR_PROJECT_DIR`. Merge preserves sibling Cursor hooks.
|
|
46
|
+
- **Hook runtime:** Cursor payload normalization (`contents` → `content`, `StrReplace` → Edit)
|
|
47
|
+
and stdout `{ permission: "deny", agent_message, user_message }` on block.
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
|
|
51
|
+
- Host support matrix: Cursor `hard-write: true` for listed ops; soft hosts are Codex/OpenCode.
|
|
52
|
+
- Docs: `docs/ai-gates.md`, agent guide, README matrix, CLI help.
|
|
53
|
+
|
|
8
54
|
## 4.5.6 — 2026-08-11
|
|
9
55
|
|
|
10
56
|
**Patch** over **4.5.5**. Field upgrade & multi-project MCP truth (Phase FX): registry-aware
|
|
@@ -12,7 +58,7 @@ package install, structured skip recovery, skill drift + opt-in refresh, post-up
|
|
|
12
58
|
MCP process package honesty, host selection notes, early whatsNew, inventory UX-copy quieting.
|
|
13
59
|
**No required config migration.** No new skill names, sensors, or scores. Freezes held.
|
|
14
60
|
|
|
15
|
-
**Status:
|
|
61
|
+
**Status: published** (on npm `latest`; see
|
|
16
62
|
`docs/releases/4.5.6.md`).
|
|
17
63
|
|
|
18
64
|
### Added
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# ArkGate — Architecture Co-pilot for AI TypeScript
|
|
4
4
|
|
|
5
|
-
**One
|
|
5
|
+
**One architecture config. One check. One coach.**
|
|
6
6
|
|
|
7
7
|
Your AI writes most of the code. ArkGate keeps that work inside an architecture you can trust —
|
|
8
8
|
and makes sure a “green” check means something real.
|
|
@@ -16,8 +16,8 @@ and makes sure a “green” check means something real.
|
|
|
16
16
|
|
|
17
17
|
</div>
|
|
18
18
|
|
|
19
|
-
> **ArkGate 4.
|
|
20
|
-
> [4.
|
|
19
|
+
> **ArkGate 4.6.0** is prepared — clearer language + shared agent home skills.
|
|
20
|
+
> [4.6.0 notes](docs/releases/4.6.0.md) · [4.5.7](docs/releases/4.5.7.md) (npm `latest`) · [4.5.6](docs/releases/4.5.6.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
@@ -43,9 +43,9 @@ npx arkgate-check --doctor # control plane: status light + primary next a
|
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
That is the product. Doctor is the control plane — when stuck, do **primary next action #1**.
|
|
46
|
-
Doctor also shows
|
|
47
|
-
dependency inversion — **not a score**).
|
|
48
|
-
are green. Details: [use.md — Improvement compass](docs/use.md#improvement-compass-not-a-score).
|
|
46
|
+
Doctor also shows **what to improve next** (architecture lenses such as separation of concerns and
|
|
47
|
+
dependency inversion — **not a score**). Leftover lenses mean design work may remain even when
|
|
48
|
+
imports are green. Details: [use.md — Improvement compass](docs/use.md#improvement-compass-not-a-score).
|
|
49
49
|
|
|
50
50
|
```text
|
|
51
51
|
start → doctor (+ compass) → day-to-day (place + gate)
|
|
@@ -64,15 +64,15 @@ A machine-readable architecture file (`ark.config.json`) plus enforcement:
|
|
|
64
64
|
|
|
65
65
|
| When | Tool |
|
|
66
66
|
|------|------|
|
|
67
|
-
| **While the AI writes** |
|
|
67
|
+
| **While the AI writes** | Pre-write block on supported hosts; warning only elsewhere |
|
|
68
68
|
| **Before merge** | `arkgate-check` as a **required** CI status |
|
|
69
69
|
|
|
70
70
|
### Two planes (4.0)
|
|
71
71
|
|
|
72
72
|
| Plane | What it guards | Config |
|
|
73
73
|
|-------|----------------|--------|
|
|
74
|
-
| **Layers** (always) | Who may
|
|
75
|
-
| **ArkRules** (opt-in) | Habits *inside* a layer — structure sensors + domain invariants as data | `arkRules` → `arkrules/<Layer>.json` |
|
|
74
|
+
| **Layers** (always) | Who may import whom — imports, placement, purity, isolation | `ark.config.json` layers + rules |
|
|
75
|
+
| **ArkRules** (opt-in; structure rules inside a layer) | Habits *inside* a layer — structure sensors + domain invariants as data | `arkRules` → `arkrules/<Layer>.json` |
|
|
76
76
|
|
|
77
77
|
Absence of ArkRules changes no inter-layer verdict. Label residual **`[Layer]`** vs **`[ArkRules]`**.
|
|
78
78
|
Details: [configuration](docs/configuration.md#arkrules-intra-layer-opt-in) · [use path](docs/use.md).
|
|
@@ -113,16 +113,16 @@ Details: [docs/use.md](docs/use.md).
|
|
|
113
113
|
| Claude Code | **Hard** block for listed ops (PreToolUse `Write` / `Edit` / `MultiEdit`) when installed + trusted | Advisory; the agent must call it | **Required GitHub status context** running `arkgate-check --strict-merge` (alias `ark-check`) | Emitted on hook deny; host must re-inject (hard path when installed + trusted) |
|
|
114
114
|
| Grok Build | **Hard** block for listed ops (PreToolUse `write` / `search_replace` (plus aliases)) when installed + trusted | Advisory; the agent must call it | **Required GitHub status context** running `arkgate-check --strict-merge` (alias `ark-check`) | Emitted on hook deny; host must re-inject (hard path when installed + trusted) |
|
|
115
115
|
| Google Antigravity | **Hard** block for listed ops (PreToolUse `write_to_file` / `replace_file_content` / `multi_replace_file_content`) when installed + trusted | Advisory; the agent must call it | **Required GitHub status context** running `arkgate-check --strict-merge` (alias `ark-check`) | Emitted on hook deny; host must re-inject (hard path when installed + trusted) |
|
|
116
|
-
| Cursor | **
|
|
117
|
-
| OpenAI Codex | **Advisory / best-effort** at write (not equivalent to Claude/Grok hard block) | Advisory; the agent must call it | **Required GitHub status context** running `arkgate-check --strict-merge` (alias `ark-check`) | Envelope may emit (`--hook-repair`); reinjection **not** guaranteed (advisory host) |
|
|
116
|
+
| Cursor | **Hard** block for listed ops (preToolUse `Write` / `StrReplace`) when installed + trusted | Advisory; the agent must call it | **Required GitHub status context** running `arkgate-check --strict-merge` (alias `ark-check`) | Envelope may emit (`--hook-repair`); reinjection **not** guaranteed (advisory host) |
|
|
117
|
+
| OpenAI Codex | **Advisory / best-effort** at write (not equivalent to Claude/Grok/Cursor hard block) | Advisory; the agent must call it | **Required GitHub status context** running `arkgate-check --strict-merge` (alias `ark-check`) | Envelope may emit (`--hook-repair`); reinjection **not** guaranteed (advisory host) |
|
|
118
118
|
| OpenCode | **Advisory / best-effort** at write (MCP + optional plugin; not a hard boundary) | Advisory; the agent must call it | **Required GitHub status context** running `arkgate-check --strict-merge` (alias `ark-check`) | No hard-boundary payload |
|
|
119
119
|
|
|
120
120
|
**Read the CI column:** for every host, the repository-wide hard guarantee is a **required**
|
|
121
121
|
GitHub **status context** that runs the CLI — not “CI file present,” and not the CLI binary name alone.
|
|
122
|
-
|
|
122
|
+
Codex/OpenCode never get a fake hard write claim. Cursor hard write covers only listed
|
|
123
|
+
`preToolUse` ops when `.cursor/hooks.json` is installed and trusted — Shell/Tab/human edits still rely on CI.
|
|
123
124
|
|
|
124
125
|
This table describes the supported profile **after its files are installed and the host loads/trusts them**. A hard local boundary covers only the listed hook operations; alternate tools, direct filesystem writes, and human edits still rely on CI. MCP validation is advisory because the agent must call it. The CI check blocks a merge only when the repository makes that status required. Repair **envelopes** may be emitted without reinjection being guaranteed; silent auto-apply never happens. Run `arkgate-check --doctor` (or `ark-check --doctor`) for the evidence actually detected in the current repository.
|
|
125
|
-
|
|
126
126
|
<!-- arkgate-host-support:end -->
|
|
127
127
|
|
|
128
128
|
#### Why the hard guarantee lives at the merge gate
|
|
@@ -175,6 +175,8 @@ npx arkgate-check --plan
|
|
|
175
175
|
npx arkgate-check --coverage
|
|
176
176
|
npx arkgate-check --strict-merge # CI / required status
|
|
177
177
|
npx arkgate-check --install-agent-gates --tools claude,cursor,codex,grok
|
|
178
|
+
# optional: refresh shared home skills (Claude/Grok/Codex; never downgrades)
|
|
179
|
+
# npx arkgate-check --install-agent-gates --skills-only --agent-homes --force
|
|
178
180
|
# optional: same 13 skills via Agent Skills ecosystem (no new names)
|
|
179
181
|
# npx skills add ./node_modules/arkgate/templates/agent-skills
|
|
180
182
|
```
|
|
@@ -208,7 +210,7 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
|
|
|
208
210
|
| Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
|
|
209
211
|
| Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
|
|
210
212
|
| Security | [SECURITY.md](SECURITY.md) |
|
|
211
|
-
| Current release (4.5.
|
|
213
|
+
| Current release (4.5.7 on npm `latest`) | [docs/releases/4.5.7.md](docs/releases/4.5.7.md) · [CHANGELOG](CHANGELOG.md) |
|
|
212
214
|
| Prior (4.5.0) | [docs/releases/4.5.0.md](docs/releases/4.5.0.md) |
|
|
213
215
|
| Prior (4.4.0) | [docs/releases/4.4.0.md](docs/releases/4.4.0.md) |
|
|
214
216
|
| Prior (4.3.0) | [docs/releases/4.3.0.md](docs/releases/4.3.0.md) |
|
|
@@ -213,6 +213,14 @@ function parseArgs(argv) {
|
|
|
213
213
|
else if (arg === '--watch') args.watch = true;
|
|
214
214
|
else if (arg === '--beginner') args.beginner = true;
|
|
215
215
|
else if (arg === '--codex-home') args.codexHome = true;
|
|
216
|
+
else if (arg === '--claude-home') args.claudeHome = true;
|
|
217
|
+
else if (arg === '--grok-home') args.grokHome = true;
|
|
218
|
+
else if (arg === '--agent-homes') {
|
|
219
|
+
args.agentHomes = true;
|
|
220
|
+
args.codexHome = true;
|
|
221
|
+
args.claudeHome = true;
|
|
222
|
+
args.grokHome = true;
|
|
223
|
+
}
|
|
216
224
|
else if (arg === '--migrate-commands') args.migrateCommands = true;
|
|
217
225
|
else if (arg === '--no-cache') args.noCache = true;
|
|
218
226
|
else if (arg === '--resident') args.resident = true;
|
|
@@ -275,7 +283,7 @@ function usage() {
|
|
|
275
283
|
' Best-effort open in browser (local TTY). No-op if open fails. --no-open / ARK_NO_OPEN_REPORT=1 to skip; --open forces open.',
|
|
276
284
|
' ark-check --init [--preset hexagonal|layered|feature-sliced|monorepo|ui-surface|vertical-slice|ddd-bounded-contexts|vite-vercel-spa|clean-architecture|onion-architecture] [--force] [--follow-config-root]',
|
|
277
285
|
' --follow-config-root On writes (init/install-agent-gates/migrate --write/…), adopt walked-up monorepo config root (default: keep explicit --root)',
|
|
278
|
-
' ark-check --install-agent-gates [--tools claude,cursor,codex,grok] [--require-write-hook <host>] [--skills-only] [--codex-home] [--force]',
|
|
286
|
+
' ark-check --install-agent-gates [--tools claude,cursor,codex,grok] [--require-write-hook <host>] [--skills-only] [--codex-home] [--claude-home] [--grok-home] [--agent-homes] [--force]',
|
|
279
287
|
' ark-check --update-baseline [file] freeze current violations (default .ark-baseline.json)',
|
|
280
288
|
' ark-check --print-config eleven-layer',
|
|
281
289
|
'',
|
|
@@ -322,9 +330,10 @@ function usage() {
|
|
|
322
330
|
'transition. Weakening or judgment-required findings fail unless --policy-ack names',
|
|
323
331
|
'every finding and is bound to both policy hashes. Use --policy-base/--policy-base-ref',
|
|
324
332
|
'for an explicit comparison; ARK_POLICY_BASE_REF is the CI environment equivalent.',
|
|
325
|
-
'Add --require-write-hook claude|grok to validate a hard local write
|
|
326
|
-
'specific host.
|
|
327
|
-
'merge blocking requires repository policy
|
|
333
|
+
'Add --require-write-hook claude|grok|antigravity|cursor to validate a hard local write',
|
|
334
|
+
'boundary for that specific host. Codex and OpenCode expose advisory MCP (plus best-effort',
|
|
335
|
+
'hooks where applicable) and the shared CI check; merge blocking requires repository policy',
|
|
336
|
+
'to make that status required.',
|
|
328
337
|
'',
|
|
329
338
|
'--require-gates implies --strict-config and fails when the Ark contract in AGENTS.md,',
|
|
330
339
|
'the project-rooted Ark server in .mcp.json, or fail-closed CI is missing/invalid.',
|
|
@@ -332,7 +341,8 @@ function usage() {
|
|
|
332
341
|
'',
|
|
333
342
|
'--install-agent-gates writes AGENTS.md, .mcp.json, and the CI workflow for every',
|
|
334
343
|
'project, plus tool-specific templates. Known tools: claude, cursor, codex, grok',
|
|
335
|
-
'(Claude/Grok hard-write hooks;
|
|
344
|
+
'(Claude/Grok/Antigravity/Cursor hard-write hooks when covered; Codex advisory MCP;',
|
|
345
|
+
'shared CI check for all) and',
|
|
336
346
|
'windsurf, cline, copilot, kiro, roo, continue, gemini',
|
|
337
347
|
'(instruction-tier rule files derived from the same contract).',
|
|
338
348
|
'It also installs the /ark-* skills shipped in templates/skills/ into each',
|
package/bin/ark-mcp-runtime.mjs
CHANGED
|
@@ -5,8 +5,11 @@ import path from 'node:path';
|
|
|
5
5
|
import readline from 'node:readline';
|
|
6
6
|
import { createHash, randomUUID } from 'node:crypto';
|
|
7
7
|
import { spawnSync } from 'node:child_process';
|
|
8
|
-
import { createRequire } from 'node:module';
|
|
9
8
|
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import {
|
|
10
|
+
buildProcessPackageHonesty,
|
|
11
|
+
readProjectInstalledArkgateVersion,
|
|
12
|
+
} from './lib/mcp-process-package.mjs';
|
|
10
13
|
import {
|
|
11
14
|
DEFAULT_INTENT_PREFIXES,
|
|
12
15
|
DEFAULT_LAYER_DIRECTORIES,
|
|
@@ -351,6 +354,8 @@ function mapAntigravityToolCall(toolCall) {
|
|
|
351
354
|
* Grok Build: { toolName, toolInput: { file_path, content | old_string/new_string } }
|
|
352
355
|
* (aliases Write/Edit/MultiEdit → write/search_replace; matcher keeps both)
|
|
353
356
|
* Antigravity: { toolCall: { name, args: { TargetFile, CodeContent, … } } }
|
|
357
|
+
* Cursor: { tool_name, tool_input, hook_event_name?, workspace_roots? }
|
|
358
|
+
* Write uses `contents`; StrReplace maps to Edit (path/old_string/new_string).
|
|
354
359
|
*/
|
|
355
360
|
function normalizeHookPayload(payload, grokHookEvent = Boolean(process.env.GROK_HOOK_EVENT)) {
|
|
356
361
|
const antigravityStyle =
|
|
@@ -367,17 +372,25 @@ function normalizeHookPayload(payload, grokHookEvent = Boolean(process.env.GROK_
|
|
|
367
372
|
toolInput: { ...(mapped?.toolInput ?? {}), file_path: filePath },
|
|
368
373
|
grokStyle: true, // decision JSON on stdout (deny)
|
|
369
374
|
antigravityStyle: true,
|
|
375
|
+
cursorStyle: false,
|
|
370
376
|
operation: mapped?.operation ?? mapped?.toolName ?? null,
|
|
371
377
|
};
|
|
372
378
|
}
|
|
373
379
|
|
|
374
380
|
const rawName = payload?.tool_name ?? payload?.toolName ?? '';
|
|
375
|
-
const
|
|
381
|
+
const toolInputRaw = payload?.tool_input ?? payload?.toolInput ?? {};
|
|
382
|
+
const toolInput =
|
|
383
|
+
toolInputRaw && typeof toolInputRaw === 'object' ? { ...toolInputRaw } : {};
|
|
384
|
+
// Cursor Write uses `contents`; Claude/Grok use `content`.
|
|
385
|
+
if (toolInput.content == null && typeof toolInput.contents === 'string') {
|
|
386
|
+
toolInput.content = toolInput.contents;
|
|
387
|
+
}
|
|
376
388
|
const nameMap = {
|
|
377
389
|
Write: 'Write',
|
|
378
390
|
write: 'Write',
|
|
379
391
|
Edit: 'Edit',
|
|
380
392
|
search_replace: 'Edit',
|
|
393
|
+
StrReplace: 'Edit',
|
|
381
394
|
MultiEdit: 'MultiEdit',
|
|
382
395
|
ApplyPatch: 'ApplyPatch',
|
|
383
396
|
apply_patch: 'ApplyPatch',
|
|
@@ -388,6 +401,15 @@ function normalizeHookPayload(payload, grokHookEvent = Boolean(process.env.GROK_
|
|
|
388
401
|
const toolName = nameMap[rawName] ?? rawName;
|
|
389
402
|
const filePath =
|
|
390
403
|
toolInput.file_path ?? toolInput.filePath ?? toolInput.path ?? toolInput.target_file;
|
|
404
|
+
const cursorStyle =
|
|
405
|
+
Boolean(process.env.CURSOR_PROJECT_DIR) ||
|
|
406
|
+
Boolean(process.env.CURSOR_VERSION) ||
|
|
407
|
+
(payload != null &&
|
|
408
|
+
typeof payload === 'object' &&
|
|
409
|
+
(payload.hook_event_name === 'preToolUse' ||
|
|
410
|
+
Array.isArray(payload.workspace_roots) ||
|
|
411
|
+
rawName === 'StrReplace' ||
|
|
412
|
+
(rawName === 'Write' && typeof toolInputRaw?.contents === 'string')));
|
|
391
413
|
return {
|
|
392
414
|
toolName,
|
|
393
415
|
toolInput: { ...toolInput, file_path: filePath },
|
|
@@ -396,7 +418,8 @@ function normalizeHookPayload(payload, grokHookEvent = Boolean(process.env.GROK_
|
|
|
396
418
|
grokHookEvent ||
|
|
397
419
|
(payload != null && typeof payload === 'object' && 'toolName' in payload),
|
|
398
420
|
antigravityStyle: false,
|
|
399
|
-
|
|
421
|
+
cursorStyle,
|
|
422
|
+
operation: rawName === 'StrReplace' ? 'StrReplace' : null,
|
|
400
423
|
};
|
|
401
424
|
}
|
|
402
425
|
|
|
@@ -551,7 +574,7 @@ function designDeltaViolations(delta) {
|
|
|
551
574
|
* generated code are inserted literally, never interpreted as replacement patterns.
|
|
552
575
|
*/
|
|
553
576
|
function proposedSource(toolName, toolInput) {
|
|
554
|
-
if (toolName === 'Write') return toolInput.content;
|
|
577
|
+
if (toolName === 'Write') return toolInput.content ?? toolInput.contents;
|
|
555
578
|
|
|
556
579
|
let text = '';
|
|
557
580
|
try {
|
|
@@ -607,18 +630,30 @@ function emitAntigravityAllow(output, antigravityStyle) {
|
|
|
607
630
|
output.stdout(`${JSON.stringify({ decision: 'allow' })}\n`);
|
|
608
631
|
}
|
|
609
632
|
|
|
633
|
+
/** Cursor preToolUse accepts explicit allow; exit 0 alone also works. */
|
|
634
|
+
function emitCursorAllow(output, cursorStyle) {
|
|
635
|
+
if (!cursorStyle) return;
|
|
636
|
+
output.stdout(`${JSON.stringify({ permission: 'allow' })}\n`);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function emitHostAllow(output, { antigravityStyle, cursorStyle }) {
|
|
640
|
+
emitAntigravityAllow(output, antigravityStyle);
|
|
641
|
+
emitCursorAllow(output, cursorStyle);
|
|
642
|
+
}
|
|
643
|
+
|
|
610
644
|
function runHookPayload(payload, gate, config, args, ts, attemptContext, output = processHookOutput()) {
|
|
611
|
-
const { toolName, toolInput, grokStyle, antigravityStyle, operation } =
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
645
|
+
const { toolName, toolInput, grokStyle, antigravityStyle, cursorStyle, operation } =
|
|
646
|
+
normalizeHookPayload(
|
|
647
|
+
payload,
|
|
648
|
+
attemptContext?.grokHookEvent ?? Boolean(process.env.GROK_HOOK_EVENT)
|
|
649
|
+
);
|
|
615
650
|
if (toolName === 'ApplyPatch') {
|
|
616
651
|
const patch = toolInput.patch ?? toolInput.input ?? toolInput.content;
|
|
617
652
|
const parsedPatch = codexPatchWrites(patch, args.root);
|
|
618
653
|
// Codex ApplyPatch is only preflighted when Ark can reconstruct every file operation.
|
|
619
654
|
// An incomplete reconstruction must not be mislabeled as atomic or hard enforcement.
|
|
620
655
|
if (!parsedPatch.complete) {
|
|
621
|
-
|
|
656
|
+
emitHostAllow(output, { antigravityStyle, cursorStyle });
|
|
622
657
|
return;
|
|
623
658
|
}
|
|
624
659
|
const patchWrites = parsedPatch.writes;
|
|
@@ -684,7 +719,7 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
684
719
|
);
|
|
685
720
|
}
|
|
686
721
|
if (changes.length === 0) {
|
|
687
|
-
|
|
722
|
+
emitHostAllow(output, { antigravityStyle, cursorStyle });
|
|
688
723
|
return;
|
|
689
724
|
}
|
|
690
725
|
result = prepareChangeFromRoot({
|
|
@@ -742,7 +777,7 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
742
777
|
? evaluateWriteDesignDelta({ root: args.root, config, changes, ts })
|
|
743
778
|
: null;
|
|
744
779
|
if (result.valid && (designDelta?.valid ?? true)) {
|
|
745
|
-
|
|
780
|
+
emitHostAllow(output, { antigravityStyle, cursorStyle });
|
|
746
781
|
return;
|
|
747
782
|
}
|
|
748
783
|
const message = [
|
|
@@ -776,23 +811,23 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
776
811
|
const filePath = toolInput.file_path;
|
|
777
812
|
if (!['Write', 'Edit', 'MultiEdit'].includes(toolName)) {
|
|
778
813
|
// Non-file tools: fail-open. Antigravity still needs an explicit allow decision.
|
|
779
|
-
|
|
814
|
+
emitHostAllow(output, { antigravityStyle, cursorStyle });
|
|
780
815
|
return;
|
|
781
816
|
}
|
|
782
817
|
if (typeof filePath !== 'string' || !SOURCE_FILE.test(filePath) || filePath.endsWith('.d.ts')) {
|
|
783
|
-
|
|
818
|
+
emitHostAllow(output, { antigravityStyle, cursorStyle });
|
|
784
819
|
return;
|
|
785
820
|
}
|
|
786
821
|
const rel = path.relative(args.root, path.resolve(filePath));
|
|
787
822
|
const segments = rel.split(path.sep);
|
|
788
823
|
if (segments[0] === '..' || segments.includes('node_modules')) {
|
|
789
|
-
|
|
824
|
+
emitHostAllow(output, { antigravityStyle, cursorStyle });
|
|
790
825
|
return;
|
|
791
826
|
}
|
|
792
827
|
|
|
793
828
|
const source = proposedSource(toolName, toolInput);
|
|
794
829
|
if (typeof source !== 'string') {
|
|
795
|
-
|
|
830
|
+
emitHostAllow(output, { antigravityStyle, cursorStyle });
|
|
796
831
|
return;
|
|
797
832
|
}
|
|
798
833
|
|
|
@@ -830,7 +865,7 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
830
865
|
})
|
|
831
866
|
: null;
|
|
832
867
|
if (result.valid && (designDelta?.valid ?? true)) {
|
|
833
|
-
|
|
868
|
+
emitHostAllow(output, { antigravityStyle, cursorStyle });
|
|
834
869
|
return;
|
|
835
870
|
}
|
|
836
871
|
|
|
@@ -861,7 +896,7 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
861
896
|
return false;
|
|
862
897
|
});
|
|
863
898
|
if (newViolations.length === 0 && (designDelta?.valid ?? true)) {
|
|
864
|
-
|
|
899
|
+
emitHostAllow(output, { antigravityStyle, cursorStyle });
|
|
865
900
|
return;
|
|
866
901
|
}
|
|
867
902
|
const combinedViolations = [...newViolations, ...designDeltaViolations(designDelta)];
|
|
@@ -923,7 +958,13 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
923
958
|
enforcement: hookEnforcement(
|
|
924
959
|
args.root,
|
|
925
960
|
attemptContext?.host ??
|
|
926
|
-
(antigravityStyle
|
|
961
|
+
(antigravityStyle
|
|
962
|
+
? 'antigravity'
|
|
963
|
+
: cursorStyle
|
|
964
|
+
? 'cursor'
|
|
965
|
+
: grokStyle
|
|
966
|
+
? 'grok'
|
|
967
|
+
: 'claude'),
|
|
927
968
|
attemptContext?.operation ??
|
|
928
969
|
operation ??
|
|
929
970
|
(antigravityStyle
|
|
@@ -932,11 +973,15 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
932
973
|
: toolName === 'MultiEdit'
|
|
933
974
|
? 'multi_replace_file_content'
|
|
934
975
|
: 'write_to_file'
|
|
935
|
-
:
|
|
976
|
+
: cursorStyle
|
|
936
977
|
? toolName === 'Edit'
|
|
937
|
-
? '
|
|
938
|
-
: '
|
|
939
|
-
:
|
|
978
|
+
? 'StrReplace'
|
|
979
|
+
: 'Write'
|
|
980
|
+
: grokStyle
|
|
981
|
+
? toolName === 'Edit'
|
|
982
|
+
? 'search_replace'
|
|
983
|
+
: 'write'
|
|
984
|
+
: toolName),
|
|
940
985
|
toolName === 'Write' || Boolean(attemptContext?.completePatch)
|
|
941
986
|
),
|
|
942
987
|
...(layer ? { layer } : {}),
|
|
@@ -968,6 +1013,16 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
968
1013
|
}) + '\n'
|
|
969
1014
|
);
|
|
970
1015
|
}
|
|
1016
|
+
// Cursor preToolUse: permission deny + agent_message (exit 2 also blocks).
|
|
1017
|
+
if (cursorStyle) {
|
|
1018
|
+
output.stdout(
|
|
1019
|
+
JSON.stringify({
|
|
1020
|
+
permission: 'deny',
|
|
1021
|
+
agent_message: message,
|
|
1022
|
+
user_message: `ArkGate blocked write to ${rel}`,
|
|
1023
|
+
}) + '\n'
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
971
1026
|
output.status(2);
|
|
972
1027
|
}
|
|
973
1028
|
|
|
@@ -1852,47 +1907,13 @@ export async function runArkMcp({ hookInput } = {}) {
|
|
|
1852
1907
|
* FX06 — process package vs project install honesty (multi-project field truth).
|
|
1853
1908
|
* Process arkgateVersion is startup-loaded; after consumer `npm i arkgate@newer`,
|
|
1854
1909
|
* long-lived MCP can report a stale process version until restart.
|
|
1910
|
+
* Algorithm lives in bin/lib/mcp-process-package.mjs for unit coverage.
|
|
1855
1911
|
*/
|
|
1856
|
-
function readProjectInstalledArkgateVersion() {
|
|
1857
|
-
try {
|
|
1858
|
-
const shallow = path.join(resolvedRoot, 'node_modules', 'arkgate', 'package.json');
|
|
1859
|
-
if (fs.existsSync(shallow)) {
|
|
1860
|
-
const v = JSON.parse(fs.readFileSync(shallow, 'utf8')).version;
|
|
1861
|
-
return typeof v === 'string' && v.trim() ? v.trim() : null;
|
|
1862
|
-
}
|
|
1863
|
-
} catch {
|
|
1864
|
-
/* fall through */
|
|
1865
|
-
}
|
|
1866
|
-
try {
|
|
1867
|
-
const requireFromProject = createRequire(path.join(resolvedRoot, 'package.json'));
|
|
1868
|
-
const pkgJson = requireFromProject.resolve('arkgate/package.json');
|
|
1869
|
-
const v = JSON.parse(fs.readFileSync(pkgJson, 'utf8')).version;
|
|
1870
|
-
return typeof v === 'string' && v.trim() ? v.trim() : null;
|
|
1871
|
-
} catch {
|
|
1872
|
-
return null;
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
1875
|
-
|
|
1876
1912
|
function processPackageHonesty() {
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
projectInstalledVersion != null &&
|
|
1882
|
-
processVersion !== projectInstalledVersion;
|
|
1883
|
-
return {
|
|
1884
|
-
schemaVersion: '1.0',
|
|
1885
|
-
notAScore: true,
|
|
1886
|
-
processArkgateVersion: processVersion,
|
|
1887
|
-
projectInstalledVersion,
|
|
1888
|
-
processPackageMismatch: mismatch,
|
|
1889
|
-
processStale: mismatch,
|
|
1890
|
-
nextAction: mismatch
|
|
1891
|
-
? 'Restart or retarget the Ark MCP server so process arkgateVersion matches the project install. Prefer project-local CLI (`npx arkgate` / `npx arkgate-check`) until identity is matched and versions align. Multi-checkout users: one expectedRoot per project; never reuse another checkout’s projectId.'
|
|
1892
|
-
: projectInstalledVersion == null
|
|
1893
|
-
? 'Project has no resolvable node_modules/arkgate; install the package or use CLI from a project that pins arkgate.'
|
|
1894
|
-
: 'Process package version matches project install for this MCP root.',
|
|
1895
|
-
};
|
|
1913
|
+
return buildProcessPackageHonesty({
|
|
1914
|
+
processVersion: typeof ark.version === 'string' ? ark.version : null,
|
|
1915
|
+
root: resolvedRoot,
|
|
1916
|
+
});
|
|
1896
1917
|
}
|
|
1897
1918
|
|
|
1898
1919
|
function contextFor(binding) {
|
package/bin/ark.mjs
CHANGED
|
@@ -207,8 +207,8 @@ Options:
|
|
|
207
207
|
--remove-host <host>
|
|
208
208
|
Preview or apply removal of that compact host integration; re-add it with --tools <host>.
|
|
209
209
|
--require-write-hook <host>
|
|
210
|
-
Require and verify a hard local write hook for Claude or
|
|
211
|
-
advisory-write plus hard CI merge only; impossible requests fail before any write.
|
|
210
|
+
Require and verify a hard local write hook for Claude, Grok, Antigravity, or Cursor.
|
|
211
|
+
Codex/OpenCode are advisory-write plus hard CI merge only; impossible requests fail before any write.
|
|
212
212
|
|
|
213
213
|
Interactive mode (TTY, no --yes): asks what application shape you are building and maps it to a preset.
|
|
214
214
|
Non-interactive (no TTY): uses the same defaults as --yes — never calls readline on a null interface.
|
package/bin/lib/agent-gates.mjs
CHANGED
|
@@ -26,9 +26,11 @@ export {
|
|
|
26
26
|
antigravityHooks,
|
|
27
27
|
claudeSettings,
|
|
28
28
|
codexProjectConfig,
|
|
29
|
+
cursorHooks,
|
|
29
30
|
grokHooks,
|
|
30
31
|
grokProjectConfig,
|
|
31
32
|
mergeAntigravityArkHook,
|
|
33
|
+
mergeCursorArkHook,
|
|
32
34
|
mergeOpencodeArkMcp,
|
|
33
35
|
opencodeProjectConfig,
|
|
34
36
|
} from './hook-templates.mjs';
|