arkgate 4.6.2 → 4.6.4
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 +46 -1
- package/README.md +18 -13
- package/bin/ark-mcp-runtime.mjs +41 -4
- package/bin/ark.mjs +1 -0
- package/bin/lib/ci-and-commands.mjs +4 -3
- package/bin/lib/first-run-help.mjs +7 -7
- package/bin/lib/host-support-matrix.mjs +13 -15
- package/bin/lib/managed-upgrade.mjs +1 -1
- package/bin/lib/status-command.mjs +1 -1
- package/bin/lib/upgrade-command.mjs +37 -4
- package/bin/lib/upgrade-whats-new.mjs +19 -1
- package/bin/lib/write-path-capabilities.mjs +9 -5
- package/bin/lib/write-path-detect.mjs +3 -3
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/docs/README.md +4 -4
- package/docs/agent-guide.md +20 -14
- package/docs/ai-gates.md +34 -20
- package/docs/develop.md +5 -1
- package/docs/enthusiast/how-to-agent-gates.md +1 -1
- package/docs/package-surface.md +6 -5
- package/docs/product-voice.md +4 -4
- package/docs/threat-model.md +6 -2
- package/docs/use.md +6 -1
- package/package.json +1 -1
- package/server.json +3 -3
- package/templates/agent-skills/README.md +1 -1
- package/templates/agent-skills/ark-coverage/SKILL.md +4 -3
- package/templates/agent-skills/ark-upgrade/SKILL.md +13 -2
- package/templates/skills/ark-coverage.md +4 -3
- package/templates/skills/ark-upgrade.md +13 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,51 @@ in the immutable pre-2.0 archive linked below.
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 4.6.4 — 2026-08-18
|
|
9
|
+
|
|
10
|
+
**Patch** over **4.6.3**. `ark upgrade` now tells Codex users how to activate the local
|
|
11
|
+
`apply_patch` boundary after upgrading: refresh the project hook, restart Codex/local Desktop,
|
|
12
|
+
trust the exact hook definition, and verify `doctor.writePath` after a governed patch.
|
|
13
|
+
**No required config migration.**
|
|
14
|
+
|
|
15
|
+
**Status: prepared** (not yet on npm `latest`; see `docs/releases/4.6.4.md`).
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **Upgrade JSON:** `whatsNew.items` includes stable id `codex-hard-write` with the exact
|
|
20
|
+
`--install-agent-gates --tools codex --force` command and evidence to inspect.
|
|
21
|
+
- **Upgrade human output:** **Suggested improvements** prints the same refresh/restart/trust/check
|
|
22
|
+
path on preview and apply, including nothing-to-apply previews.
|
|
23
|
+
- **Upgrade skill:** flat and Agent Skills guidance tells Codex to exercise a governed
|
|
24
|
+
`apply_patch` and keeps hosted/specialized/shell/direct/incomplete/human paths CI-backed.
|
|
25
|
+
- **Stale MCP fail-closed:** when process version no longer matches the project install,
|
|
26
|
+
`ark_identity` reports non-authoritative evidence and project tools return
|
|
27
|
+
`PROCESS_PACKAGE_STALE` until restart/retarget.
|
|
28
|
+
- **Global CLI handoff:** a modern stale global `ark upgrade` delegates the original invocation
|
|
29
|
+
to the project-local `node_modules/arkgate/bin/ark.mjs` instead of managing from the wrong PATH
|
|
30
|
+
version. Pre-4.6.4 globals still require one `npx arkgate upgrade` entry.
|
|
31
|
+
|
|
32
|
+
## 4.6.3 — 2026-08-18
|
|
33
|
+
|
|
34
|
+
**Patch** over **4.6.2**. Codex CLI and local ChatGPT Desktop/App Server now get a
|
|
35
|
+
runtime-proven pre-write block for complete `apply_patch` calls. ArkGate accepts the current
|
|
36
|
+
`tool_input.command` payload, while incomplete, hosted, specialized, shell/direct, and human
|
|
37
|
+
write paths remain CI-backed. **No required config migration.**
|
|
38
|
+
|
|
39
|
+
**Status: published** (npm `latest` from signed tag `v4.6.3`; OIDC run `32167523804`;
|
|
40
|
+
see `docs/releases/4.6.3.md`).
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- **Codex hook payload:** current `PreToolUse` `apply_patch` bodies are read from
|
|
45
|
+
`tool_input.command`; historical patch/input/content fields stay compatible.
|
|
46
|
+
- **Operation-scoped hard write:** a complete trusted and runtime-observed local patch can report
|
|
47
|
+
`hard:true` and exit `2` before disk mutation. Hook files alone stay unverified.
|
|
48
|
+
- **Honesty surfaces:** host matrix, doctor/status, `--require-write-hook codex`, onboarding,
|
|
49
|
+
upgrade self-service, skills, and public docs now share the same boundary.
|
|
50
|
+
- **All-path backstop:** required `arkgate-check --strict-merge` CI remains mandatory. MCP stays
|
|
51
|
+
advisory and repair reinjection is not claimed.
|
|
52
|
+
|
|
8
53
|
## 4.6.2 — 2026-08-16
|
|
9
54
|
|
|
10
55
|
**Patch** over **4.6.1**. First-contact copy: a newcomer (human or coding agent) sees what
|
|
@@ -12,7 +57,7 @@ to do in a few lines — `arkgate` / `arkgate-check --help`, start wrap-up, doct
|
|
|
12
57
|
#1, write-gate deny, SessionStart, MCP tool order, and the five doors. Same 13 skill names.
|
|
13
58
|
**No required config migration.**
|
|
14
59
|
|
|
15
|
-
**Status:
|
|
60
|
+
**Status: published** (on npm `latest`; see `docs/releases/4.6.2.md`).
|
|
16
61
|
|
|
17
62
|
### Changed
|
|
18
63
|
|
package/README.md
CHANGED
|
@@ -16,9 +16,9 @@ and makes sure a “green” check means something real.
|
|
|
16
16
|
|
|
17
17
|
</div>
|
|
18
18
|
|
|
19
|
-
> **ArkGate 4.6.
|
|
20
|
-
> **4.6.
|
|
21
|
-
> [4.6.
|
|
19
|
+
> **ArkGate 4.6.3** is on npm `latest` — Codex CLI/local Desktop hard-block complete trusted `apply_patch`.
|
|
20
|
+
> **4.6.4 is prepared** — `ark upgrade` tells Codex how to refresh, trust, restart, and verify it.
|
|
21
|
+
> [4.6.4 notes](docs/releases/4.6.4.md) · [4.6.3](docs/releases/4.6.3.md) · [4.6.2](docs/releases/4.6.2.md) · [4.6.1](docs/releases/4.6.1.md) · [Docs hub](docs/README.md) · [Product voice](docs/product-voice.md)
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
@@ -117,14 +117,15 @@ Details: [docs/use.md](docs/use.md).
|
|
|
117
117
|
| 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) |
|
|
118
118
|
| 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) |
|
|
119
119
|
| 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) |
|
|
120
|
-
| 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
|
|
121
|
-
| OpenAI Codex | **
|
|
120
|
+
| 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 |
|
|
121
|
+
| OpenAI Codex | **Hard** block for listed ops (PreToolUse `apply_patch` in Codex CLI and local ChatGPT Desktop/App Server) 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 |
|
|
122
122
|
| 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 |
|
|
123
123
|
|
|
124
124
|
**Read the CI column:** for every host, the repository-wide hard guarantee is a **required**
|
|
125
125
|
GitHub **status context** that runs the CLI — not “CI file present,” and not the CLI binary name alone.
|
|
126
|
-
Codex
|
|
127
|
-
`preToolUse` ops
|
|
126
|
+
Codex hard write covers only a complete local `apply_patch`; Cursor covers only listed
|
|
127
|
+
`preToolUse` ops. In both cases the project hook must be installed + trusted, while shell/direct
|
|
128
|
+
filesystem writes, hosted or specialized opt-out paths, and human edits still rely on CI.
|
|
128
129
|
|
|
129
130
|
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.
|
|
130
131
|
<!-- arkgate-host-support:end -->
|
|
@@ -133,10 +134,11 @@ This table describes the supported profile **after its files are installed and t
|
|
|
133
134
|
|
|
134
135
|
The split above is a deliberate trade-off, not a gap. ArkGate validates at the earliest boundary
|
|
135
136
|
each host offers and enforces at the earliest boundary a repository can make non-bypassable: the
|
|
136
|
-
required merge status. Hard hooks (Claude Code, Grok Build, Google Antigravity
|
|
137
|
-
write operations at write time; advisory
|
|
138
|
-
while it works. But any local boundary can
|
|
139
|
-
|
|
137
|
+
required merge status. Hard hooks (Claude Code, Grok Build, Google Antigravity, Cursor, and
|
|
138
|
+
Codex’s complete local `apply_patch`) deny their listed write operations at write time; advisory
|
|
139
|
+
surfaces (MCP, rules, OpenCode plugins) coach the agent while it works. But any local boundary can
|
|
140
|
+
be routed around — another tool, a hosted/specialized path, a direct filesystem write, or a human
|
|
141
|
+
edit — so the only guarantee ArkGate claims for every path is the
|
|
140
142
|
`arkgate-check --strict-merge` check, and only when the repository makes that status required.
|
|
141
143
|
Local checks optimize feedback speed; the merge gate owns correctness.
|
|
142
144
|
|
|
@@ -214,8 +216,11 @@ for real systems. Details: [docs/production-hardening.md](docs/production-harden
|
|
|
214
216
|
| Config · package surface · TS | [configuration](docs/configuration.md) · [package-surface](docs/package-surface.md) · [typescript-support](docs/typescript-support.md) |
|
|
215
217
|
| Brownfield | [docs/brownfield-adoption.md](docs/brownfield-adoption.md) |
|
|
216
218
|
| Security | [SECURITY.md](SECURITY.md) |
|
|
217
|
-
| Current release (4.6.
|
|
218
|
-
| Prepared (4.6.
|
|
219
|
+
| Current release (4.6.3 on npm `latest`) | [docs/releases/4.6.3.md](docs/releases/4.6.3.md) · [CHANGELOG](CHANGELOG.md) |
|
|
220
|
+
| Prepared patch (4.6.4) | [docs/releases/4.6.4.md](docs/releases/4.6.4.md) |
|
|
221
|
+
| Prior (4.6.2) | [docs/releases/4.6.2.md](docs/releases/4.6.2.md) |
|
|
222
|
+
| Prior (4.6.1) | [docs/releases/4.6.1.md](docs/releases/4.6.1.md) |
|
|
223
|
+
| Prior (4.6.0) | [docs/releases/4.6.0.md](docs/releases/4.6.0.md) |
|
|
219
224
|
| Prior (4.5.7) | [docs/releases/4.5.7.md](docs/releases/4.5.7.md) |
|
|
220
225
|
| Prior (4.5.0) | [docs/releases/4.5.0.md](docs/releases/4.5.0.md) |
|
|
221
226
|
| Prior (4.4.0) | [docs/releases/4.4.0.md](docs/releases/4.4.0.md) |
|
package/bin/ark-mcp-runtime.mjs
CHANGED
|
@@ -358,6 +358,7 @@ function mapAntigravityToolCall(toolCall) {
|
|
|
358
358
|
* Antigravity: { toolCall: { name, args: { TargetFile, CodeContent, … } } }
|
|
359
359
|
* Cursor: { tool_name, tool_input, hook_event_name?, workspace_roots? }
|
|
360
360
|
* Write uses `contents`; StrReplace maps to Edit (path/old_string/new_string).
|
|
361
|
+
* Codex: { tool_name: "apply_patch", tool_input: { command: "*** Begin Patch..." } }
|
|
361
362
|
*/
|
|
362
363
|
function normalizeHookPayload(payload, grokHookEvent = Boolean(process.env.GROK_HOOK_EVENT)) {
|
|
363
364
|
const antigravityStyle =
|
|
@@ -669,7 +670,10 @@ function runHookPayload(payload, gate, config, args, ts, attemptContext, output
|
|
|
669
670
|
attemptContext?.grokHookEvent ?? Boolean(process.env.GROK_HOOK_EVENT)
|
|
670
671
|
);
|
|
671
672
|
if (toolName === 'ApplyPatch') {
|
|
672
|
-
|
|
673
|
+
// Current Codex CLI/Desktop hook schema uses tool_input.command. Keep the
|
|
674
|
+
// historical fields for older clients and existing integration fixtures.
|
|
675
|
+
const patch =
|
|
676
|
+
toolInput.command ?? toolInput.patch ?? toolInput.input ?? toolInput.content;
|
|
673
677
|
const parsedPatch = codexPatchWrites(patch, args.root);
|
|
674
678
|
// Codex ApplyPatch is only preflighted when Ark can reconstruct every file operation.
|
|
675
679
|
// An incomplete reconstruction must not be mislabeled as atomic or hard enforcement.
|
|
@@ -1956,11 +1960,14 @@ export async function runArkMcp({ hookInput } = {}) {
|
|
|
1956
1960
|
}
|
|
1957
1961
|
|
|
1958
1962
|
function contextFor(binding) {
|
|
1963
|
+
const processPackage = processPackageHonesty();
|
|
1959
1964
|
return {
|
|
1960
1965
|
projectIdentity,
|
|
1961
1966
|
binding,
|
|
1962
|
-
authoritative
|
|
1963
|
-
|
|
1967
|
+
// A correctly bound project is still non-authoritative when this long-lived
|
|
1968
|
+
// process loaded a different package version than the project now resolves.
|
|
1969
|
+
authoritative: binding.authoritative && !processPackage.processStale,
|
|
1970
|
+
processPackage,
|
|
1964
1971
|
};
|
|
1965
1972
|
}
|
|
1966
1973
|
|
|
@@ -2037,13 +2044,36 @@ export async function runArkMcp({ hookInput } = {}) {
|
|
|
2037
2044
|
);
|
|
2038
2045
|
}
|
|
2039
2046
|
|
|
2047
|
+
function staleProcessFailureResult(binding) {
|
|
2048
|
+
const processPackage = processPackageHonesty();
|
|
2049
|
+
return withProjectContext(
|
|
2050
|
+
{
|
|
2051
|
+
content: [
|
|
2052
|
+
{
|
|
2053
|
+
type: 'text',
|
|
2054
|
+
text: JSON.stringify({
|
|
2055
|
+
ok: false,
|
|
2056
|
+
error: {
|
|
2057
|
+
code: 'PROCESS_PACKAGE_STALE',
|
|
2058
|
+
message: processPackage.nextAction,
|
|
2059
|
+
},
|
|
2060
|
+
}),
|
|
2061
|
+
},
|
|
2062
|
+
],
|
|
2063
|
+
isError: true,
|
|
2064
|
+
},
|
|
2065
|
+
binding
|
|
2066
|
+
);
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2040
2069
|
const TOOLS = [
|
|
2041
2070
|
{
|
|
2042
2071
|
name: 'ark_identity',
|
|
2043
2072
|
description:
|
|
2044
2073
|
'First call. Prove this MCP process is the right project: pass project.expectedRoot ' +
|
|
2045
2074
|
'(exact absolute root) and reuse the returned projectId. Do this before any other Ark tool. ' +
|
|
2046
|
-
'A missing
|
|
2075
|
+
'A missing/unmatched root or processPackage.processStale means restart/retarget the host ' +
|
|
2076
|
+
'and use the project-local CLI until identity and package versions align.',
|
|
2047
2077
|
inputSchema: { type: 'object', properties: {} },
|
|
2048
2078
|
},
|
|
2049
2079
|
{
|
|
@@ -3001,6 +3031,13 @@ export async function runArkMcp({ hookInput } = {}) {
|
|
|
3001
3031
|
reply(id, bindingFailureResult(binding));
|
|
3002
3032
|
return;
|
|
3003
3033
|
}
|
|
3034
|
+
// Keep ark_identity available so the host can diagnose the stale process,
|
|
3035
|
+
// but fail every project tool closed until the MCP server restarts on the
|
|
3036
|
+
// version installed for this root.
|
|
3037
|
+
if (params?.name !== 'ark_identity' && processPackageHonesty().processStale) {
|
|
3038
|
+
reply(id, staleProcessFailureResult(binding));
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3004
3041
|
try {
|
|
3005
3042
|
reply(id, withProjectContext(handler(params, binding), binding));
|
|
3006
3043
|
} catch (error) {
|
package/bin/ark.mjs
CHANGED
|
@@ -370,9 +370,10 @@ ${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.
|
|
374
|
-
|
|
375
|
-
|
|
373
|
+
and the CI check passes. Claude/Grok/Antigravity/Cursor provide hard local boundaries for their
|
|
374
|
+
listed operations. Codex provides a hard boundary for complete local apply_patch when
|
|
375
|
+
.codex/hooks.json is trusted and runtime-observed; hosted/specialized/direct-write paths remain
|
|
376
|
+
CI-backed. OpenCode uses advisory MCP plus CI. The experimental runtime is not required.
|
|
376
377
|
`;
|
|
377
378
|
}
|
|
378
379
|
|
|
@@ -88,8 +88,8 @@ Options:
|
|
|
88
88
|
--remove-host <host>
|
|
89
89
|
Preview or apply removal of that compact host integration; re-add it with --tools <host>.
|
|
90
90
|
--require-write-hook <host>
|
|
91
|
-
Require and verify a hard local write hook for Claude, Grok, Antigravity, or
|
|
92
|
-
Codex
|
|
91
|
+
Require and verify a hard local write hook for Claude, Grok, Antigravity, Cursor, or Codex.
|
|
92
|
+
Codex covers complete local apply_patch only; OpenCode is advisory-write plus hard CI merge.
|
|
93
93
|
|
|
94
94
|
Interactive mode (TTY, no --yes): asks what application shape you are building and maps it to a preset.
|
|
95
95
|
Non-interactive (no TTY): uses the same defaults as --yes — never calls readline on a null interface.
|
|
@@ -183,10 +183,10 @@ export function checkUsageAll() {
|
|
|
183
183
|
'transition. Weakening or judgment-required findings fail unless --policy-ack names',
|
|
184
184
|
'every finding and is bound to both policy hashes. Use --policy-base/--policy-base-ref',
|
|
185
185
|
'for an explicit comparison; ARK_POLICY_BASE_REF is the CI environment equivalent.',
|
|
186
|
-
'Add --require-write-hook claude|grok|antigravity|cursor to validate a hard local
|
|
187
|
-
'boundary for that specific host. Codex
|
|
188
|
-
'
|
|
189
|
-
'to make
|
|
186
|
+
'Add --require-write-hook claude|grok|antigravity|cursor|codex to validate a hard local',
|
|
187
|
+
'write boundary for that specific host. Codex covers complete local apply_patch only;',
|
|
188
|
+
'hosted/specialized/direct-write paths and OpenCode remain CI-backed. Merge blocking requires',
|
|
189
|
+
'repository policy to make the shared CI status required.',
|
|
190
190
|
'',
|
|
191
191
|
'--require-gates implies --strict-config and fails when the Ark contract in AGENTS.md,',
|
|
192
192
|
'the project-rooted Ark server in .mcp.json, or fail-closed CI is missing/invalid.',
|
|
@@ -194,7 +194,7 @@ export function checkUsageAll() {
|
|
|
194
194
|
'',
|
|
195
195
|
'--install-agent-gates writes AGENTS.md, .mcp.json, and the CI workflow for every',
|
|
196
196
|
'project, plus tool-specific templates. Known tools: claude, cursor, codex, grok',
|
|
197
|
-
'(Claude/Grok/Antigravity/Cursor hard-write hooks when covered; Codex
|
|
197
|
+
'(Claude/Grok/Antigravity/Cursor hard-write hooks when covered; Codex hard local apply_patch;',
|
|
198
198
|
'shared CI check for all) and',
|
|
199
199
|
'windsurf, cline, copilot, kiro, roo, continue, gemini',
|
|
200
200
|
'(instruction-tier rule files derived from the same contract).',
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
function hostProfile(label, hookPath, hookSurface, hookOperations, hardWrite, repairPayload, extras = {}) {
|
|
19
19
|
// EH07: repair envelope emission ≠ reinjection guarantee.
|
|
20
|
-
// Codex hooks may emit --hook-repair JSON while reinjection stays host-dependent
|
|
20
|
+
// Cursor/Codex hooks may emit --hook-repair JSON while reinjection stays host-dependent.
|
|
21
21
|
const repairEnvelopeEmitted =
|
|
22
22
|
extras.repairEnvelopeEmitted === true || repairPayload === true;
|
|
23
23
|
const repairReinjectionGuaranteed = hardWrite === true && repairPayload === true;
|
|
@@ -94,15 +94,15 @@ export const HOST_SUPPORT_MATRIX = Object.freeze({
|
|
|
94
94
|
codex: hostProfile(
|
|
95
95
|
'OpenAI Codex',
|
|
96
96
|
'.codex/hooks.json',
|
|
97
|
-
'
|
|
97
|
+
'PreToolUse `apply_patch` in Codex CLI and local ChatGPT Desktop/App Server',
|
|
98
98
|
['apply_patch'],
|
|
99
|
-
|
|
99
|
+
true,
|
|
100
100
|
false,
|
|
101
101
|
{
|
|
102
|
-
// Install writes --hook-repair; envelope can
|
|
102
|
+
// Install writes --hook-repair; envelope can emit, but host reinjection is not guaranteed.
|
|
103
103
|
repairEnvelopeEmitted: true,
|
|
104
104
|
operationCoverage: {
|
|
105
|
-
apply_patch:
|
|
105
|
+
apply_patch: true,
|
|
106
106
|
shell: false,
|
|
107
107
|
'pre-commit': false,
|
|
108
108
|
},
|
|
@@ -152,15 +152,12 @@ export function renderHostSupportMatrixMarkdown() {
|
|
|
152
152
|
const rows = HOST_SUPPORT_HOSTS.map((host) => {
|
|
153
153
|
const profile = HOST_SUPPORT_MATRIX[host];
|
|
154
154
|
const capabilities = profile.capabilities;
|
|
155
|
-
// Fail-closed honesty:
|
|
156
|
-
//
|
|
155
|
+
// Fail-closed honesty: hard hosts claim only listed operations when hooks are
|
|
156
|
+
// installed + trusted. OpenCode remains advisory; CI is required-status.
|
|
157
157
|
// hookSurface already includes "PreToolUse/preToolUse …" — do not prefix again.
|
|
158
158
|
let local;
|
|
159
159
|
if (capabilities['hard-write']) {
|
|
160
160
|
local = `**Hard** block for listed ops (${profile.hookSurface}) when installed + trusted`;
|
|
161
|
-
} else if (host === 'codex') {
|
|
162
|
-
local =
|
|
163
|
-
'**Advisory / best-effort** at write (not equivalent to Claude/Grok/Cursor hard block)';
|
|
164
161
|
} else if (host === 'opencode') {
|
|
165
162
|
local =
|
|
166
163
|
'**Advisory / best-effort** at write (MCP + optional plugin; not a hard boundary)';
|
|
@@ -172,7 +169,7 @@ export function renderHostSupportMatrixMarkdown() {
|
|
|
172
169
|
if (capabilities['repair-reinjection-guaranteed']) {
|
|
173
170
|
repair = 'Emitted on hook deny; host must re-inject (hard path when installed + trusted)';
|
|
174
171
|
} else if (capabilities['repair-envelope-emitted']) {
|
|
175
|
-
repair = 'Envelope may emit (`--hook-repair`); reinjection **not** guaranteed
|
|
172
|
+
repair = 'Envelope may emit (`--hook-repair`); reinjection **not** guaranteed';
|
|
176
173
|
} else {
|
|
177
174
|
repair = 'No hard-boundary payload';
|
|
178
175
|
}
|
|
@@ -188,8 +185,9 @@ ${rows}
|
|
|
188
185
|
|
|
189
186
|
**Read the CI column:** for every host, the repository-wide hard guarantee is a **required**
|
|
190
187
|
GitHub **status context** that runs the CLI — not “CI file present,” and not the CLI binary name alone.
|
|
191
|
-
Codex
|
|
192
|
-
\`preToolUse\` ops
|
|
188
|
+
Codex hard write covers only a complete local \`apply_patch\`; Cursor covers only listed
|
|
189
|
+
\`preToolUse\` ops. In both cases the project hook must be installed + trusted, while shell/direct
|
|
190
|
+
filesystem writes, hosted or specialized opt-out paths, and human edits still rely on CI.
|
|
193
191
|
|
|
194
192
|
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.`;
|
|
195
193
|
}
|
|
@@ -226,8 +224,8 @@ export function doctorWritePathHonestyMessage(activeHost, hardWriteActive) {
|
|
|
226
224
|
if (host === 'cursor' && !hardWriteActive) {
|
|
227
225
|
return `Cursor: pre-write block is supported for Write/StrReplace when .cursor/hooks.json is installed + trusted; without runtime-observed hook evidence, the block is unverified. ${mergeBoundary}.`;
|
|
228
226
|
}
|
|
229
|
-
if (host === 'codex') {
|
|
230
|
-
return `Codex:
|
|
227
|
+
if (host === 'codex' && !hardWriteActive) {
|
|
228
|
+
return `Codex: a trusted PreToolUse hook can block complete local apply_patch calls in CLI and Desktop; without fresh runtime-observed apply_patch evidence, the block is unverified. Specialized/hosted paths and direct writes still rely on CI. ${mergeBoundary}.`;
|
|
231
229
|
}
|
|
232
230
|
if (host === 'opencode') {
|
|
233
231
|
return `OpenCode: edits are warning only (not blocked). ${mergeBoundary}.`;
|
|
@@ -725,7 +725,7 @@ export function buildPostUpgradeChecks(root, options = {}) {
|
|
|
725
725
|
neverGateInput: true,
|
|
726
726
|
checks,
|
|
727
727
|
mcpNote:
|
|
728
|
-
'If you used Ark MCP this session: restart/retarget
|
|
728
|
+
'If you used Ark MCP this session: restart/retarget after the package bump. A stale process is non-authoritative and project tools return PROCESS_PACKAGE_STALE; always pass project.expectedRoot + expectedProjectId. Prefer project-local CLI until identity and package versions align.',
|
|
729
729
|
};
|
|
730
730
|
}
|
|
731
731
|
|
|
@@ -299,7 +299,7 @@ export function collectStatusFacts(options = {}) {
|
|
|
299
299
|
? writePath.activeHost.trim().toLowerCase()
|
|
300
300
|
: String(activeHost).trim().toLowerCase();
|
|
301
301
|
const matrix = HOST_SUPPORT_MATRIX[hostKey] ?? null;
|
|
302
|
-
// Soft only when the host is known and matrix hard-write is false (
|
|
302
|
+
// Soft only when the host is known and matrix hard-write is false (currently OpenCode).
|
|
303
303
|
const softWriteHost = Boolean(matrix && matrix.capabilities?.['hard-write'] !== true);
|
|
304
304
|
const writePathHonesty = buildWritePathHonesty(hostKey, hardWriteActive, {
|
|
305
305
|
packageInstalled: writePath?.enforcementState?.localWrite?.installed !== false,
|
|
@@ -148,8 +148,9 @@ function staleCliRefuseMessage(detail) {
|
|
|
148
148
|
* project's installed arkgate. Legacy global 2.x mutates skills; a newer
|
|
149
149
|
* project pin must not be managed by an older outside-tree binary.
|
|
150
150
|
*
|
|
151
|
-
* Allow: no local install
|
|
152
|
-
* (realpath)
|
|
151
|
+
* Allow directly: no local install or CLI package root inside the project install tree
|
|
152
|
+
* (realpath). Any outside-tree modern CLI prefers a project-local handoff, even at the same
|
|
153
|
+
* or newer version, so one checkout never manages another through PATH state.
|
|
153
154
|
*
|
|
154
155
|
* @param {string} root project root for --root
|
|
155
156
|
* @param {{
|
|
@@ -157,7 +158,7 @@ function staleCliRefuseMessage(detail) {
|
|
|
157
158
|
* cliPackageRoot?: string|null,
|
|
158
159
|
* projectPackageJsonPath?: string|null,
|
|
159
160
|
* }} [options]
|
|
160
|
-
* @returns {{ refuse: boolean, reason: string, message?: string, cliVersion?: string|null, projectVersion?: string|null, cliPackageRoot?: string|null, projectPackageRoot?: string|null }}
|
|
161
|
+
* @returns {{ refuse: boolean, handoff?: boolean, reason: string, message?: string, cliVersion?: string|null, projectVersion?: string|null, cliPackageRoot?: string|null, projectPackageRoot?: string|null }}
|
|
161
162
|
*/
|
|
162
163
|
export function evaluateStaleUpgradeCli(root, options = {}) {
|
|
163
164
|
const projectPkgPath =
|
|
@@ -272,7 +273,8 @@ export function evaluateStaleUpgradeCli(root, options = {}) {
|
|
|
272
273
|
|
|
273
274
|
return {
|
|
274
275
|
refuse: false,
|
|
275
|
-
|
|
276
|
+
handoff: true,
|
|
277
|
+
reason: 'outside-cli-project-local-preferred',
|
|
276
278
|
cliVersion,
|
|
277
279
|
projectVersion,
|
|
278
280
|
cliPackageRoot,
|
|
@@ -325,6 +327,15 @@ function verify(root, json, arkCheck, runArkCheck) {
|
|
|
325
327
|
return { exitCode: result.status ?? 1, stderr: result.stderr?.trim() || undefined };
|
|
326
328
|
}
|
|
327
329
|
|
|
330
|
+
function runProjectLocalCli({ root, script, argv }) {
|
|
331
|
+
const result = spawnSync(process.execPath, [script, ...argv], {
|
|
332
|
+
cwd: root,
|
|
333
|
+
stdio: 'inherit',
|
|
334
|
+
env: { ...process.env, ARK_PROJECT_LOCAL_HANDOFF: '1' },
|
|
335
|
+
});
|
|
336
|
+
return result.status ?? 1;
|
|
337
|
+
}
|
|
338
|
+
|
|
328
339
|
export function runUpgradeCommand(args, dependencies) {
|
|
329
340
|
const root = args.root;
|
|
330
341
|
// Fail closed before install/plan when PATH resolves a global/stale CLI older
|
|
@@ -339,6 +350,28 @@ export function runUpgradeCommand(args, dependencies) {
|
|
|
339
350
|
cliVersion: dependencies?.cliVersion,
|
|
340
351
|
cliPackageRoot: dependencies?.cliPackageRoot,
|
|
341
352
|
});
|
|
353
|
+
if (staleGuard?.refuse || staleGuard?.handoff) {
|
|
354
|
+
const projectLocalScript =
|
|
355
|
+
typeof staleGuard.projectPackageRoot === 'string'
|
|
356
|
+
? path.join(staleGuard.projectPackageRoot, 'bin', 'ark.mjs')
|
|
357
|
+
: null;
|
|
358
|
+
if (
|
|
359
|
+
process.env.ARK_PROJECT_LOCAL_HANDOFF !== '1' &&
|
|
360
|
+
Array.isArray(dependencies?.rawArgv) &&
|
|
361
|
+
projectLocalScript &&
|
|
362
|
+
fs.existsSync(projectLocalScript)
|
|
363
|
+
) {
|
|
364
|
+
const handoff =
|
|
365
|
+
typeof dependencies.runProjectLocalCli === 'function'
|
|
366
|
+
? dependencies.runProjectLocalCli
|
|
367
|
+
: runProjectLocalCli;
|
|
368
|
+
return handoff({
|
|
369
|
+
root,
|
|
370
|
+
script: projectLocalScript,
|
|
371
|
+
argv: dependencies.rawArgv,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
}
|
|
342
375
|
if (staleGuard?.refuse) {
|
|
343
376
|
const message = staleGuard.message || 'Refusing ark upgrade: stale CLI.';
|
|
344
377
|
if (args.json) {
|
|
@@ -65,6 +65,15 @@ export function buildUpgradeWhatsNewSuggestions() {
|
|
|
65
65
|
why:
|
|
66
66
|
'Project skills follow this pin. Shared homes stay on the newest ArkGate on the machine (additive; never downgrade). Orphan 2.x global skills stop coaching the wrong version.',
|
|
67
67
|
},
|
|
68
|
+
{
|
|
69
|
+
id: 'codex-hard-write',
|
|
70
|
+
title: 'Codex complete local apply_patch block',
|
|
71
|
+
try: 'npx arkgate-check --install-agent-gates --tools codex --force',
|
|
72
|
+
inspect:
|
|
73
|
+
'.codex/hooks.json trust + doctor.writePath after a runtime-observed apply_patch',
|
|
74
|
+
why:
|
|
75
|
+
'After upgrade, refresh the project hook, restart Codex or local Desktop, and trust the exact hook definition. Only a complete observed apply_patch is hard; hosted, specialized, shell/direct, incomplete, and human paths still rely on required CI.',
|
|
76
|
+
},
|
|
68
77
|
{
|
|
69
78
|
id: 'deep-module-coach',
|
|
70
79
|
title: 'Deep-module coach (hot paths + deepening)',
|
|
@@ -129,7 +138,16 @@ export function buildUpgradeWhatsNewSuggestions() {
|
|
|
129
138
|
try: 'ark_identity with project.expectedRoot; read processPackage on every tool',
|
|
130
139
|
inspect: 'processPackage.processPackageMismatch / processStale + nextAction',
|
|
131
140
|
why:
|
|
132
|
-
'One user, many checkouts: after package bump, restart MCP so process arkgateVersion matches install.
|
|
141
|
+
'One user, many checkouts: after package bump, restart MCP so process arkgateVersion matches install. A stale process is non-authoritative and project tools fail closed until restart.',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: 'process-version-recovery',
|
|
145
|
+
title: 'Stale MCP / global CLI recovery',
|
|
146
|
+
try: 'npx arkgate upgrade --json',
|
|
147
|
+
inspect:
|
|
148
|
+
'processPackage.processStale + PROCESS_PACKAGE_STALE; project-local CLI handoff for stale global upgrade',
|
|
149
|
+
why:
|
|
150
|
+
'A long-lived MCP on an older package can no longer return authoritative project evidence. A newer global ArkGate that is older than this project automatically hands `ark upgrade` to the project-local CLI instead of staying stuck on PATH.',
|
|
133
151
|
},
|
|
134
152
|
],
|
|
135
153
|
};
|
|
@@ -219,6 +219,9 @@ function requiredWriteOperations(relativePath) {
|
|
|
219
219
|
if (relativePath === '.cursor/hooks.json' || relativePath.startsWith('.cursor/hooks')) {
|
|
220
220
|
return ['Write', 'StrReplace'];
|
|
221
221
|
}
|
|
222
|
+
if (relativePath === '.codex/hooks.json' || relativePath.startsWith('.codex/hooks')) {
|
|
223
|
+
return ['apply_patch'];
|
|
224
|
+
}
|
|
222
225
|
return ['Write', 'Edit', 'MultiEdit'];
|
|
223
226
|
}
|
|
224
227
|
|
|
@@ -445,6 +448,7 @@ export function detectWritePathInventory(root) {
|
|
|
445
448
|
const grokHook = hookEvidence(root, '.grok/hooks/ark-write-gate.json');
|
|
446
449
|
const antigravityHook = hookEvidence(root, '.agents/hooks.json');
|
|
447
450
|
const cursorHook = cursorHookEvidence(root);
|
|
451
|
+
const codexHook = hookEvidence(root, '.codex/hooks.json');
|
|
448
452
|
const hosts = {
|
|
449
453
|
claude: hostRecord(
|
|
450
454
|
claudeHook.hard,
|
|
@@ -478,11 +482,11 @@ export function detectWritePathInventory(root) {
|
|
|
478
482
|
[],
|
|
479
483
|
merge
|
|
480
484
|
),
|
|
481
|
-
// Codex
|
|
482
|
-
//
|
|
483
|
-
//
|
|
484
|
-
//
|
|
485
|
-
codex: hostRecord(
|
|
485
|
+
// Current Codex CLI and local ChatGPT Desktop/App Server synchronously run
|
|
486
|
+
// PreToolUse for apply_patch. Disk evidence remains unverified until a fresh
|
|
487
|
+
// covered invocation; hosted/specialized/direct-write paths stay outside scope.
|
|
488
|
+
// Repair envelopes may emit, but host reinjection is not guaranteed.
|
|
489
|
+
codex: hostRecord(codexHook.hard, codexMcpEvidence(root), [], merge),
|
|
486
490
|
// OpenCode: MCP only (plugin hooks are incomplete / subagent-bypassable).
|
|
487
491
|
opencode: hostRecord([], opencodeMcpEvidence(root), [], merge),
|
|
488
492
|
};
|
|
@@ -111,9 +111,9 @@ export function detectWritePathCapabilities(root, explicitHost, attempt) {
|
|
|
111
111
|
} else if (mode === 'mcp-only') {
|
|
112
112
|
const honesty =
|
|
113
113
|
activeHost === 'codex'
|
|
114
|
-
? 'Codex
|
|
115
|
-
'
|
|
116
|
-
'
|
|
114
|
+
? 'Codex MCP is advisory because no complete Ark apply_patch hook was detected. ' +
|
|
115
|
+
'Install and trust .codex/hooks.json for the operation-scoped pre-write block; ' +
|
|
116
|
+
'CI --strict-merge plus a required status remains the all-path merge boundary.'
|
|
117
117
|
: activeHost === 'opencode'
|
|
118
118
|
? 'OpenCode local write is advisory (MCP + optional experimental plugin — not a hard boundary; ' +
|
|
119
119
|
'not equivalent to Claude/Grok/Antigravity PreToolUse hard-write). ' +
|