arkgate 2.10.0 → 2.12.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 +107 -0
- package/README.md +21 -12
- package/SECURITY.md +3 -4
- package/bin/ark-check.mjs +41 -16
- package/bin/ark-mcp.mjs +54 -10
- package/bin/ark.mjs +87 -24
- package/bin/lib/agent-gates.mjs +68 -2090
- package/bin/lib/architecture-scan.mjs +4 -1
- package/bin/lib/baseline-key.mjs +17 -0
- package/bin/lib/ci-and-commands.mjs +386 -0
- package/bin/lib/config-warnings.mjs +22 -0
- package/bin/lib/core-layers.mjs +7 -0
- package/bin/lib/core-ratchet.mjs +3 -7
- package/bin/lib/deploy-path.mjs +205 -0
- package/bin/lib/doctor-plan.mjs +29 -5
- package/bin/lib/gate-files.mjs +223 -0
- package/bin/lib/hook-templates.mjs +99 -0
- package/bin/lib/install-migrate.mjs +442 -0
- package/bin/lib/mcp-adoption.mjs +423 -0
- package/bin/lib/presets.mjs +3 -0
- package/bin/lib/safety-diagnostics.mjs +263 -0
- package/bin/lib/scan-files.mjs +51 -6
- package/bin/lib/skill-install.mjs +259 -0
- package/bin/lib/typescript-host.mjs +88 -0
- package/bin/lib/violations.mjs +3 -3
- package/bin/lib/write-path-detect.mjs +138 -0
- package/dist/index.cjs +103 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +103 -8
- package/dist/index.js.map +1 -1
- package/dist/nestjs/index.cjs +18 -5
- package/dist/nestjs/index.cjs.map +1 -1
- package/dist/nestjs/index.d.cts +1 -1
- package/dist/nestjs/index.d.ts +1 -1
- package/dist/nestjs/index.js +18 -5
- package/dist/nestjs/index.js.map +1 -1
- package/dist/runtime/index.cjs +103 -8
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.js +103 -8
- package/dist/runtime/index.js.map +1 -1
- package/dist/{types-D6Q8WHes.d.cts → types-BZ17b9i5.d.cts} +5 -1
- package/dist/{types-D6Q8WHes.d.ts → types-BZ17b9i5.d.ts} +5 -1
- package/docs/agent-guide.md +12 -2
- package/docs/ai-gates.md +20 -2
- package/docs/package-surface.md +10 -3
- package/docs/production-hardening.md +5 -0
- package/package.json +5 -2
- package/server.json +2 -2
- package/templates/skills/ark-autopilot.md +77 -45
- package/templates/skills/ark-explain.md +2 -1
- package/templates/skills/ark-explore.md +135 -34
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migrate command runners and install agent-gate templates.
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import {
|
|
7
|
+
arkCommand,
|
|
8
|
+
detectPackageManager,
|
|
9
|
+
execCommandParts,
|
|
10
|
+
execRunner,
|
|
11
|
+
presentLockfiles,
|
|
12
|
+
} from '../ark-shared.mjs';
|
|
13
|
+
import {
|
|
14
|
+
codexPromptsDir,
|
|
15
|
+
codexConfigPath,
|
|
16
|
+
isTempOrUpgradeRoot,
|
|
17
|
+
wireCodexMcp,
|
|
18
|
+
} from './codex-home.mjs';
|
|
19
|
+
import {
|
|
20
|
+
PREFERRED_MCP_BIN,
|
|
21
|
+
claudeSettings,
|
|
22
|
+
grokHooks,
|
|
23
|
+
grokProjectConfig,
|
|
24
|
+
} from './hook-templates.mjs';
|
|
25
|
+
import {
|
|
26
|
+
hasCheckArchitectureScript,
|
|
27
|
+
ensureTypecheckScript,
|
|
28
|
+
writeTemplate,
|
|
29
|
+
} from './gate-files.mjs';
|
|
30
|
+
import {
|
|
31
|
+
packageManager,
|
|
32
|
+
agentInstructions,
|
|
33
|
+
mcpJson,
|
|
34
|
+
githubWorkflow,
|
|
35
|
+
detectCiNode,
|
|
36
|
+
cursorRule,
|
|
37
|
+
instructionRule,
|
|
38
|
+
codexTomlSnippet,
|
|
39
|
+
arkCheckCommand,
|
|
40
|
+
checkArchitectureScriptSnippet,
|
|
41
|
+
} from './ci-and-commands.mjs';
|
|
42
|
+
import {
|
|
43
|
+
normalizeToolsList,
|
|
44
|
+
resolveTools,
|
|
45
|
+
KNOWN_TOOLS,
|
|
46
|
+
SKILL_TOOL_TARGETS,
|
|
47
|
+
skillTemplates,
|
|
48
|
+
stampSkill,
|
|
49
|
+
installedSkillVersion,
|
|
50
|
+
isVersionOlder,
|
|
51
|
+
detectSkillGaps,
|
|
52
|
+
arkPackageVersion,
|
|
53
|
+
} from './skill-install.mjs';
|
|
54
|
+
import { detectDeployPathQuality } from './deploy-path.mjs';
|
|
55
|
+
import {
|
|
56
|
+
stripMcpServerArgs,
|
|
57
|
+
COMMAND_GATE_TEXT_FILES,
|
|
58
|
+
COMMAND_GATE_JSON_FILES,
|
|
59
|
+
PREFERRED_CHECK_BIN,
|
|
60
|
+
RUNNER_BEFORE_ARK,
|
|
61
|
+
} from './mcp-adoption.mjs';
|
|
62
|
+
|
|
63
|
+
export function staleRunnerGateFiles(root) {
|
|
64
|
+
const want = execRunner(root);
|
|
65
|
+
if (want === 'npx') return [];
|
|
66
|
+
const stale = [];
|
|
67
|
+
for (const rel of COMMAND_GATE_TEXT_FILES) {
|
|
68
|
+
let text;
|
|
69
|
+
try {
|
|
70
|
+
text = fs.readFileSync(path.join(root, rel), 'utf8');
|
|
71
|
+
} catch {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
RUNNER_BEFORE_ARK.lastIndex = 0;
|
|
75
|
+
let match;
|
|
76
|
+
while ((match = RUNNER_BEFORE_ARK.exec(text))) {
|
|
77
|
+
if (match[0] !== want) {
|
|
78
|
+
stale.push(rel);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const rel of COMMAND_GATE_JSON_FILES) {
|
|
84
|
+
let json;
|
|
85
|
+
try {
|
|
86
|
+
json = JSON.parse(fs.readFileSync(path.join(root, rel), 'utf8'));
|
|
87
|
+
} catch {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const ark = json?.mcpServers?.ark;
|
|
91
|
+
if (ark && ark.command && ark.command !== want.split(' ')[0]) stale.push(rel);
|
|
92
|
+
}
|
|
93
|
+
return stale;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// When more than one lockfile is present the project is ambiguous. detectPackageManager()
|
|
97
|
+
// resolves it (package-lock.json wins so a stray pnpm-lock.yaml can't hijack an npm project),
|
|
98
|
+
// but the user should know it happened and how to make it explicit — otherwise a leftover
|
|
99
|
+
// lockfile silently steers which runner every emitted command uses.
|
|
100
|
+
export function warnLockfileConflict(root) {
|
|
101
|
+
const locks = presentLockfiles(root);
|
|
102
|
+
if (locks.length <= 1) return;
|
|
103
|
+
const chosen = detectPackageManager(root);
|
|
104
|
+
const files = { pnpm: 'pnpm-lock.yaml', yarn: 'yarn.lock', npm: 'package-lock.json' };
|
|
105
|
+
console.log('');
|
|
106
|
+
console.log(
|
|
107
|
+
`Note: multiple lockfiles present (${locks.map((pm) => files[pm]).join(', ')}). Treating this`
|
|
108
|
+
);
|
|
109
|
+
console.log(
|
|
110
|
+
`as a ${chosen} project — Ark commands use "${execRunner(root)}". If that's wrong, set`
|
|
111
|
+
);
|
|
112
|
+
console.log(
|
|
113
|
+
'"packageManager" in package.json (e.g. "pnpm@9") to declare it, or remove the stray lockfile.'
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// --migrate-commands: rewrite ONLY the Ark command runner in existing gate files to the
|
|
118
|
+
// project's package manager (no --force clobber). Closes the upgrade gap where a repo that
|
|
119
|
+
// adopted before the package-manager-aware templates keeps a stale `npx`.
|
|
120
|
+
// Also normalizes MCP JSON to a single preferred bin (arkgate-mcp), stripping any dual
|
|
121
|
+
// ark-mcp + arkgate-mcp residue left by partial renames during package identity cutover.
|
|
122
|
+
export function runMigrateCommands(root) {
|
|
123
|
+
const runner = execRunner(root);
|
|
124
|
+
const changed = [];
|
|
125
|
+
for (const rel of COMMAND_GATE_TEXT_FILES) {
|
|
126
|
+
const full = path.join(root, rel);
|
|
127
|
+
let text;
|
|
128
|
+
try {
|
|
129
|
+
text = fs.readFileSync(full, 'utf8');
|
|
130
|
+
} catch {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
let next = text.replace(RUNNER_BEFORE_ARK, runner);
|
|
134
|
+
// Prefer primary product bins in command strings (aliases still work if left alone).
|
|
135
|
+
next = next
|
|
136
|
+
.replace(/\bark-mcp\b/g, PREFERRED_MCP_BIN)
|
|
137
|
+
.replace(/\bark-check\b/g, PREFERRED_CHECK_BIN);
|
|
138
|
+
// Do not blanket-replace bare `ark` — it appears in prose ("Ark check", product name).
|
|
139
|
+
if (next !== text) {
|
|
140
|
+
fs.writeFileSync(full, next);
|
|
141
|
+
changed.push(rel);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
for (const rel of COMMAND_GATE_JSON_FILES) {
|
|
145
|
+
const full = path.join(root, rel);
|
|
146
|
+
let json;
|
|
147
|
+
try {
|
|
148
|
+
json = JSON.parse(fs.readFileSync(full, 'utf8'));
|
|
149
|
+
} catch {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const ark = json?.mcpServers?.ark;
|
|
153
|
+
if (!ark) continue;
|
|
154
|
+
const binArgs = stripMcpServerArgs(ark.args);
|
|
155
|
+
const parts = execCommandParts(root, PREFERRED_MCP_BIN, binArgs);
|
|
156
|
+
if (ark.command !== parts.command || JSON.stringify(ark.args) !== JSON.stringify(parts.args)) {
|
|
157
|
+
json.mcpServers.ark = { ...ark, ...parts };
|
|
158
|
+
fs.writeFileSync(full, `${JSON.stringify(json, null, 2)}\n`);
|
|
159
|
+
changed.push(rel);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const pm = runner === 'pnpm exec' || runner.startsWith('pnpm ') ? 'pnpm' : runner;
|
|
163
|
+
console.log(`Migrated ArkGate command runners to "${pm}" and normalized MCP bins in gate files.`);
|
|
164
|
+
if (changed.length === 0) {
|
|
165
|
+
console.log(' Nothing to change — runners and MCP bins already look correct.');
|
|
166
|
+
} else {
|
|
167
|
+
for (const rel of changed) console.log(` updated ${rel}`);
|
|
168
|
+
console.log(
|
|
169
|
+
` (runner + single MCP bin \`${PREFERRED_MCP_BIN}\`; customized non-command content is untouched.)`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
warnLockfileConflict(root);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function runInstallAgentGates(args) {
|
|
176
|
+
const root = args.root;
|
|
177
|
+
if (args.migrateCommands) {
|
|
178
|
+
runMigrateCommands(root);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (args.tools != null) {
|
|
182
|
+
const list = normalizeToolsList(args.tools);
|
|
183
|
+
args.tools = list;
|
|
184
|
+
const unknown = list.filter((tool) => !KNOWN_TOOLS.includes(tool));
|
|
185
|
+
if (list.length === 0 || unknown.length > 0) {
|
|
186
|
+
console.error(
|
|
187
|
+
`--tools expects a comma-separated subset of: ${KNOWN_TOOLS.join(', ')}` +
|
|
188
|
+
(unknown.length > 0 ? ` (unknown: ${unknown.join(', ')})` : '')
|
|
189
|
+
);
|
|
190
|
+
process.exitCode = 2;
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const pm = packageManager(root);
|
|
195
|
+
const hasCheckScript = hasCheckArchitectureScript(root);
|
|
196
|
+
const { tools, source } = resolveTools(args);
|
|
197
|
+
const toolSource =
|
|
198
|
+
source === 'explicit'
|
|
199
|
+
? 'from --tools'
|
|
200
|
+
: source === 'detected'
|
|
201
|
+
? 'auto-detected from config dirs'
|
|
202
|
+
: 'default set — no agent config dirs found';
|
|
203
|
+
console.log(`Agent gates for: ${[...tools].sort().join(', ')} (${toolSource})`);
|
|
204
|
+
const templates = [];
|
|
205
|
+
// --skills-only refreshes just the canonical /ark-* skills, which are safe to
|
|
206
|
+
// overwrite (they track the package). The gate/instruction files (AGENTS.md,
|
|
207
|
+
// settings.json, CI workflow, rules) are the ones users customize, so a plain
|
|
208
|
+
// `--force` clobbers them — this is the safe way to pick up new skill versions.
|
|
209
|
+
// Do not mutate package.json under --skills-only (typecheck bootstrap is gates/CI).
|
|
210
|
+
if (!args.skillsOnly) {
|
|
211
|
+
// Bootstrap typecheck before CI template so generated workflow includes the step.
|
|
212
|
+
const typecheckBootstrap = ensureTypecheckScript(root, { write: true });
|
|
213
|
+
if (typecheckBootstrap.changed && !args.json) {
|
|
214
|
+
console.log(
|
|
215
|
+
`Added package.json script "typecheck": "${typecheckBootstrap.script}" (tsconfig present; local/CI parity).`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
// Base gates: tool-agnostic contract + CI backstop, always written.
|
|
219
|
+
templates.push(['AGENTS.md', agentInstructions(root)]);
|
|
220
|
+
templates.push(['.mcp.json', mcpJson(root)]);
|
|
221
|
+
templates.push([
|
|
222
|
+
'.github/workflows/ark-check.yml',
|
|
223
|
+
(() => {
|
|
224
|
+
const deploy = detectDeployPathQuality(root);
|
|
225
|
+
return githubWorkflow(pm, detectCiNode(root), {
|
|
226
|
+
hasLintScript: deploy.hasLintScript,
|
|
227
|
+
hasTypecheckScript: deploy.hasTypecheckScript,
|
|
228
|
+
});
|
|
229
|
+
})(),
|
|
230
|
+
]);
|
|
231
|
+
if (tools.has('cursor')) {
|
|
232
|
+
templates.push(['.cursor/mcp.json', mcpJson(root)]);
|
|
233
|
+
templates.push(['.cursor/rules/ark.mdc', cursorRule(root)]);
|
|
234
|
+
}
|
|
235
|
+
if (tools.has('claude')) {
|
|
236
|
+
templates.push(['.claude/settings.json', claudeSettings(root)]);
|
|
237
|
+
}
|
|
238
|
+
if (tools.has('codex')) {
|
|
239
|
+
templates.push(['docs/ark-codex-config.toml', codexTomlSnippet(root)]);
|
|
240
|
+
}
|
|
241
|
+
if (tools.has('grok')) {
|
|
242
|
+
templates.push(['.grok/config.toml', grokProjectConfig(root)]);
|
|
243
|
+
templates.push(['.grok/hooks/ark-write-gate.json', grokHooks(root)]);
|
|
244
|
+
}
|
|
245
|
+
// Instruction-tier hosts: one shared rule text, host-specific path.
|
|
246
|
+
if (tools.has('windsurf')) {
|
|
247
|
+
templates.push(['.windsurf/rules/ark.md', instructionRule(root)]);
|
|
248
|
+
}
|
|
249
|
+
if (tools.has('cline')) {
|
|
250
|
+
templates.push(['.clinerules/ark.md', instructionRule(root)]);
|
|
251
|
+
}
|
|
252
|
+
if (tools.has('copilot')) {
|
|
253
|
+
templates.push(['.github/copilot-instructions.md', instructionRule(root)]);
|
|
254
|
+
}
|
|
255
|
+
if (tools.has('kiro')) {
|
|
256
|
+
templates.push(['.kiro/steering/ark.md', instructionRule(root)]);
|
|
257
|
+
}
|
|
258
|
+
if (tools.has('roo')) {
|
|
259
|
+
templates.push(['.roo/rules/ark.md', instructionRule(root)]);
|
|
260
|
+
}
|
|
261
|
+
if (tools.has('continue')) {
|
|
262
|
+
templates.push(['.continue/rules/ark.md', instructionRule(root)]);
|
|
263
|
+
}
|
|
264
|
+
// Gemini CLI reads GEMINI.md as its primary project context (it also reads
|
|
265
|
+
// AGENTS.md, but GEMINI.md wins when both are present), so the rule lives there.
|
|
266
|
+
if (tools.has('gemini')) {
|
|
267
|
+
templates.push(['GEMINI.md', instructionRule(root)]);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// /ark-* skills for every detected tool that supports project-level commands.
|
|
271
|
+
// Stamp each with the shipping version so a later ark-check can flag skills
|
|
272
|
+
// left behind by an older Ark (see detectSkillGaps) without nagging about
|
|
273
|
+
// user edits to the body.
|
|
274
|
+
const version = arkPackageVersion();
|
|
275
|
+
const skills = skillTemplates().map(([name, content]) => [name, stampSkill(content, version)]);
|
|
276
|
+
const skillPaths = new Set();
|
|
277
|
+
for (const tool of tools) {
|
|
278
|
+
const target = SKILL_TOOL_TARGETS[tool];
|
|
279
|
+
if (!target) continue;
|
|
280
|
+
for (const [name, content] of skills) {
|
|
281
|
+
const relativePath = target(name);
|
|
282
|
+
skillPaths.add(relativePath);
|
|
283
|
+
templates.push([relativePath, content]);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const results = templates.map(([relativePath, content]) =>
|
|
288
|
+
writeTemplate(root, relativePath, content, args.force)
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
console.log('Ark agent gate templates:');
|
|
292
|
+
let staleSkipped = 0;
|
|
293
|
+
for (const result of results) {
|
|
294
|
+
const marker =
|
|
295
|
+
result.status === 'written'
|
|
296
|
+
? 'wrote'
|
|
297
|
+
: result.status === 'merged'
|
|
298
|
+
? 'merged'
|
|
299
|
+
: result.status === 'skipped-non-ark'
|
|
300
|
+
? 'kept'
|
|
301
|
+
: result.status === 'failed'
|
|
302
|
+
? 'FAILED'
|
|
303
|
+
: 'skipped';
|
|
304
|
+
// A skipped skill reads as "you're fine" — but it may be a version behind.
|
|
305
|
+
// Say which, so the user isn't left guessing (and knows the safe refresh cmd).
|
|
306
|
+
let note = '';
|
|
307
|
+
if (result.status === 'skipped' && skillPaths.has(result.relativePath) && version) {
|
|
308
|
+
const installed = installedSkillVersion(path.join(root, result.relativePath));
|
|
309
|
+
if (installed === null || isVersionOlder(installed, version)) {
|
|
310
|
+
staleSkipped += 1;
|
|
311
|
+
note = ` (stale: ${installed ?? 'no stamp'} < ${version})`;
|
|
312
|
+
} else {
|
|
313
|
+
note = ' (up to date)';
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
console.log(` ${marker.padEnd(7)} ${result.relativePath}${note}`);
|
|
317
|
+
}
|
|
318
|
+
if (staleSkipped > 0 && !args.skillsOnly) {
|
|
319
|
+
console.log('');
|
|
320
|
+
console.log(
|
|
321
|
+
` ${staleSkipped} skill(s) are outdated but were left untouched. Refresh them with:`
|
|
322
|
+
);
|
|
323
|
+
console.log(` ${arkCommand(root, 'ark-check', '--install-agent-gates --skills-only --force')}`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// --codex-home writes the canonical skills straight to $CODEX_HOME/prompts.
|
|
327
|
+
// Codex reads prompts from there (not the repo), so this is the only way to
|
|
328
|
+
// refresh them for a repo that isn't itself configured for Codex. It writes to
|
|
329
|
+
// the user's home dir, hence explicit opt-in rather than part of a normal run.
|
|
330
|
+
const homeResults = [];
|
|
331
|
+
if (args.codexHome) {
|
|
332
|
+
const dir = codexPromptsDir();
|
|
333
|
+
console.log('');
|
|
334
|
+
console.log(`Codex home skills (${dir}):`);
|
|
335
|
+
try {
|
|
336
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
337
|
+
} catch (error) {
|
|
338
|
+
console.error(` FAILED to create ${dir} (${error.message})`);
|
|
339
|
+
homeResults.push({ status: 'failed' });
|
|
340
|
+
}
|
|
341
|
+
if (homeResults.length === 0) {
|
|
342
|
+
for (const [name, content] of skills) {
|
|
343
|
+
const file = path.join(dir, `${name}.md`);
|
|
344
|
+
if (fs.existsSync(file) && !args.force) {
|
|
345
|
+
const installed = installedSkillVersion(file);
|
|
346
|
+
const behind = installed === null || (version && isVersionOlder(installed, version));
|
|
347
|
+
const note = behind
|
|
348
|
+
? ` (stale: ${installed ?? 'no stamp'} < ${version}; use --force)`
|
|
349
|
+
: ' (up to date)';
|
|
350
|
+
console.log(` ${'skipped'.padEnd(7)} ${name}.md${note}`);
|
|
351
|
+
homeResults.push({ status: 'skipped' });
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
fs.writeFileSync(file, content);
|
|
356
|
+
console.log(` ${'wrote'.padEnd(7)} ${name}.md`);
|
|
357
|
+
homeResults.push({ status: 'written' });
|
|
358
|
+
} catch (error) {
|
|
359
|
+
console.log(` ${'FAILED'.padEnd(7)} ${name}.md (${error.message})`);
|
|
360
|
+
homeResults.push({ status: 'failed' });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Auto-wire the ark MCP server into Codex's home config.toml. Claude and Cursor get
|
|
367
|
+
// machine-readable registrations (.claude/settings.json, .cursor/mcp.json) written as repo
|
|
368
|
+
// templates above; Codex reads MCP servers only from ~/.codex/config.toml, so it needs a
|
|
369
|
+
// home-dir merge instead. Fires whenever Codex is in play so `ark://manifest` is live
|
|
370
|
+
// without a manual copy step.
|
|
371
|
+
//
|
|
372
|
+
// Skip home-dir mutation when the project root is a temp/upgrade scratch *and*
|
|
373
|
+
// CODEX_HOME is the default (~/.codex). Fixtures must not rewrite the developer's
|
|
374
|
+
// real Codex config. When CODEX_HOME is redirected (unit tests set a temp home) or
|
|
375
|
+
// --codex-home is explicit, wire as usual.
|
|
376
|
+
let codexMcp = null;
|
|
377
|
+
const wantCodexWire = tools.has('codex') || args.codexHome;
|
|
378
|
+
const skipHomeWire =
|
|
379
|
+
wantCodexWire &&
|
|
380
|
+
isTempOrUpgradeRoot(root) &&
|
|
381
|
+
!args.codexHome &&
|
|
382
|
+
!process.env.CODEX_HOME;
|
|
383
|
+
if (wantCodexWire && !skipHomeWire) {
|
|
384
|
+
codexMcp = wireCodexMcp(root, args.force);
|
|
385
|
+
console.log('');
|
|
386
|
+
console.log(`Codex MCP registration (${codexMcp.file}):`);
|
|
387
|
+
if (codexMcp.status === 'written-multi') {
|
|
388
|
+
console.log(
|
|
389
|
+
` ${'wrote'.padEnd(7)} [mcp_servers.${codexMcp.table}] (multi-project — primary [mcp_servers.ark] left unchanged; --force rebinds primary)`
|
|
390
|
+
);
|
|
391
|
+
} else if (codexMcp.status === 'skipped') {
|
|
392
|
+
console.log(` ${'skipped'.padEnd(7)} [mcp_servers.ark] already present (use --force to overwrite)`);
|
|
393
|
+
} else if (codexMcp.status === 'failed') {
|
|
394
|
+
console.log(` ${'FAILED'.padEnd(7)} [mcp_servers.ark] (${codexMcp.message})`);
|
|
395
|
+
} else {
|
|
396
|
+
const verb = codexMcp.status === 'updated' ? 'updated' : 'wrote';
|
|
397
|
+
console.log(` ${verb.padEnd(7)} [mcp_servers.ark] with absolute paths`);
|
|
398
|
+
console.log(' RESTART Codex — it does not hot-load MCP servers.');
|
|
399
|
+
console.log(' Then expect: resource ark://manifest + tools validate_code, ark_check, ark_coverage, ark_place.');
|
|
400
|
+
}
|
|
401
|
+
} else if (skipHomeWire) {
|
|
402
|
+
codexMcp = { status: 'skipped', file: codexConfigPath(), reason: 'temp-root' };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Repo templates + explicit --codex-home skill writes are hard failures.
|
|
406
|
+
// Home MCP wire is best-effort: unreadable ~/.codex (sandbox, permissions) must not
|
|
407
|
+
// mark an otherwise successful repo gate install as failed.
|
|
408
|
+
const hardFailed = [...results, ...homeResults].filter((result) => result.status === 'failed');
|
|
409
|
+
if (hardFailed.length > 0) {
|
|
410
|
+
console.error(`\nFailed to write ${hardFailed.length} template(s).`);
|
|
411
|
+
process.exitCode = 1;
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (codexMcp?.status === 'failed') {
|
|
415
|
+
console.error(
|
|
416
|
+
`\nWarning: Codex home MCP registration failed (${codexMcp.message}). Repo gates were written; fix ~/.codex access or re-run with --tools codex --force.`
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
console.log('');
|
|
420
|
+
console.log('Next steps:');
|
|
421
|
+
console.log(' 1. Review the generated files and commit the ones that match your tools.');
|
|
422
|
+
console.log(` 2. Run: ${arkCheckCommand(root)}`);
|
|
423
|
+
if (!hasCheckScript) {
|
|
424
|
+
console.log(' 3. Add the package.json alias if you want `run check:architecture`:');
|
|
425
|
+
console.log(` ${checkArchitectureScriptSnippet(root)}`);
|
|
426
|
+
}
|
|
427
|
+
if ((tools.has('codex') || args.codexHome)) {
|
|
428
|
+
console.log('');
|
|
429
|
+
if (codexMcp && codexMcp.status !== 'failed') {
|
|
430
|
+
console.log(` Codex: ark MCP registered in ${codexMcp.file} — restart Codex so \`ark://manifest\` loads.`);
|
|
431
|
+
}
|
|
432
|
+
if (args.codexHome) {
|
|
433
|
+
console.log(` Codex: refreshed the /ark-* skills in ${codexPromptsDir()} — Codex loads them from there.`);
|
|
434
|
+
} else if (skills.length > 0) {
|
|
435
|
+
console.log(' Codex loads slash-command prompts from $CODEX_HOME/prompts (~/.codex/prompts),');
|
|
436
|
+
console.log(' not the repo. Install the /ark-* skills there with:');
|
|
437
|
+
console.log(` ${arkCommand(root, 'ark-check', '--install-agent-gates --codex-home')}`);
|
|
438
|
+
console.log(' (writes to your home dir; agents driving this setup should offer to run it).');
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
warnLockfileConflict(root);
|
|
442
|
+
}
|