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.
Files changed (47) hide show
  1. package/CHANGELOG.md +47 -1
  2. package/README.md +16 -14
  3. package/bin/ark-check-runtime.mjs +15 -5
  4. package/bin/ark-mcp-runtime.mjs +82 -61
  5. package/bin/ark.mjs +2 -2
  6. package/bin/lib/agent-gates.mjs +2 -0
  7. package/bin/lib/agent-homes.mjs +296 -0
  8. package/bin/lib/agent-projection.mjs +1 -1
  9. package/bin/lib/ci-and-commands.mjs +9 -8
  10. package/bin/lib/design-smells.mjs +3 -7
  11. package/bin/lib/doctor-plan.mjs +36 -12
  12. package/bin/lib/gate-files.mjs +1 -1
  13. package/bin/lib/golden-pattern.mjs +1 -1
  14. package/bin/lib/hook-templates.mjs +13 -11
  15. package/bin/lib/host-support-matrix.mjs +32 -12
  16. package/bin/lib/html-report-depth.mjs +7 -8
  17. package/bin/lib/html-report.mjs +2 -1
  18. package/bin/lib/install-migrate.mjs +36 -0
  19. package/bin/lib/managed-upgrade.mjs +6 -1
  20. package/bin/lib/mcp-adoption.mjs +6 -1
  21. package/bin/lib/mcp-process-package.mjs +95 -0
  22. package/bin/lib/post-green-path.mjs +3 -2
  23. package/bin/lib/product-copy.mjs +32 -0
  24. package/bin/lib/skill-write.mjs +1 -1
  25. package/bin/lib/start-preview.mjs +5 -1
  26. package/bin/lib/upgrade-whats-new.mjs +16 -0
  27. package/bin/lib/write-path-capabilities.mjs +62 -1
  28. package/dist/index.cjs +19 -19
  29. package/dist/index.d.ts +1 -1
  30. package/dist/index.js +22 -22
  31. package/docs/README.md +3 -2
  32. package/docs/agent-guide.md +18 -13
  33. package/docs/ai-gates.md +43 -14
  34. package/docs/develop.md +3 -3
  35. package/docs/enthusiast/how-to-agent-gates.md +4 -3
  36. package/docs/package-surface.md +3 -3
  37. package/docs/product-voice.md +80 -74
  38. package/docs/use.md +7 -5
  39. package/package.json +2 -2
  40. package/server.json +3 -3
  41. package/templates/agent-skills/README.md +1 -1
  42. package/templates/agent-skills/ark-autopilot/SKILL.md +1 -1
  43. package/templates/agent-skills/ark-explore/SKILL.md +5 -5
  44. package/templates/agent-skills/ark-upgrade/SKILL.md +2 -1
  45. package/templates/skills/ark-autopilot.md +1 -1
  46. package/templates/skills/ark-explore.md +5 -5
  47. 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: prepared** (not on npm `latest` until publish train completes; see
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 contract. One gate. One co-pilot.**
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.5.5** is on npm `latest` deep-module coach, upgrade what’s new, session honesty.
20
- > [4.5.5 notes](docs/releases/4.5.5.md) · [4.5.0](docs/releases/4.5.0.md) · [4.4.0](docs/releases/4.4.0.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
19
+ > **ArkGate 4.6.0** is preparedclearer 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 an **improvement compass** (architecture lenses such as separation of concerns and
47
- dependency inversion — **not a score**). Residual lenses mean Shape work may remain even when edges
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** | Hard PreToolUse on supported hosts; advisory MCP elsewhere |
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 talk to whom — imports, placement, purity, isolation | `ark.config.json` layers + rules |
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 | **Advisory only** at write (no hard hook) | Advisory; the agent must call it | **Required GitHub status context** running `arkgate-check --strict-merge` (alias `ark-check`) | No hard-boundary payload |
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
- Cursor/Codex/OpenCode never get a fake hard write claim.
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.5 on npm `latest`) | [docs/releases/4.5.5.md](docs/releases/4.5.5.md) · [CHANGELOG](CHANGELOG.md) |
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 boundary for that',
326
- 'specific host. Cursor and Codex expose advisory MCP tools plus the shared CI check;',
327
- 'merge blocking requires repository policy to make that status required.',
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; Cursor/Codex advisory MCP; shared CI check for all) and',
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',
@@ -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 toolInput = payload?.tool_input ?? payload?.toolInput ?? {};
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
- operation: null,
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 } = normalizeHookPayload(
612
- payload,
613
- attemptContext?.grokHookEvent ?? Boolean(process.env.GROK_HOOK_EVENT)
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
- emitAntigravityAllow(output, antigravityStyle);
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
- emitAntigravityAllow(output, antigravityStyle);
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
- emitAntigravityAllow(output, antigravityStyle);
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
- emitAntigravityAllow(output, antigravityStyle);
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
- emitAntigravityAllow(output, antigravityStyle);
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
- emitAntigravityAllow(output, antigravityStyle);
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
- emitAntigravityAllow(output, antigravityStyle);
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
- emitAntigravityAllow(output, antigravityStyle);
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
- emitAntigravityAllow(output, antigravityStyle);
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 ? 'antigravity' : grokStyle ? 'grok' : 'claude'),
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
- : grokStyle
976
+ : cursorStyle
936
977
  ? toolName === 'Edit'
937
- ? 'search_replace'
938
- : 'write'
939
- : toolName),
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
- const processVersion = typeof ark.version === 'string' ? ark.version : null;
1878
- const projectInstalledVersion = readProjectInstalledArkgateVersion();
1879
- const mismatch =
1880
- processVersion != null &&
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 Grok. Cursor/Codex are
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.
@@ -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';