arkgate 3.8.2 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +91 -0
- package/README.md +94 -345
- package/bin/ark-mcp-runtime.mjs +137 -11
- package/bin/ark-shared.mjs +89 -2
- package/bin/ark.mjs +41 -19
- package/bin/lib/agent-gates.mjs +4 -0
- package/bin/lib/ci-and-commands.mjs +28 -21
- package/bin/lib/doctor-plan.mjs +37 -28
- package/bin/lib/hook-templates.mjs +13 -9
- package/bin/lib/host-support-matrix.mjs +64 -4
- package/bin/lib/install-migrate.mjs +96 -1
- package/bin/lib/managed-upgrade.mjs +30 -1
- package/bin/lib/mcp-adoption.mjs +60 -2
- package/bin/lib/post-green-path.mjs +2 -2
- package/bin/lib/skill-install.mjs +46 -2
- package/bin/lib/start-preview.mjs +23 -7
- package/bin/lib/upgrade-command.mjs +57 -15
- package/bin/lib/write-path-capabilities.mjs +67 -18
- package/bin/lib/write-path-detect.mjs +11 -7
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/docs/README.md +70 -0
- package/docs/agent-guide.md +59 -17
- package/docs/ai-gates.md +97 -11
- package/docs/develop.md +127 -0
- package/docs/enthusiast/README.md +2 -0
- package/docs/package-surface.md +3 -3
- package/docs/product-voice.md +194 -0
- package/docs/use.md +88 -0
- package/package.json +5 -1
- package/server.json +2 -2
- package/templates/hooks/opencode-ark-write-gate.mjs +85 -0
- package/templates/skills/ark-autopilot.md +20 -7
- package/templates/skills/ark-explore.md +17 -4
|
@@ -8,18 +8,28 @@ import { arkCommand } from '../ark-shared.mjs';
|
|
|
8
8
|
import { codexPromptsDir, codexSkillsDir } from './codex-home.mjs';
|
|
9
9
|
import { __packageRoot, isCompactRouterAgentsContent, readJson } from './gate-files.mjs';
|
|
10
10
|
|
|
11
|
+
/** Alias map so --tools agy installs the antigravity profile. */
|
|
12
|
+
const TOOL_ALIASES = Object.freeze({
|
|
13
|
+
agy: 'antigravity',
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function normalizeToolId(value) {
|
|
17
|
+
const id = String(value).trim().toLowerCase();
|
|
18
|
+
return TOOL_ALIASES[id] ?? id;
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
export function normalizeToolsList(tools) {
|
|
12
22
|
if (tools == null) return [];
|
|
13
23
|
if (Array.isArray(tools)) {
|
|
14
24
|
return tools
|
|
15
25
|
.flatMap((t) => String(t).split(','))
|
|
16
|
-
.map(
|
|
26
|
+
.map(normalizeToolId)
|
|
17
27
|
.filter(Boolean);
|
|
18
28
|
}
|
|
19
29
|
if (typeof tools === 'string') {
|
|
20
30
|
return tools
|
|
21
31
|
.split(',')
|
|
22
|
-
.map(
|
|
32
|
+
.map(normalizeToolId)
|
|
23
33
|
.filter(Boolean);
|
|
24
34
|
}
|
|
25
35
|
return [];
|
|
@@ -77,6 +87,25 @@ export function detectActiveAgentHost(env = process.env) {
|
|
|
77
87
|
) {
|
|
78
88
|
return 'codex';
|
|
79
89
|
}
|
|
90
|
+
// Google Antigravity / agy CLI
|
|
91
|
+
if (
|
|
92
|
+
envTruthy(env.ANTIGRAVITY) ||
|
|
93
|
+
envTruthy(env.AGY) ||
|
|
94
|
+
env.ANTIGRAVITY_WORKSPACE ||
|
|
95
|
+
env.AGY_WORKSPACE ||
|
|
96
|
+
/antigravity/i.test(String(env.TERM_PROGRAM ?? ''))
|
|
97
|
+
) {
|
|
98
|
+
return 'antigravity';
|
|
99
|
+
}
|
|
100
|
+
// OpenCode
|
|
101
|
+
if (
|
|
102
|
+
envTruthy(env.OPENCODE) ||
|
|
103
|
+
env.OPENCODE_SESSION_ID ||
|
|
104
|
+
env.OPENCODE_CONFIG ||
|
|
105
|
+
envTruthy(env.OPENCODE_CLI)
|
|
106
|
+
) {
|
|
107
|
+
return 'opencode';
|
|
108
|
+
}
|
|
80
109
|
return null;
|
|
81
110
|
}
|
|
82
111
|
|
|
@@ -101,6 +130,15 @@ export function resolveTools(args) {
|
|
|
101
130
|
if (fs.existsSync(path.join(root, '.cursor'))) detected.add('cursor');
|
|
102
131
|
if (fs.existsSync(path.join(root, '.codex'))) detected.add('codex');
|
|
103
132
|
if (fs.existsSync(path.join(root, '.grok'))) detected.add('grok');
|
|
133
|
+
// Antigravity project hooks (distinct from Codex-only `.agents/skills`).
|
|
134
|
+
if (fs.existsSync(path.join(root, '.agents', 'hooks.json'))) detected.add('antigravity');
|
|
135
|
+
if (
|
|
136
|
+
fs.existsSync(path.join(root, 'opencode.json')) ||
|
|
137
|
+
fs.existsSync(path.join(root, 'opencode.jsonc')) ||
|
|
138
|
+
fs.existsSync(path.join(root, '.opencode'))
|
|
139
|
+
) {
|
|
140
|
+
detected.add('opencode');
|
|
141
|
+
}
|
|
104
142
|
if (fs.existsSync(path.join(root, '.windsurf'))) detected.add('windsurf');
|
|
105
143
|
// .clinerules can also be a single FILE (older Cline convention); only a directory
|
|
106
144
|
// can receive .clinerules/ark.md, so a file must not trigger detection.
|
|
@@ -136,6 +174,8 @@ export const KNOWN_TOOLS = [
|
|
|
136
174
|
'cursor',
|
|
137
175
|
'codex',
|
|
138
176
|
'grok',
|
|
177
|
+
'antigravity',
|
|
178
|
+
'opencode',
|
|
139
179
|
'windsurf',
|
|
140
180
|
'cline',
|
|
141
181
|
'copilot',
|
|
@@ -161,6 +201,10 @@ export const SKILL_TOOL_TARGETS = {
|
|
|
161
201
|
codex: (name) => `.agents/skills/${name}/SKILL.md`,
|
|
162
202
|
// Grok Build: project skills at .grok/skills/<name>/SKILL.md (slash-invocable).
|
|
163
203
|
grok: (name) => `.grok/skills/${name}/SKILL.md`,
|
|
204
|
+
// Antigravity loads Agent Skills from `.agents/skills` (shared path with Codex).
|
|
205
|
+
antigravity: (name) => `.agents/skills/${name}/SKILL.md`,
|
|
206
|
+
// OpenCode project skills under `.opencode/skills`.
|
|
207
|
+
opencode: (name) => `.opencode/skills/${name}/SKILL.md`,
|
|
164
208
|
windsurf: (name) => `.windsurf/workflows/${name}.md`,
|
|
165
209
|
cline: (name) => `.clinerules/workflows/${name}.md`,
|
|
166
210
|
copilot: (name) => `.github/prompts/${name}.prompt.md`,
|
|
@@ -5,7 +5,14 @@ import os from 'node:os';
|
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { arkCommand, buildArchitectureRecommendation } from '../ark-shared.mjs';
|
|
7
7
|
import { compactAgentInstructions, instructionRule, mcpJson } from './ci-and-commands.mjs';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
antigravityHooks,
|
|
10
|
+
claudeSettings,
|
|
11
|
+
codexHooks,
|
|
12
|
+
grokHooks,
|
|
13
|
+
grokProjectConfig,
|
|
14
|
+
opencodeProjectConfig,
|
|
15
|
+
} from './hook-templates.mjs';
|
|
9
16
|
|
|
10
17
|
const COMPACT_HOST_TEMPLATES = {
|
|
11
18
|
claude: (root) => [
|
|
@@ -16,8 +23,13 @@ const COMPACT_HOST_TEMPLATES = {
|
|
|
16
23
|
['.grok/config.toml', grokProjectConfig(root)],
|
|
17
24
|
['.grok/hooks/ark-write-gate.json', grokHooks(root)],
|
|
18
25
|
],
|
|
26
|
+
antigravity: (root) => [
|
|
27
|
+
['.agents/hooks.json', antigravityHooks(root)],
|
|
28
|
+
['.mcp.json', mcpJson(root)],
|
|
29
|
+
],
|
|
19
30
|
cursor: (root) => [['.cursor/mcp.json', mcpJson(root)]],
|
|
20
31
|
codex: (root) => [['.codex/hooks.json', codexHooks(root)]],
|
|
32
|
+
opencode: (root) => [['opencode.json', opencodeProjectConfig(root)]],
|
|
21
33
|
windsurf: (root) => [['.windsurf/rules/ark.md', instructionRule(root)]],
|
|
22
34
|
cline: (root) => [['.clinerules/ark.md', instructionRule(root)]],
|
|
23
35
|
copilot: (root) => [['.github/copilot-instructions.md', instructionRule(root)]],
|
|
@@ -70,12 +82,14 @@ function setupBudget(changes) {
|
|
|
70
82
|
(total, item) => total + (item.afterBase64 ? Buffer.from(item.afterBase64, 'base64').length : 0),
|
|
71
83
|
0
|
|
72
84
|
);
|
|
85
|
+
// Compact start includes shared MCP + one host registration + CI + AGENTS + config.
|
|
86
|
+
// Budget raised from 5→8 so .mcp.json always fits (field: grok compact hit the old ceiling).
|
|
73
87
|
return {
|
|
74
88
|
files: generatedChanges.length,
|
|
75
89
|
bytes,
|
|
76
|
-
maxFiles:
|
|
77
|
-
maxBytes:
|
|
78
|
-
ok: generatedChanges.length <=
|
|
90
|
+
maxFiles: 8,
|
|
91
|
+
maxBytes: 32 * 1024,
|
|
92
|
+
ok: generatedChanges.length <= 8 && bytes < 32 * 1024,
|
|
79
93
|
};
|
|
80
94
|
}
|
|
81
95
|
|
|
@@ -84,7 +98,8 @@ function commands(root, args, helpers) {
|
|
|
84
98
|
return [`ark start --root ${root} --tools ${args.removeHost} --apply`];
|
|
85
99
|
}
|
|
86
100
|
const result = [];
|
|
87
|
-
|
|
101
|
+
// Default install=true: surface the package pin/install command unless --no-install.
|
|
102
|
+
if (args.install !== false && fs.existsSync(path.join(root, 'package.json'))) {
|
|
88
103
|
const [command, commandArgs] = helpers.packageInstallArgv(root, `^${helpers.cliVersion()}`);
|
|
89
104
|
result.push(`${command} ${commandArgs.join(' ')}`);
|
|
90
105
|
}
|
|
@@ -216,8 +231,9 @@ export async function planStart(args, helpers) {
|
|
|
216
231
|
if (args.yes) childArgs.push('--yes');
|
|
217
232
|
if (args.force) childArgs.push('--force');
|
|
218
233
|
if (!args.strict) childArgs.push('--no-strict');
|
|
219
|
-
|
|
220
|
-
if (args.
|
|
234
|
+
// Propagate install intent into the shadow plan so package.json pin is in the diff by default.
|
|
235
|
+
if (args.install === false) childArgs.push('--no-install');
|
|
236
|
+
else childArgs.push('--install');
|
|
221
237
|
if (args.tools) childArgs.push('--tools', args.tools);
|
|
222
238
|
if (args.requireWriteHook) childArgs.push('--require-write-hook', args.requireWriteHook);
|
|
223
239
|
const planned = spawnSync(process.execPath, [helpers.cliPath, ...childArgs], {
|
|
@@ -55,23 +55,50 @@ function verify(root, json, arkCheck, runArkCheck) {
|
|
|
55
55
|
export function runUpgradeCommand(args, dependencies) {
|
|
56
56
|
const root = args.root;
|
|
57
57
|
if (args.apply && args.install) {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
const skip =
|
|
59
|
+
typeof dependencies.shouldSkipArkgateInstall === 'function'
|
|
60
|
+
? dependencies.shouldSkipArkgateInstall(root, dependencies.cliVersion)
|
|
61
|
+
: { skip: false };
|
|
62
|
+
if (skip.skip) {
|
|
63
|
+
if (!args.json) {
|
|
64
|
+
console.log(
|
|
65
|
+
`Package already at arkgate@${skip.installedVersion}; skipping install and recomputing managed preview.`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
const [command, commandArgs] = dependencies.packageInstallArgv(root);
|
|
70
|
+
if (!args.json) console.log(`Updating ArkGate: ${command} ${commandArgs.join(' ')}`);
|
|
71
|
+
const install = spawnSync(command, commandArgs, {
|
|
72
|
+
cwd: root,
|
|
73
|
+
stdio: args.json ? ['ignore', 'pipe', 'pipe'] : 'inherit',
|
|
74
|
+
encoding: 'utf8',
|
|
75
|
+
});
|
|
76
|
+
const exitCode = install.status ?? 1;
|
|
77
|
+
if (exitCode !== 0) {
|
|
78
|
+
if (args.json && install.stderr) console.error(install.stderr.trim());
|
|
79
|
+
const recovery = `${command} ${commandArgs.join(' ')}`;
|
|
80
|
+
console.error(
|
|
81
|
+
`Package update failed (exit ${exitCode}). Fix the install and re-run:\n` +
|
|
82
|
+
` ${recovery}\n` +
|
|
83
|
+
`Then: ark upgrade --no-install --root ${JSON.stringify(root)}` +
|
|
84
|
+
(args.tools ? ` --tools ${args.tools}` : '') +
|
|
85
|
+
(!args.strict ? ' --no-strict' : '') +
|
|
86
|
+
(args.json ? ' --json' : '')
|
|
87
|
+
);
|
|
88
|
+
return exitCode;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Re-enter via installed CLI so the managed plan uses the newly installed package bytes.
|
|
92
|
+
let cli;
|
|
93
|
+
try {
|
|
94
|
+
cli = installedCli(root);
|
|
95
|
+
} catch {
|
|
68
96
|
console.error(
|
|
69
|
-
|
|
70
|
-
'`ark upgrade --no-install` against the installed version.'
|
|
97
|
+
'arkgate is not installed in this project after the package step. Install it, then re-run with --no-install.'
|
|
71
98
|
);
|
|
72
|
-
return
|
|
99
|
+
return 1;
|
|
73
100
|
}
|
|
74
|
-
return spawnSync(process.execPath, [
|
|
101
|
+
return spawnSync(process.execPath, [cli, ...previewArgs(args)], {
|
|
75
102
|
cwd: root,
|
|
76
103
|
stdio: 'inherit',
|
|
77
104
|
encoding: 'utf8',
|
|
@@ -111,7 +138,14 @@ export function runUpgradeCommand(args, dependencies) {
|
|
|
111
138
|
return 0;
|
|
112
139
|
}
|
|
113
140
|
|
|
114
|
-
|
|
141
|
+
let applied;
|
|
142
|
+
try {
|
|
143
|
+
applied = applyManagedUpgrade(root, plan, args.planDigest);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
146
|
+
console.error(message);
|
|
147
|
+
return 2;
|
|
148
|
+
}
|
|
115
149
|
if (applied.blocked) {
|
|
116
150
|
if (args.json) console.log(JSON.stringify(applied, null, 2));
|
|
117
151
|
else renderManagedUpgrade(applied, {
|
|
@@ -119,6 +153,14 @@ export function runUpgradeCommand(args, dependencies) {
|
|
|
119
153
|
});
|
|
120
154
|
return 1;
|
|
121
155
|
}
|
|
156
|
+
if (applied.nothingToApply && !applied.applied) {
|
|
157
|
+
if (args.json) console.log(JSON.stringify(applied, null, 2));
|
|
158
|
+
else {
|
|
159
|
+
renderManagedUpgrade(applied);
|
|
160
|
+
console.log('No managed content writes pending (optional stamp refresh needs --plan-digest).');
|
|
161
|
+
}
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
122
164
|
const verification = args.strict
|
|
123
165
|
? { mode: 'strict-merge', ...verify(root, args.json, dependencies.arkCheck, dependencies.runArkCheck) }
|
|
124
166
|
: { mode: 'skipped', exitCode: 0 };
|
|
@@ -194,9 +194,11 @@ function commandArkMcpInvocation(command) {
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
function requiredWriteOperations(relativePath) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
if (relativePath.startsWith('.grok/')) return ['write', 'search_replace'];
|
|
198
|
+
if (relativePath === '.agents/hooks.json' || relativePath.startsWith('.agents/')) {
|
|
199
|
+
return ['write_to_file', 'replace_file_content', 'multi_replace_file_content'];
|
|
200
|
+
}
|
|
201
|
+
return ['Write', 'Edit', 'MultiEdit'];
|
|
200
202
|
}
|
|
201
203
|
|
|
202
204
|
function matcherOperations(relativePath, matcher) {
|
|
@@ -248,25 +250,39 @@ function tomlArkMcpIsValid(text) {
|
|
|
248
250
|
return primary.length === 1 && tomlMcpBlockIsValid(primary[0].block);
|
|
249
251
|
}
|
|
250
252
|
|
|
253
|
+
function collectPreToolUseGroups(parsed) {
|
|
254
|
+
// Claude/Grok/Codex: { hooks: { PreToolUse: [...] } }
|
|
255
|
+
if (Array.isArray(parsed?.hooks?.PreToolUse)) return parsed.hooks.PreToolUse;
|
|
256
|
+
// Antigravity: { "named-hook": { PreToolUse: [...] }, ... }
|
|
257
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
258
|
+
const groups = [];
|
|
259
|
+
for (const value of Object.values(parsed)) {
|
|
260
|
+
if (value && typeof value === 'object' && Array.isArray(value.PreToolUse)) {
|
|
261
|
+
groups.push(...value.PreToolUse);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return groups;
|
|
265
|
+
}
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
268
|
+
|
|
251
269
|
function hookEvidence(root, relativePath) {
|
|
252
270
|
const text = readText(path.join(root, relativePath));
|
|
253
271
|
let hooks = [];
|
|
254
272
|
try {
|
|
255
|
-
const groups = JSON.parse(text)
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
);
|
|
269
|
-
}
|
|
273
|
+
const groups = collectPreToolUseGroups(JSON.parse(text));
|
|
274
|
+
hooks = groups.flatMap((group) =>
|
|
275
|
+
Array.isArray(group?.hooks)
|
|
276
|
+
? group.hooks
|
|
277
|
+
.filter((hook) => !hook?.type || hook.type === 'command')
|
|
278
|
+
.map((hook) => ({
|
|
279
|
+
hook,
|
|
280
|
+
invocation: commandArkMcpInvocation(hook?.command),
|
|
281
|
+
operations: matcherOperations(relativePath, group.matcher),
|
|
282
|
+
}))
|
|
283
|
+
.filter((entry) => entry.invocation)
|
|
284
|
+
: []
|
|
285
|
+
);
|
|
270
286
|
} catch {
|
|
271
287
|
hooks = [];
|
|
272
288
|
}
|
|
@@ -290,6 +306,29 @@ function hookEvidence(root, relativePath) {
|
|
|
290
306
|
};
|
|
291
307
|
}
|
|
292
308
|
|
|
309
|
+
/** OpenCode local MCP: `{ mcp: { ark: { type: "local", command: [...] } } }`. */
|
|
310
|
+
function opencodeMcpIsValid(text) {
|
|
311
|
+
try {
|
|
312
|
+
const server = JSON.parse(text)?.mcp?.ark;
|
|
313
|
+
if (!server || typeof server !== 'object') return false;
|
|
314
|
+
if (server.type != null && server.type !== 'local') return false;
|
|
315
|
+
if (!Array.isArray(server.command) || server.command.length === 0) return false;
|
|
316
|
+
if (!server.command.every((value) => typeof value === 'string')) return false;
|
|
317
|
+
const [command, ...args] = server.command;
|
|
318
|
+
return mcpServerRunsArk({ command, args });
|
|
319
|
+
} catch {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function opencodeMcpEvidence(root) {
|
|
325
|
+
for (const relativePath of ['opencode.json', 'opencode.jsonc']) {
|
|
326
|
+
const text = readText(path.join(root, relativePath));
|
|
327
|
+
if (text && opencodeMcpIsValid(text)) return [relativePath];
|
|
328
|
+
}
|
|
329
|
+
return [];
|
|
330
|
+
}
|
|
331
|
+
|
|
293
332
|
function mcpEvidence(root, relativePath) {
|
|
294
333
|
const text = readText(path.join(root, relativePath));
|
|
295
334
|
const valid = relativePath.endsWith('.toml')
|
|
@@ -338,6 +377,7 @@ export function detectWritePathInventory(root) {
|
|
|
338
377
|
const merge = ci.failClosed ? ci.arkWorkflowFiles : [];
|
|
339
378
|
const claudeHook = hookEvidence(root, '.claude/settings.json');
|
|
340
379
|
const grokHook = hookEvidence(root, '.grok/hooks/ark-write-gate.json');
|
|
380
|
+
const antigravityHook = hookEvidence(root, '.agents/hooks.json');
|
|
341
381
|
const hosts = {
|
|
342
382
|
claude: hostRecord(
|
|
343
383
|
claudeHook.hard,
|
|
@@ -351,12 +391,21 @@ export function detectWritePathInventory(root) {
|
|
|
351
391
|
grokHook.repair,
|
|
352
392
|
merge
|
|
353
393
|
),
|
|
394
|
+
antigravity: hostRecord(
|
|
395
|
+
antigravityHook.hard,
|
|
396
|
+
// Shared project MCP (.mcp.json) is the common advisory surface; hooks own hard write.
|
|
397
|
+
mcpEvidence(root, '.mcp.json'),
|
|
398
|
+
antigravityHook.repair,
|
|
399
|
+
merge
|
|
400
|
+
),
|
|
354
401
|
cursor: hostRecord([], mcpEvidence(root, '.cursor/mcp.json'), [], merge),
|
|
355
402
|
// Codex 0.123+ emits PreToolUse for the native apply_patch handler, but some
|
|
356
403
|
// Code Mode hosts execute deferred nested writes without dispatching that
|
|
357
404
|
// project hook. Keep the installed hook as best-effort protection; do not
|
|
358
405
|
// report a hard boundary that cannot be verified for every write surface.
|
|
359
406
|
codex: hostRecord([], codexMcpEvidence(root), [], merge),
|
|
407
|
+
// OpenCode: MCP only (plugin hooks are incomplete / subagent-bypassable).
|
|
408
|
+
opencode: hostRecord([], opencodeMcpEvidence(root), [], merge),
|
|
360
409
|
};
|
|
361
410
|
|
|
362
411
|
const evidence = emptyEvidence();
|
|
@@ -11,7 +11,7 @@ import { buildWritePathCapabilityModel } from './write-path-capabilities.mjs';
|
|
|
11
11
|
|
|
12
12
|
function installToolsForHost(activeHost) {
|
|
13
13
|
return activeHost === 'unknown'
|
|
14
|
-
? 'claude,grok,cursor,codex'
|
|
14
|
+
? 'claude,grok,antigravity,cursor,codex,opencode'
|
|
15
15
|
: activeHost;
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -109,22 +109,26 @@ export function detectWritePathCapabilities(root, explicitHost, attempt) {
|
|
|
109
109
|
),
|
|
110
110
|
};
|
|
111
111
|
} else if (mode === 'mcp-only') {
|
|
112
|
-
const
|
|
112
|
+
const honesty =
|
|
113
113
|
activeHost === 'codex'
|
|
114
114
|
? 'Codex local write is advisory (MCP + best-effort hooks.json — not a hard boundary; ' +
|
|
115
115
|
'not equivalent to Claude/Grok PreToolUse hard-write + repair). ' +
|
|
116
116
|
'The hard merge backstop is CI --strict-merge plus a required status check.'
|
|
117
|
-
:
|
|
118
|
-
'
|
|
117
|
+
: activeHost === 'opencode'
|
|
118
|
+
? 'OpenCode local write is advisory (MCP + optional experimental plugin — not a hard boundary; ' +
|
|
119
|
+
'not equivalent to Claude/Grok/Antigravity PreToolUse hard-write). ' +
|
|
120
|
+
'The hard merge backstop is CI --strict-merge plus a required status check.'
|
|
121
|
+
: `Active host ${activeHost} has advisory prepare-write/autoPatch tools, ` +
|
|
122
|
+
'but no hard write boundary; CI can report failure, while merge blocking requires provider policy.';
|
|
119
123
|
gap = {
|
|
120
124
|
id: 'write-path-mcp-only',
|
|
121
125
|
severity: 'info',
|
|
122
126
|
host: activeHost,
|
|
123
|
-
message:
|
|
127
|
+
message: honesty,
|
|
124
128
|
fix:
|
|
125
|
-
activeHost === 'codex'
|
|
129
|
+
activeHost === 'codex' || activeHost === 'opencode'
|
|
126
130
|
? 'Keep CI on --strict-merge and require the ark-check status on the default branch; ' +
|
|
127
|
-
`refresh
|
|
131
|
+
`refresh ${activeHost} MCP/skills with ${arkCommand(root, 'ark-check', `--install-agent-gates --tools ${activeHost}`)}`
|
|
128
132
|
: arkCommand(root, 'ark-check', `--install-agent-gates --tools ${tools}`),
|
|
129
133
|
};
|
|
130
134
|
}
|