arkgate 4.5.5 → 4.5.7
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 +68 -2
- package/README.md +8 -9
- package/bin/ark-check-runtime.mjs +6 -4
- package/bin/ark-mcp-runtime.mjs +91 -21
- package/bin/ark-shared.mjs +8 -29
- package/bin/ark.mjs +9 -5
- package/bin/lib/agent-gates.mjs +2 -0
- package/bin/lib/ci-and-commands.mjs +2 -1
- package/bin/lib/gate-files.mjs +1 -1
- package/bin/lib/hook-templates.mjs +13 -11
- package/bin/lib/host-support-matrix.mjs +31 -11
- package/bin/lib/install-migrate.mjs +24 -0
- package/bin/lib/managed-upgrade.mjs +204 -2
- package/bin/lib/mcp-adoption.mjs +6 -1
- package/bin/lib/mcp-process-package.mjs +95 -0
- package/bin/lib/rules-inventory.mjs +23 -0
- package/bin/lib/start-preview.mjs +5 -1
- package/bin/lib/upgrade-command.mjs +132 -11
- package/bin/lib/upgrade-package-decision.mjs +241 -0
- package/bin/lib/upgrade-whats-new.mjs +25 -0
- package/bin/lib/write-path-capabilities.mjs +62 -1
- package/dist/index.cjs +21 -21
- package/dist/index.d.ts +1 -1
- package/dist/index.js +19 -19
- package/docs/README.md +2 -3
- package/docs/agent-guide.md +29 -6
- package/docs/ai-gates.md +24 -14
- package/docs/enthusiast/how-to-agent-gates.md +4 -3
- package/docs/package-surface.md +5 -3
- package/package.json +1 -1
- package/server.json +2 -2
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-upgrade/SKILL.md +48 -16
- package/templates/skills/ark-upgrade.md +48 -16
package/CHANGELOG.md
CHANGED
|
@@ -5,14 +5,80 @@ in the immutable pre-2.0 archive linked below.
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 4.5.7 — 2026-08-12
|
|
9
|
+
|
|
10
|
+
**Patch** over **4.5.6**. Cursor hard write path: project `.cursor/hooks.json` `preToolUse`
|
|
11
|
+
gate for `Write` / `StrReplace` (failClosed), host matrix + doctor honesty, payload mapping
|
|
12
|
+
(`contents` / `StrReplace`), install/merge without wiping sibling hooks. Repair envelope may
|
|
13
|
+
emit; Write `updated_input` reinjection is **not** claimed. Codex/OpenCode stay advisory at
|
|
14
|
+
write. Shell/Tab/human edits still rely on required CI. **No required config migration.**
|
|
15
|
+
|
|
16
|
+
**Status: prepared** (not published).
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **Cursor hard write (CH01):** install writes `.cursor/hooks.json` with
|
|
21
|
+
`preToolUse` matcher `Write|StrReplace`, `failClosed: true`, `--hook --hook-repair`,
|
|
22
|
+
`--root-env CURSOR_PROJECT_DIR`. Merge preserves sibling Cursor hooks.
|
|
23
|
+
- **Hook runtime:** Cursor payload normalization (`contents` → `content`, `StrReplace` → Edit)
|
|
24
|
+
and stdout `{ permission: "deny", agent_message, user_message }` on block.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- Host support matrix: Cursor `hard-write: true` for listed ops; soft hosts are Codex/OpenCode.
|
|
29
|
+
- Docs: `docs/ai-gates.md`, agent guide, README matrix, CLI help.
|
|
30
|
+
|
|
31
|
+
## 4.5.6 — 2026-08-11
|
|
32
|
+
|
|
33
|
+
**Patch** over **4.5.5**. Field upgrade & multi-project MCP truth (Phase FX): registry-aware
|
|
34
|
+
package install, structured skip recovery, skill drift + opt-in refresh, post-upgrade checks,
|
|
35
|
+
MCP process package honesty, host selection notes, early whatsNew, inventory UX-copy quieting.
|
|
36
|
+
**No required config migration.** No new skill names, sensors, or scores. Freezes held.
|
|
37
|
+
|
|
38
|
+
**Status: published** (on npm `latest`; see
|
|
39
|
+
`docs/releases/4.5.6.md`).
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- **Registry-aware upgrade (FX01–FX02):** `ark upgrade --apply` no longer skips package install
|
|
44
|
+
solely because CLI version equals `node_modules/arkgate`. When the registry (injectable for
|
|
45
|
+
tests) is ahead, install runs. Skip paths emit structured JSON (`reasonCode`,
|
|
46
|
+
`installedVersion`, `cliVersion`, `registryLatest`, `suggestedInstallCmd`) plus human
|
|
47
|
+
recovery. Offline / registry-unknown stays honest (`REGISTRY_UNAVAILABLE`) without inventing
|
|
48
|
+
a bump.
|
|
49
|
+
- **Skill content drift honesty (FX03):** upgrade preview/JSON `skillDrift` counts
|
|
50
|
+
current/stale/customized/missing skills with sample paths and preserve notes.
|
|
51
|
+
- **Opt-in skill refresh (FX04):** `--refresh-skills` rewrites customized managed *skills* to
|
|
52
|
+
package templates with explicit consent. Conflicted assets still need `--accept-conflicts`.
|
|
53
|
+
Never silent overwrite of true user edits by default.
|
|
54
|
+
- **Post-upgrade verification block (FX05):** after apply, advisory `postUpgradeChecks` (pin↔CLI,
|
|
55
|
+
architecture verification, dual-truth, doctor/status/MCP notes). Always `notAScore`.
|
|
56
|
+
- **MCP multi-project process honesty (FX06):** every MCP tool context includes
|
|
57
|
+
`processPackage` (`processArkgateVersion`, `projectInstalledVersion`,
|
|
58
|
+
`processPackageMismatch` / `processStale`, `nextAction`). Prefer project-local CLI until
|
|
59
|
+
identity matched and versions align; multi-checkout recipe in agent-guide + `/ark-upgrade`.
|
|
60
|
+
- **Host selection honesty (FX07):** upgrade notes when detected active host ∉ managed
|
|
61
|
+
`--tools`/manifest hosts and suggests `--tools` expansion.
|
|
62
|
+
- **whatsNew early path (FX08):** suggested improvements on preview even when nothing to apply;
|
|
63
|
+
includes FX field-truth try/inspect items.
|
|
64
|
+
- **Inventory UX message quieting (FX09):** rules inventory downranks pure UX/error-message
|
|
65
|
+
string constants so they do not crowd business-rule pilots (no new sensors).
|
|
66
|
+
|
|
67
|
+
### Changed
|
|
68
|
+
|
|
69
|
+
- `/ark-upgrade` skill + upgrade help document registry-aware install, `--refresh-skills`,
|
|
70
|
+
skill drift, post-upgrade checks, and multi-project MCP restart guidance.
|
|
71
|
+
|
|
72
|
+
**Freezes held:** no principle scores/ranks; no new skill names; no silent customized overwrite;
|
|
73
|
+
no silent multi-project MCP retarget; soft hosts stay advisory; no fake published claims.
|
|
74
|
+
|
|
8
75
|
## 4.5.5 — 2026-08-11
|
|
9
76
|
|
|
10
77
|
**Patch** over **4.5.0**. Deep-module coach train plus upgrade **suggested improvements /
|
|
11
78
|
what’s new** so consumers know what to try after install. **No required config migration.**
|
|
12
79
|
No new skill names, sensors, or scores. Freezes held.
|
|
13
80
|
|
|
14
|
-
**Status:
|
|
15
|
-
the publication checklist completes).
|
|
81
|
+
**Status: published** (`arkgate@4.5.5` on npm `latest`; see `docs/releases/4.5.5.md`).
|
|
16
82
|
|
|
17
83
|
### Added
|
|
18
84
|
|
package/README.md
CHANGED
|
@@ -16,9 +16,8 @@ and makes sure a “green” check means something real.
|
|
|
16
16
|
|
|
17
17
|
</div>
|
|
18
18
|
|
|
19
|
-
> **ArkGate 4.5.
|
|
20
|
-
>
|
|
21
|
-
> [4.4.0](docs/releases/4.4.0.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
|
|
19
|
+
> **ArkGate 4.5.6** is on npm `latest` — field upgrade truth, multi-project MCP honesty, skill drift.
|
|
20
|
+
> [4.5.6 notes](docs/releases/4.5.6.md) · [4.5.5](docs/releases/4.5.5.md) · [4.5.0](docs/releases/4.5.0.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
|
|
22
21
|
|
|
23
22
|
---
|
|
24
23
|
|
|
@@ -114,16 +113,16 @@ Details: [docs/use.md](docs/use.md).
|
|
|
114
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) |
|
|
115
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) |
|
|
116
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) |
|
|
117
|
-
| Cursor | **
|
|
118
|
-
| 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) |
|
|
119
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 |
|
|
120
119
|
|
|
121
120
|
**Read the CI column:** for every host, the repository-wide hard guarantee is a **required**
|
|
122
121
|
GitHub **status context** that runs the CLI — not “CI file present,” and not the CLI binary name alone.
|
|
123
|
-
|
|
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.
|
|
124
124
|
|
|
125
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.
|
|
126
|
-
|
|
127
126
|
<!-- arkgate-host-support:end -->
|
|
128
127
|
|
|
129
128
|
#### Why the hard guarantee lives at the merge gate
|
|
@@ -209,8 +208,8 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
|
|
|
209
208
|
| Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
|
|
210
209
|
| Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
|
|
211
210
|
| Security | [SECURITY.md](SECURITY.md) |
|
|
212
|
-
|
|
|
213
|
-
|
|
|
211
|
+
| Current release (4.5.6 on npm `latest`) | [docs/releases/4.5.6.md](docs/releases/4.5.6.md) · [CHANGELOG](CHANGELOG.md) |
|
|
212
|
+
| Prior (4.5.0) | [docs/releases/4.5.0.md](docs/releases/4.5.0.md) |
|
|
214
213
|
| Prior (4.4.0) | [docs/releases/4.4.0.md](docs/releases/4.4.0.md) |
|
|
215
214
|
| Prior (4.3.0) | [docs/releases/4.3.0.md](docs/releases/4.3.0.md) |
|
|
216
215
|
| Prior (4.2.1) | [docs/releases/4.2.1.md](docs/releases/4.2.1.md) |
|
|
@@ -322,9 +322,10 @@ function usage() {
|
|
|
322
322
|
'transition. Weakening or judgment-required findings fail unless --policy-ack names',
|
|
323
323
|
'every finding and is bound to both policy hashes. Use --policy-base/--policy-base-ref',
|
|
324
324
|
'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
|
|
325
|
+
'Add --require-write-hook claude|grok|antigravity|cursor to validate a hard local write',
|
|
326
|
+
'boundary for that specific host. Codex and OpenCode expose advisory MCP (plus best-effort',
|
|
327
|
+
'hooks where applicable) and the shared CI check; merge blocking requires repository policy',
|
|
328
|
+
'to make that status required.',
|
|
328
329
|
'',
|
|
329
330
|
'--require-gates implies --strict-config and fails when the Ark contract in AGENTS.md,',
|
|
330
331
|
'the project-rooted Ark server in .mcp.json, or fail-closed CI is missing/invalid.',
|
|
@@ -332,7 +333,8 @@ function usage() {
|
|
|
332
333
|
'',
|
|
333
334
|
'--install-agent-gates writes AGENTS.md, .mcp.json, and the CI workflow for every',
|
|
334
335
|
'project, plus tool-specific templates. Known tools: claude, cursor, codex, grok',
|
|
335
|
-
'(Claude/Grok hard-write hooks;
|
|
336
|
+
'(Claude/Grok/Antigravity/Cursor hard-write hooks when covered; Codex advisory MCP;',
|
|
337
|
+
'shared CI check for all) and',
|
|
336
338
|
'windsurf, cline, copilot, kiro, roo, continue, gemini',
|
|
337
339
|
'(instruction-tier rule files derived from the same contract).',
|
|
338
340
|
'It also installs the /ark-* skills shipped in templates/skills/ into each',
|
package/bin/ark-mcp-runtime.mjs
CHANGED
|
@@ -6,6 +6,10 @@ import readline from 'node:readline';
|
|
|
6
6
|
import { createHash, randomUUID } from 'node:crypto';
|
|
7
7
|
import { spawnSync } from 'node:child_process';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import {
|
|
10
|
+
buildProcessPackageHonesty,
|
|
11
|
+
readProjectInstalledArkgateVersion,
|
|
12
|
+
} from './lib/mcp-process-package.mjs';
|
|
9
13
|
import {
|
|
10
14
|
DEFAULT_INTENT_PREFIXES,
|
|
11
15
|
DEFAULT_LAYER_DIRECTORIES,
|
|
@@ -350,6 +354,8 @@ function mapAntigravityToolCall(toolCall) {
|
|
|
350
354
|
* Grok Build: { toolName, toolInput: { file_path, content | old_string/new_string } }
|
|
351
355
|
* (aliases Write/Edit/MultiEdit → write/search_replace; matcher keeps both)
|
|
352
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).
|
|
353
359
|
*/
|
|
354
360
|
function normalizeHookPayload(payload, grokHookEvent = Boolean(process.env.GROK_HOOK_EVENT)) {
|
|
355
361
|
const antigravityStyle =
|
|
@@ -366,17 +372,25 @@ function normalizeHookPayload(payload, grokHookEvent = Boolean(process.env.GROK_
|
|
|
366
372
|
toolInput: { ...(mapped?.toolInput ?? {}), file_path: filePath },
|
|
367
373
|
grokStyle: true, // decision JSON on stdout (deny)
|
|
368
374
|
antigravityStyle: true,
|
|
375
|
+
cursorStyle: false,
|
|
369
376
|
operation: mapped?.operation ?? mapped?.toolName ?? null,
|
|
370
377
|
};
|
|
371
378
|
}
|
|
372
379
|
|
|
373
380
|
const rawName = payload?.tool_name ?? payload?.toolName ?? '';
|
|
374
|
-
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
|
+
}
|
|
375
388
|
const nameMap = {
|
|
376
389
|
Write: 'Write',
|
|
377
390
|
write: 'Write',
|
|
378
391
|
Edit: 'Edit',
|
|
379
392
|
search_replace: 'Edit',
|
|
393
|
+
StrReplace: 'Edit',
|
|
380
394
|
MultiEdit: 'MultiEdit',
|
|
381
395
|
ApplyPatch: 'ApplyPatch',
|
|
382
396
|
apply_patch: 'ApplyPatch',
|
|
@@ -387,6 +401,15 @@ function normalizeHookPayload(payload, grokHookEvent = Boolean(process.env.GROK_
|
|
|
387
401
|
const toolName = nameMap[rawName] ?? rawName;
|
|
388
402
|
const filePath =
|
|
389
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')));
|
|
390
413
|
return {
|
|
391
414
|
toolName,
|
|
392
415
|
toolInput: { ...toolInput, file_path: filePath },
|
|
@@ -395,7 +418,8 @@ function normalizeHookPayload(payload, grokHookEvent = Boolean(process.env.GROK_
|
|
|
395
418
|
grokHookEvent ||
|
|
396
419
|
(payload != null && typeof payload === 'object' && 'toolName' in payload),
|
|
397
420
|
antigravityStyle: false,
|
|
398
|
-
|
|
421
|
+
cursorStyle,
|
|
422
|
+
operation: rawName === 'StrReplace' ? 'StrReplace' : null,
|
|
399
423
|
};
|
|
400
424
|
}
|
|
401
425
|
|
|
@@ -550,7 +574,7 @@ function designDeltaViolations(delta) {
|
|
|
550
574
|
* generated code are inserted literally, never interpreted as replacement patterns.
|
|
551
575
|
*/
|
|
552
576
|
function proposedSource(toolName, toolInput) {
|
|
553
|
-
if (toolName === 'Write') return toolInput.content;
|
|
577
|
+
if (toolName === 'Write') return toolInput.content ?? toolInput.contents;
|
|
554
578
|
|
|
555
579
|
let text = '';
|
|
556
580
|
try {
|
|
@@ -606,18 +630,30 @@ function emitAntigravityAllow(output, antigravityStyle) {
|
|
|
606
630
|
output.stdout(`${JSON.stringify({ decision: 'allow' })}\n`);
|
|
607
631
|
}
|
|
608
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
|
+
|
|
609
644
|
function runHookPayload(payload, gate, config, args, ts, attemptContext, output = processHookOutput()) {
|
|
610
|
-
const { toolName, toolInput, grokStyle, antigravityStyle, operation } =
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
645
|
+
const { toolName, toolInput, grokStyle, antigravityStyle, cursorStyle, operation } =
|
|
646
|
+
normalizeHookPayload(
|
|
647
|
+
payload,
|
|
648
|
+
attemptContext?.grokHookEvent ?? Boolean(process.env.GROK_HOOK_EVENT)
|
|
649
|
+
);
|
|
614
650
|
if (toolName === 'ApplyPatch') {
|
|
615
651
|
const patch = toolInput.patch ?? toolInput.input ?? toolInput.content;
|
|
616
652
|
const parsedPatch = codexPatchWrites(patch, args.root);
|
|
617
653
|
// Codex ApplyPatch is only preflighted when Ark can reconstruct every file operation.
|
|
618
654
|
// An incomplete reconstruction must not be mislabeled as atomic or hard enforcement.
|
|
619
655
|
if (!parsedPatch.complete) {
|
|
620
|
-
|
|
656
|
+
emitHostAllow(output, { antigravityStyle, cursorStyle });
|
|
621
657
|
return;
|
|
622
658
|
}
|
|
623
659
|
const patchWrites = parsedPatch.writes;
|
|
@@ -683,7 +719,7 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
683
719
|
);
|
|
684
720
|
}
|
|
685
721
|
if (changes.length === 0) {
|
|
686
|
-
|
|
722
|
+
emitHostAllow(output, { antigravityStyle, cursorStyle });
|
|
687
723
|
return;
|
|
688
724
|
}
|
|
689
725
|
result = prepareChangeFromRoot({
|
|
@@ -741,7 +777,7 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
741
777
|
? evaluateWriteDesignDelta({ root: args.root, config, changes, ts })
|
|
742
778
|
: null;
|
|
743
779
|
if (result.valid && (designDelta?.valid ?? true)) {
|
|
744
|
-
|
|
780
|
+
emitHostAllow(output, { antigravityStyle, cursorStyle });
|
|
745
781
|
return;
|
|
746
782
|
}
|
|
747
783
|
const message = [
|
|
@@ -775,23 +811,23 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
775
811
|
const filePath = toolInput.file_path;
|
|
776
812
|
if (!['Write', 'Edit', 'MultiEdit'].includes(toolName)) {
|
|
777
813
|
// Non-file tools: fail-open. Antigravity still needs an explicit allow decision.
|
|
778
|
-
|
|
814
|
+
emitHostAllow(output, { antigravityStyle, cursorStyle });
|
|
779
815
|
return;
|
|
780
816
|
}
|
|
781
817
|
if (typeof filePath !== 'string' || !SOURCE_FILE.test(filePath) || filePath.endsWith('.d.ts')) {
|
|
782
|
-
|
|
818
|
+
emitHostAllow(output, { antigravityStyle, cursorStyle });
|
|
783
819
|
return;
|
|
784
820
|
}
|
|
785
821
|
const rel = path.relative(args.root, path.resolve(filePath));
|
|
786
822
|
const segments = rel.split(path.sep);
|
|
787
823
|
if (segments[0] === '..' || segments.includes('node_modules')) {
|
|
788
|
-
|
|
824
|
+
emitHostAllow(output, { antigravityStyle, cursorStyle });
|
|
789
825
|
return;
|
|
790
826
|
}
|
|
791
827
|
|
|
792
828
|
const source = proposedSource(toolName, toolInput);
|
|
793
829
|
if (typeof source !== 'string') {
|
|
794
|
-
|
|
830
|
+
emitHostAllow(output, { antigravityStyle, cursorStyle });
|
|
795
831
|
return;
|
|
796
832
|
}
|
|
797
833
|
|
|
@@ -829,7 +865,7 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
829
865
|
})
|
|
830
866
|
: null;
|
|
831
867
|
if (result.valid && (designDelta?.valid ?? true)) {
|
|
832
|
-
|
|
868
|
+
emitHostAllow(output, { antigravityStyle, cursorStyle });
|
|
833
869
|
return;
|
|
834
870
|
}
|
|
835
871
|
|
|
@@ -860,7 +896,7 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
860
896
|
return false;
|
|
861
897
|
});
|
|
862
898
|
if (newViolations.length === 0 && (designDelta?.valid ?? true)) {
|
|
863
|
-
|
|
899
|
+
emitHostAllow(output, { antigravityStyle, cursorStyle });
|
|
864
900
|
return;
|
|
865
901
|
}
|
|
866
902
|
const combinedViolations = [...newViolations, ...designDeltaViolations(designDelta)];
|
|
@@ -922,7 +958,13 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
922
958
|
enforcement: hookEnforcement(
|
|
923
959
|
args.root,
|
|
924
960
|
attemptContext?.host ??
|
|
925
|
-
(antigravityStyle
|
|
961
|
+
(antigravityStyle
|
|
962
|
+
? 'antigravity'
|
|
963
|
+
: cursorStyle
|
|
964
|
+
? 'cursor'
|
|
965
|
+
: grokStyle
|
|
966
|
+
? 'grok'
|
|
967
|
+
: 'claude'),
|
|
926
968
|
attemptContext?.operation ??
|
|
927
969
|
operation ??
|
|
928
970
|
(antigravityStyle
|
|
@@ -931,11 +973,15 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
931
973
|
: toolName === 'MultiEdit'
|
|
932
974
|
? 'multi_replace_file_content'
|
|
933
975
|
: 'write_to_file'
|
|
934
|
-
:
|
|
976
|
+
: cursorStyle
|
|
935
977
|
? toolName === 'Edit'
|
|
936
|
-
? '
|
|
937
|
-
: '
|
|
938
|
-
:
|
|
978
|
+
? 'StrReplace'
|
|
979
|
+
: 'Write'
|
|
980
|
+
: grokStyle
|
|
981
|
+
? toolName === 'Edit'
|
|
982
|
+
? 'search_replace'
|
|
983
|
+
: 'write'
|
|
984
|
+
: toolName),
|
|
939
985
|
toolName === 'Write' || Boolean(attemptContext?.completePatch)
|
|
940
986
|
),
|
|
941
987
|
...(layer ? { layer } : {}),
|
|
@@ -967,6 +1013,16 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
967
1013
|
}) + '\n'
|
|
968
1014
|
);
|
|
969
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
|
+
}
|
|
970
1026
|
output.status(2);
|
|
971
1027
|
}
|
|
972
1028
|
|
|
@@ -1847,11 +1903,25 @@ export async function runArkMcp({ hookInput } = {}) {
|
|
|
1847
1903
|
return currentBinding;
|
|
1848
1904
|
}
|
|
1849
1905
|
|
|
1906
|
+
/**
|
|
1907
|
+
* FX06 — process package vs project install honesty (multi-project field truth).
|
|
1908
|
+
* Process arkgateVersion is startup-loaded; after consumer `npm i arkgate@newer`,
|
|
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.
|
|
1911
|
+
*/
|
|
1912
|
+
function processPackageHonesty() {
|
|
1913
|
+
return buildProcessPackageHonesty({
|
|
1914
|
+
processVersion: typeof ark.version === 'string' ? ark.version : null,
|
|
1915
|
+
root: resolvedRoot,
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1850
1919
|
function contextFor(binding) {
|
|
1851
1920
|
return {
|
|
1852
1921
|
projectIdentity,
|
|
1853
1922
|
binding,
|
|
1854
1923
|
authoritative: binding.authoritative,
|
|
1924
|
+
processPackage: processPackageHonesty(),
|
|
1855
1925
|
};
|
|
1856
1926
|
}
|
|
1857
1927
|
|
package/bin/ark-shared.mjs
CHANGED
|
@@ -722,35 +722,14 @@ export function packageInstallArgv(root, versionSpec = 'latest') {
|
|
|
722
722
|
return ['npm', ['install', '-D', pkgSpec]];
|
|
723
723
|
}
|
|
724
724
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
export function shouldSkipArkgateInstall(root, cliVersion) {
|
|
734
|
-
const pkgPath = path.join(root, 'node_modules', 'arkgate', 'package.json');
|
|
735
|
-
if (!fs.existsSync(pkgPath)) {
|
|
736
|
-
return { skip: false, installedVersion: null, reason: 'not-installed' };
|
|
737
|
-
}
|
|
738
|
-
let installedVersion = null;
|
|
739
|
-
try {
|
|
740
|
-
installedVersion = JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version ?? null;
|
|
741
|
-
} catch {
|
|
742
|
-
return { skip: false, installedVersion: null, reason: 'unreadable' };
|
|
743
|
-
}
|
|
744
|
-
if (
|
|
745
|
-
typeof cliVersion === 'string' &&
|
|
746
|
-
cliVersion &&
|
|
747
|
-
installedVersion &&
|
|
748
|
-
installedVersion === cliVersion
|
|
749
|
-
) {
|
|
750
|
-
return { skip: true, installedVersion, reason: 'already-current' };
|
|
751
|
-
}
|
|
752
|
-
return { skip: false, installedVersion, reason: 'version-differs' };
|
|
753
|
-
}
|
|
725
|
+
// FX01–FX02: registry-aware skip lives in upgrade-package-decision (injectable probe).
|
|
726
|
+
export {
|
|
727
|
+
shouldSkipArkgateInstall,
|
|
728
|
+
buildPackageInstallSkipPayload,
|
|
729
|
+
formatPackageInstallDecisionHuman,
|
|
730
|
+
compareSemverCore,
|
|
731
|
+
probeRegistryArkgateLatest,
|
|
732
|
+
} from './lib/upgrade-package-decision.mjs';
|
|
754
733
|
|
|
755
734
|
/** Package-manager aware "install a dev dependency" hint (e.g. for a missing typescript). */
|
|
756
735
|
export function installDevHint(root, pkg) {
|
package/bin/ark.mjs
CHANGED
|
@@ -77,6 +77,7 @@ function parseArgs(argv) {
|
|
|
77
77
|
installExplicit: false,
|
|
78
78
|
apply: false,
|
|
79
79
|
acceptConflicts: false,
|
|
80
|
+
refreshSkills: false,
|
|
80
81
|
planDigest: undefined,
|
|
81
82
|
json: false,
|
|
82
83
|
internalApply: false,
|
|
@@ -118,6 +119,7 @@ function parseArgs(argv) {
|
|
|
118
119
|
}
|
|
119
120
|
else if (arg === '--apply') args.apply = true;
|
|
120
121
|
else if (arg === '--accept-conflicts') args.acceptConflicts = true;
|
|
122
|
+
else if (arg === '--refresh-skills') args.refreshSkills = true;
|
|
121
123
|
else if (arg === '--plan-digest') args.planDigest = requireValue(arg, i++);
|
|
122
124
|
else if (arg === '--json') args.json = true;
|
|
123
125
|
else if (arg === '--internal-apply') args.internalApply = true;
|
|
@@ -153,7 +155,7 @@ function usage() {
|
|
|
153
155
|
ark start [--root <project>] [--tools <host>] [--require-write-hook <host>] [--install] [--apply] [--json]
|
|
154
156
|
ark init [--root <project>] [--preset hexagonal|layered|feature-sliced|monorepo|ui-surface|vertical-slice|ddd-bounded-contexts|clean-architecture|onion-architecture]
|
|
155
157
|
[--archetype <playbook-id>] [--tools <list>] [--require-write-hook <host>] [--yes] [--force] [--no-strict]
|
|
156
|
-
ark upgrade [--root <project>] [--tools <list>] [--apply] [--plan-digest <sha256>] [--accept-conflicts] [--json] [--no-install] [--no-strict]
|
|
158
|
+
ark upgrade [--root <project>] [--tools <list>] [--apply] [--plan-digest <sha256>] [--accept-conflicts] [--refresh-skills] [--json] [--no-install] [--no-strict]
|
|
157
159
|
ark preflight --changes <change-set.json> [--change-map <map.json>] [--root <project>] [--config ark.config.json] [--manifest <manifest.json>] [--tsconfig <tsconfig.json>] [--json]
|
|
158
160
|
ark status [--root <project>] [--config ark.config.json] [--json]
|
|
159
161
|
[--expected-root <abs>] [--expected-project-id sha256:…] [--tools <host>]
|
|
@@ -164,8 +166,10 @@ Commands:
|
|
|
164
166
|
start New here? Analyze and preview the complete setup. Read-only unless --apply.
|
|
165
167
|
init Configure Ark project enforcement with explicit prompts.
|
|
166
168
|
upgrade Preview identity-proven Ark-managed asset updates. With package install,
|
|
167
|
-
--apply bumps
|
|
168
|
-
--apply --no-install applies
|
|
169
|
+
--apply bumps toward registry latest when behind (not only when CLI ≠ pin)
|
|
170
|
+
and recomputes the preview; a second explicit --apply --no-install applies
|
|
171
|
+
those exact bytes and verifies them. --refresh-skills opts in to rewrite
|
|
172
|
+
customized managed skills to package templates (never silent default).
|
|
169
173
|
(alias: ark update)
|
|
170
174
|
preflight Validate one atomic create/update/delete set without writing project files.
|
|
171
175
|
status Unified session/project manifest (identity, activation, last check, rules).
|
|
@@ -203,8 +207,8 @@ Options:
|
|
|
203
207
|
--remove-host <host>
|
|
204
208
|
Preview or apply removal of that compact host integration; re-add it with --tools <host>.
|
|
205
209
|
--require-write-hook <host>
|
|
206
|
-
Require and verify a hard local write hook for Claude or
|
|
207
|
-
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.
|
|
208
212
|
|
|
209
213
|
Interactive mode (TTY, no --yes): asks what application shape you are building and maps it to a preset.
|
|
210
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';
|
|
@@ -370,7 +370,8 @@ ${steps}
|
|
|
370
370
|
${placementBody}
|
|
371
371
|
|
|
372
372
|
The project is only considered Ark-enforced when its host-appropriate write path is configured
|
|
373
|
-
and the CI check passes. Only Claude/Grok provide a hard local write boundary
|
|
373
|
+
and the CI check passes. Only Claude/Grok/Antigravity/Cursor provide a hard local write boundary
|
|
374
|
+
(Cursor: Write/StrReplace when .cursor/hooks.json is trusted); Codex/OpenCode use
|
|
374
375
|
advisory MCP plus CI. The experimental runtime is not required.
|
|
375
376
|
`;
|
|
376
377
|
}
|
package/bin/lib/gate-files.mjs
CHANGED
|
@@ -127,7 +127,7 @@ const FAIL_CLOSED_ARK_FLAG = /(?:^|\s)--(?:strict|strict-merge|require-gates)(?=
|
|
|
127
127
|
const COMPACT_HOST_FILES = {
|
|
128
128
|
claude: ['.claude/settings.json'],
|
|
129
129
|
grok: ['.grok/config.toml', '.grok/hooks/ark-write-gate.json'],
|
|
130
|
-
cursor: ['.cursor/mcp.json'],
|
|
130
|
+
cursor: ['.cursor/mcp.json', '.cursor/hooks.json'],
|
|
131
131
|
codex: ['.codex/hooks.json', '.codex/config.toml'],
|
|
132
132
|
windsurf: ['.windsurf/rules/ark.md'],
|
|
133
133
|
cline: ['.clinerules/ark.md'],
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
// Generated from hook-templates.source.mjs — run npm run generate:packaged-tooling.
|
|
2
|
-
import{execCommandParts as
|
|
3
|
-
`}function
|
|
4
|
-
`}function
|
|
2
|
+
import{execCommandParts as l,execRunner as i}from"../ark-shared.mjs";const s="arkgate-mcp";function h(e){const r=i(e);return`${JSON.stringify({version:1,hooks:{preToolUse:[{command:`${r} ${s} --hook --hook-repair --fail-on-new-smells --root . --root-env CURSOR_PROJECT_DIR --config ark.config.json`,matcher:"Write|StrReplace",failClosed:!0,timeout:30}]}},null,2)}
|
|
3
|
+
`}function y(e){const r=i(e);return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",command:`${r} ${s} --session-context --root . --root-env CLAUDE_PROJECT_DIR --config ark.config.json`}]}],PreToolUse:[{matcher:"Write|Edit|MultiEdit",hooks:[{type:"command",command:`${r} ${s} --hook --hook-repair --fail-on-new-smells --root . --root-env CLAUDE_PROJECT_DIR --config ark.config.json`}]}]}},null,2)}
|
|
4
|
+
`}function $(e){const r=i(e);return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",timeout:30,command:`${r} ${s} --session-context --root . --root-env CODEX_PROJECT_DIR --config ark.config.json`}]}],PreToolUse:[{matcher:"ApplyPatch|apply_patch|Write|Edit|MultiEdit",hooks:[{type:"command",timeout:30,command:`${r} ${s} --hook --hook-repair --fail-on-new-smells --root . --root-env CODEX_PROJECT_DIR --config ark.config.json`}]}]}},null,2)}
|
|
5
|
+
`}function O(e){const{command:r,args:o}=l(e,s,["--root",".","--config","ark.config.json"]),t=a=>a.replace(/\\/g,"\\\\").replace(/"/g,'\\"'),c=o.map(a=>`"${t(a)}"`).join(", ");return`# Generated by ark-check --install-agent-gates (Codex project scope).
|
|
5
6
|
# CONFIGURED ON DISK \u2014 RUNTIME NOT VERIFIED.
|
|
6
7
|
# Restart Codex, then call ark_identity with expectedRoot before trusting MCP verdicts.
|
|
7
8
|
[mcp_servers.ark]
|
|
8
9
|
command = "${t(r)}"
|
|
9
|
-
args = [${
|
|
10
|
-
`}function
|
|
10
|
+
args = [${c}]
|
|
11
|
+
`}function R(e){const{command:r,args:o}=l(e,s,["--root",".","--config","ark.config.json"]),t=o.map(c=>`"${c.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"`).join(", ");return`# Generated by ark-check --install-agent-gates (Grok Build project scope).
|
|
11
12
|
# Restart Grok (or /mcps \u2192 refresh) after changes. Also loads repo-root .mcp.json.
|
|
12
13
|
[mcp_servers.ark]
|
|
13
14
|
command = "${r.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}"
|
|
14
15
|
args = [${t}]
|
|
15
|
-
`}function
|
|
16
|
-
`}function
|
|
17
|
-
`}function
|
|
18
|
-
`}function
|
|
19
|
-
`}function
|
|
20
|
-
`}
|
|
16
|
+
`}function _(e){const r=i(e);return`${JSON.stringify({hooks:{SessionStart:[{hooks:[{type:"command",timeout:30,command:`${r} ${s} --session-context --root . --root-env GROK_WORKSPACE_ROOT,CLAUDE_PROJECT_DIR --config ark.config.json`}]}],PreToolUse:[{matcher:"Write|Edit|MultiEdit|write|search_replace",hooks:[{type:"command",timeout:30,command:`${r} ${s} --hook --hook-repair --fail-on-new-smells --root . --root-env GROK_WORKSPACE_ROOT,CLAUDE_PROJECT_DIR --config ark.config.json`}]}]}},null,2)}
|
|
17
|
+
`}function A(e){const r=i(e);return`${JSON.stringify({"ark-write-gate":{PreToolUse:[{matcher:"write_to_file|replace_file_content|multi_replace_file_content",hooks:[{type:"command",timeout:30,command:`${r} ${s} --hook --hook-repair --fail-on-new-smells --root . --config ark.config.json`}]}]}},null,2)}
|
|
18
|
+
`}function j(e){const{command:r,args:o}=l(e,s,["--root",".","--config","ark.config.json"]);return`${JSON.stringify({$schema:"https://opencode.ai/config.json",mcp:{ark:{type:"local",command:[r,...o],enabled:!0}}},null,2)}
|
|
19
|
+
`}function E(e,r){let o,t;try{o=e&&e.trim()?JSON.parse(e):{},t=JSON.parse(r)}catch{return null}if(!o||typeof o!="object"||Array.isArray(o)||!t||typeof t!="object"||Array.isArray(t))return null;const c=t.hooks,p=(Array.isArray(c?.preToolUse)?c.preToolUse:[]).find(n=>n&&typeof n=="object"&&typeof n.command=="string"&&/arkgate-mcp|ark-mcp/.test(n.command)&&/\s--hook(?:\s|$)/.test(` ${n.command} `));if(!p)return null;const m=o.hooks&&typeof o.hooks=="object"&&!Array.isArray(o.hooks)?{...o.hooks}:{},f=Array.isArray(m.preToolUse)?[...m.preToolUse]:[],k=n=>n&&typeof n=="object"&&typeof n.command=="string"&&/arkgate-mcp|ark-mcp/.test(n.command)&&/\s--hook(?:\s|$)/.test(` ${n.command} `)&&(typeof n.matcher!="string"||/Write/.test(n.matcher)||/StrReplace/.test(n.matcher)),g=f.filter(n=>!k(n));m.preToolUse=[...g,p];const u={...o,version:o.version??t.version??1,hooks:m};return`${JSON.stringify(u,null,2)}
|
|
20
|
+
`}function S(e,r){let o,t;try{o=e&&e.trim()?JSON.parse(e):{},t=JSON.parse(r)}catch{return null}if(!o||typeof o!="object"||Array.isArray(o)||!t||typeof t!="object"||Array.isArray(t))return null;const c=t["ark-write-gate"];if(!c||typeof c!="object")return null;const a={...o,"ark-write-gate":c};return`${JSON.stringify(a,null,2)}
|
|
21
|
+
`}function C(e,r){let o,t;try{o=e&&e.trim()?JSON.parse(e):{},t=JSON.parse(r)}catch{return null}if(!o||typeof o!="object"||Array.isArray(o))return null;const c={...o};!c.$schema&&t.$schema&&(c.$schema=t.$schema);const a=o.mcp&&typeof o.mcp=="object"&&!Array.isArray(o.mcp)?{...o.mcp}:{};return a.ark=t.mcp.ark,c.mcp=a,`${JSON.stringify(c,null,2)}
|
|
22
|
+
`}export{s as PREFERRED_MCP_BIN,A as antigravityHooks,y as claudeSettings,$ as codexHooks,O as codexProjectConfig,h as cursorHooks,_ as grokHooks,R as grokProjectConfig,S as mergeAntigravityArkHook,E as mergeCursorArkHook,C as mergeOpencodeArkMcp,j as opencodeProjectConfig};
|