@haposoft/cafekit 0.8.15 → 0.8.17
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/README.md +43 -4
- package/bin/install.js +698 -114
- package/package.json +1 -1
- package/src/claude/CLAUDE.md +4 -0
- package/src/claude/hooks/lib/config.cjs +0 -1
- package/src/claude/hooks/lib/context.cjs +2 -2
- package/src/claude/hooks/rules.cjs +7 -0
- package/src/claude/migration-manifest.json +11 -3
- package/src/claude/rules/skill-domain-routing.md +87 -0
- package/src/claude/rules/skill-workflow-routing.md +84 -0
- package/src/claude/scripts/generate-skill-catalog.cjs +180 -0
- package/src/claude/settings/settings.json +0 -4
- package/src/opencode/AGENTS.md +140 -0
- package/src/antigravity/GEMINI.md +0 -162
- package/src/antigravity/workflows/code.md +0 -16
- package/src/antigravity/workflows/docs-init.md +0 -432
- package/src/antigravity/workflows/docs-update.md +0 -245
- package/src/antigravity/workflows/impact-analysis-output-example.md +0 -313
- package/src/antigravity/workflows/impact-analysis.md +0 -730
- package/src/antigravity/workflows/review.md +0 -15
- package/src/antigravity/workflows/spec-design.md +0 -220
- package/src/antigravity/workflows/spec-init.md +0 -78
- package/src/antigravity/workflows/spec-requirements.md +0 -122
- package/src/antigravity/workflows/spec-status.md +0 -95
- package/src/antigravity/workflows/spec-tasks.md +0 -156
- package/src/antigravity/workflows/spec-validate.md +0 -106
- package/src/antigravity/workflows/test.md +0 -13
- package/src/claude/hooks/lib/skill-router-routes.cjs +0 -196
- package/src/claude/hooks/skill-router.cjs +0 -68
package/bin/install.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Supported platforms:
|
|
8
8
|
* - claude: Claude Code (.claude/)
|
|
9
|
-
* -
|
|
9
|
+
* - opencode: OpenCode (.opencode/)
|
|
10
10
|
*
|
|
11
11
|
* To add a new platform:
|
|
12
12
|
* 1. Add to PLATFORMS registry below
|
|
@@ -57,68 +57,100 @@ const CLAUDE_MIGRATION_MANIFEST = loadClaudeMigrationManifest();
|
|
|
57
57
|
const DEPENDENCY_TEMPLATES = {
|
|
58
58
|
commands: {
|
|
59
59
|
claude: {},
|
|
60
|
-
|
|
61
|
-
'code.md': `---
|
|
62
|
-
description: Implement approved work from specification tasks and then hand off to test and hapo:code-review.
|
|
63
|
-
allowed-tools: Read, Glob, Grep, Edit, Write, Bash
|
|
64
|
-
argument-hint: <feature-name>
|
|
65
|
-
---
|
|
66
|
-
|
|
67
|
-
# /code
|
|
68
|
-
|
|
69
|
-
Use this workflow after /spec-tasks.
|
|
70
|
-
|
|
71
|
-
1. Read .specs/$ARGUMENTS/tasks.md and identify the next pending task.
|
|
72
|
-
2. Implement only that task following project standards.
|
|
73
|
-
3. Run /test.
|
|
74
|
-
4. Run /hapo:code-review.
|
|
75
|
-
|
|
76
|
-
Preferred flow: /spec-init -> /spec-requirements -> /spec-design -> /spec-tasks -> /code -> /test -> /hapo:code-review
|
|
77
|
-
`,
|
|
78
|
-
'test.md': `---
|
|
79
|
-
description: Run project tests and report failures concisely.
|
|
80
|
-
allowed-tools: Bash, Read, Grep
|
|
81
|
-
argument-hint: [scope]
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
# /test
|
|
85
|
-
|
|
86
|
-
Run the project's test command and report:
|
|
87
|
-
- total passed/failed
|
|
88
|
-
- failing test names
|
|
89
|
-
- root cause hints
|
|
90
|
-
- next fix action
|
|
91
|
-
`,
|
|
92
|
-
}
|
|
60
|
+
opencode: {}
|
|
93
61
|
},
|
|
94
62
|
agents: {
|
|
95
63
|
claude: {},
|
|
96
|
-
|
|
97
|
-
'frontend-specialist.md': `---
|
|
98
|
-
name: frontend-specialist
|
|
99
|
-
description: Implement approved UI and interaction tasks from specifications.
|
|
100
|
-
---
|
|
101
|
-
|
|
102
|
-
Implement UI tasks from approved specs with accessibility and responsive behavior.
|
|
103
|
-
`,
|
|
104
|
-
'test-engineer.md': `---
|
|
105
|
-
name: test-engineer
|
|
106
|
-
description: Execute tests and report reliability issues.
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
Run test suites, highlight failures, and propose precise fixes.
|
|
110
|
-
`,
|
|
111
|
-
'code-archaeologist.md': `---
|
|
112
|
-
name: code-archaeologist
|
|
113
|
-
description: Review recent changes for regressions and hidden impacts.
|
|
114
|
-
---
|
|
115
|
-
|
|
116
|
-
Inspect changed code paths, dependencies, and potential regressions.
|
|
117
|
-
`
|
|
118
|
-
}
|
|
64
|
+
opencode: {}
|
|
119
65
|
}
|
|
120
66
|
};
|
|
121
67
|
|
|
68
|
+
const OPENCODE_TOOL_MAP = {
|
|
69
|
+
Read: 'read',
|
|
70
|
+
Glob: 'glob',
|
|
71
|
+
Grep: 'grep',
|
|
72
|
+
Edit: 'edit',
|
|
73
|
+
Write: 'edit',
|
|
74
|
+
MultiEdit: 'edit',
|
|
75
|
+
Bash: 'bash',
|
|
76
|
+
WebFetch: 'webfetch',
|
|
77
|
+
WebSearch: 'websearch',
|
|
78
|
+
NotebookEdit: 'edit'
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const OPENCODE_COMMAND_TEMPLATES = [
|
|
82
|
+
{
|
|
83
|
+
fileName: 'brainstorm.md',
|
|
84
|
+
skillName: 'brainstorm',
|
|
85
|
+
agent: 'brainstormer',
|
|
86
|
+
subtask: true,
|
|
87
|
+
description: 'Explore and narrow an idea before writing a CafeKit spec.'
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
fileName: 'specs.md',
|
|
91
|
+
skillName: 'specs',
|
|
92
|
+
agent: 'spec-maker',
|
|
93
|
+
subtask: true,
|
|
94
|
+
description: 'Create, update, validate, or approve a CafeKit specification.'
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
fileName: 'develop.md',
|
|
98
|
+
skillName: 'develop',
|
|
99
|
+
agent: 'god-developer',
|
|
100
|
+
subtask: true,
|
|
101
|
+
description: 'Implement approved CafeKit spec tasks with scope fidelity.'
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
fileName: 'test.md',
|
|
105
|
+
skillName: 'test',
|
|
106
|
+
agent: 'test-runner',
|
|
107
|
+
subtask: true,
|
|
108
|
+
description: 'Run CafeKit verification, QA, and evidence collection.'
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
fileName: 'code-review.md',
|
|
112
|
+
skillName: 'code-review',
|
|
113
|
+
agent: 'code-auditor',
|
|
114
|
+
subtask: true,
|
|
115
|
+
description: 'Review implementation against code quality and spec compliance.'
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
fileName: 'debug.md',
|
|
119
|
+
skillName: 'debug',
|
|
120
|
+
agent: 'debugger',
|
|
121
|
+
subtask: true,
|
|
122
|
+
description: 'Diagnose a bug or failure with evidence before fixing.'
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
fileName: 'hotfix.md',
|
|
126
|
+
skillName: 'hotfix',
|
|
127
|
+
agent: 'god-developer',
|
|
128
|
+
subtask: true,
|
|
129
|
+
description: 'Apply a scout-first, narrow production hotfix.'
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
fileName: 'docs.md',
|
|
133
|
+
skillName: 'docs',
|
|
134
|
+
agent: 'docs-keeper',
|
|
135
|
+
subtask: true,
|
|
136
|
+
description: 'Create, update, or reconstruct source-backed project documentation.'
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
fileName: 'inspect.md',
|
|
140
|
+
skillName: 'inspect',
|
|
141
|
+
agent: 'inspector',
|
|
142
|
+
subtask: true,
|
|
143
|
+
description: 'Inspect a codebase, artifact, or external context before planning.'
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
fileName: 'generate-graph.md',
|
|
147
|
+
skillName: 'generate-graph',
|
|
148
|
+
agent: 'god-developer',
|
|
149
|
+
subtask: true,
|
|
150
|
+
description: 'Generate technical diagrams from project context.'
|
|
151
|
+
}
|
|
152
|
+
];
|
|
153
|
+
|
|
122
154
|
// ═══════════════════════════════════════════════════════════
|
|
123
155
|
// PLATFORM REGISTRY - Add new platforms here
|
|
124
156
|
// ═══════════════════════════════════════════════════════════
|
|
@@ -136,18 +168,19 @@ const PLATFORMS = {
|
|
|
136
168
|
sourceDir: 'claude', // Maps to src/claude/
|
|
137
169
|
sourceSubdir: 'commands' // Source subfolder within src/claude/
|
|
138
170
|
},
|
|
139
|
-
|
|
140
|
-
id: '
|
|
141
|
-
name: '
|
|
142
|
-
description: '
|
|
143
|
-
folder: '.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
171
|
+
opencode: {
|
|
172
|
+
id: 'opencode',
|
|
173
|
+
name: 'OpenCode',
|
|
174
|
+
description: 'OpenCode terminal AI coding agent',
|
|
175
|
+
folder: '.opencode',
|
|
176
|
+
detectFiles: ['.opencode', 'opencode.json', 'opencode.jsonc'],
|
|
177
|
+
commandsDir: '.opencode/commands',
|
|
178
|
+
skillsDir: '.claude/skills',
|
|
179
|
+
agentsDir: '.opencode/agents',
|
|
180
|
+
skillsRef: '.claude/skills',
|
|
148
181
|
commandPrefix: '/',
|
|
149
|
-
sourceDir: '
|
|
150
|
-
sourceSubdir: '
|
|
182
|
+
sourceDir: 'claude',
|
|
183
|
+
sourceSubdir: 'archive-command'
|
|
151
184
|
}
|
|
152
185
|
// Add new platforms here:
|
|
153
186
|
// cursor: {
|
|
@@ -169,7 +202,8 @@ function detectPlatforms() {
|
|
|
169
202
|
const detected = [];
|
|
170
203
|
|
|
171
204
|
for (const [key, config] of Object.entries(PLATFORMS)) {
|
|
172
|
-
|
|
205
|
+
const markers = Array.isArray(config.detectFiles) ? config.detectFiles : [config.folder];
|
|
206
|
+
if (markers.some(marker => fs.existsSync(marker))) {
|
|
173
207
|
detected.push(key);
|
|
174
208
|
}
|
|
175
209
|
}
|
|
@@ -189,6 +223,18 @@ function getPlatformKeys() {
|
|
|
189
223
|
return Object.keys(PLATFORMS);
|
|
190
224
|
}
|
|
191
225
|
|
|
226
|
+
function isClaudeCompatibleRuntime(platformKey) {
|
|
227
|
+
return platformKey === 'claude' || platformKey === 'opencode';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function getClaudeSupportTargetDir(platformKey, subdir) {
|
|
231
|
+
if (platformKey === 'opencode') {
|
|
232
|
+
return path.join('.claude', subdir);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return path.join(PLATFORMS[platformKey].folder, subdir);
|
|
236
|
+
}
|
|
237
|
+
|
|
192
238
|
function parseInstallerArgs(argv) {
|
|
193
239
|
const args = {
|
|
194
240
|
upgrade: false
|
|
@@ -205,6 +251,149 @@ function parseInstallerArgs(argv) {
|
|
|
205
251
|
return args;
|
|
206
252
|
}
|
|
207
253
|
|
|
254
|
+
function splitFrontmatter(content) {
|
|
255
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
256
|
+
|
|
257
|
+
if (!match) {
|
|
258
|
+
return {
|
|
259
|
+
frontmatter: {},
|
|
260
|
+
body: content
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const frontmatter = {};
|
|
265
|
+
match[1].split(/\r?\n/).forEach(line => {
|
|
266
|
+
const separatorIndex = line.indexOf(':');
|
|
267
|
+
if (separatorIndex === -1) return;
|
|
268
|
+
|
|
269
|
+
const key = line.slice(0, separatorIndex).trim();
|
|
270
|
+
let value = line.slice(separatorIndex + 1).trim();
|
|
271
|
+
|
|
272
|
+
if (
|
|
273
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
274
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
275
|
+
) {
|
|
276
|
+
value = value.slice(1, -1);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (key) {
|
|
280
|
+
frontmatter[key] = value;
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
frontmatter,
|
|
286
|
+
body: content.slice(match[0].length)
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function formatOpenCodeValue(value) {
|
|
291
|
+
return JSON.stringify(String(value || ''));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function normalizeOpenCodeBody(content) {
|
|
295
|
+
return content
|
|
296
|
+
.replace(/\.claude\/agents\//g, '.opencode/agents/')
|
|
297
|
+
.replace(/CLAUDE\.md/g, 'AGENTS.md');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function getOpenCodeToolEntries(toolsValue) {
|
|
301
|
+
const toolNames = String(toolsValue || '')
|
|
302
|
+
.split(',')
|
|
303
|
+
.map(tool => tool.trim())
|
|
304
|
+
.filter(Boolean);
|
|
305
|
+
|
|
306
|
+
const mappedTools = new Set();
|
|
307
|
+
|
|
308
|
+
toolNames.forEach(toolName => {
|
|
309
|
+
const mapped = OPENCODE_TOOL_MAP[toolName];
|
|
310
|
+
if (mapped) {
|
|
311
|
+
mappedTools.add(mapped);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
return Array.from(mappedTools).sort();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function convertOpenCodeAgentContent(content, fileName) {
|
|
319
|
+
const { frontmatter, body } = splitFrontmatter(content);
|
|
320
|
+
const agentName = frontmatter.name || path.basename(fileName, '.md');
|
|
321
|
+
const description = frontmatter.description || `CafeKit agent: ${agentName}`;
|
|
322
|
+
const mode = agentName === 'brainstormer' ? 'primary' : 'subagent';
|
|
323
|
+
const permissionEntries = new Set(getOpenCodeToolEntries(frontmatter.tools));
|
|
324
|
+
permissionEntries.add('skill');
|
|
325
|
+
permissionEntries.add('task');
|
|
326
|
+
const lines = [
|
|
327
|
+
'---',
|
|
328
|
+
`description: ${formatOpenCodeValue(description)}`,
|
|
329
|
+
`mode: ${mode}`
|
|
330
|
+
];
|
|
331
|
+
|
|
332
|
+
if (permissionEntries.size > 0) {
|
|
333
|
+
lines.push('permission:');
|
|
334
|
+
Array.from(permissionEntries).sort().forEach(permissionName => {
|
|
335
|
+
lines.push(` ${permissionName}: allow`);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
lines.push('---', '');
|
|
340
|
+
|
|
341
|
+
return `${lines.join('\n')}${normalizeOpenCodeBody(body).trimStart()}`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function convertOpenCodeCommandContent(content, fileName) {
|
|
345
|
+
const { frontmatter, body } = splitFrontmatter(content);
|
|
346
|
+
const commandName = path.basename(fileName, '.md');
|
|
347
|
+
const description = frontmatter.description || `CafeKit command: ${commandName}`;
|
|
348
|
+
const lines = [
|
|
349
|
+
'---',
|
|
350
|
+
`description: ${formatOpenCodeValue(description)}`
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
if (frontmatter.agent) {
|
|
354
|
+
lines.push(`agent: ${formatOpenCodeValue(frontmatter.agent)}`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
lines.push('---', '');
|
|
358
|
+
|
|
359
|
+
return `${lines.join('\n')}${normalizeOpenCodeBody(body).trimStart()}`;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function createOpenCodeSkillCommandContent(command) {
|
|
363
|
+
const lines = [
|
|
364
|
+
'---',
|
|
365
|
+
`description: ${formatOpenCodeValue(command.description)}`
|
|
366
|
+
];
|
|
367
|
+
|
|
368
|
+
if (command.agent) {
|
|
369
|
+
lines.push(`agent: ${formatOpenCodeValue(command.agent)}`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (typeof command.subtask === 'boolean') {
|
|
373
|
+
lines.push(`subtask: ${command.subtask ? 'true' : 'false'}`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (command.model) {
|
|
377
|
+
lines.push(`model: ${formatOpenCodeValue(command.model)}`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
lines.push(
|
|
381
|
+
'---',
|
|
382
|
+
'',
|
|
383
|
+
`Use the CafeKit skill at \`.claude/skills/${command.skillName}/SKILL.md\`.`,
|
|
384
|
+
'',
|
|
385
|
+
'Read that skill first, then execute its workflow with these arguments:',
|
|
386
|
+
'',
|
|
387
|
+
'$ARGUMENTS',
|
|
388
|
+
'',
|
|
389
|
+
'If the skill references supporting files, resolve them relative to the skill directory.',
|
|
390
|
+
'Keep the response aligned with the skill instructions and the current project context.',
|
|
391
|
+
''
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
return lines.join('\n');
|
|
395
|
+
}
|
|
396
|
+
|
|
208
397
|
// ═══════════════════════════════════════════════════════════
|
|
209
398
|
// USER INTERACTION
|
|
210
399
|
// ═══════════════════════════════════════════════════════════
|
|
@@ -409,18 +598,8 @@ function getPlatformSpecFiles(platformKey) {
|
|
|
409
598
|
];
|
|
410
599
|
}
|
|
411
600
|
|
|
412
|
-
if (platformKey === '
|
|
413
|
-
return [
|
|
414
|
-
'spec-init.md',
|
|
415
|
-
'spec-requirements.md',
|
|
416
|
-
'spec-design.md',
|
|
417
|
-
'spec-validate.md',
|
|
418
|
-
'spec-tasks.md',
|
|
419
|
-
'spec-status.md',
|
|
420
|
-
'impact-analysis.md',
|
|
421
|
-
'docs-init.md',
|
|
422
|
-
'docs-update.md'
|
|
423
|
-
];
|
|
601
|
+
if (platformKey === 'opencode') {
|
|
602
|
+
return [];
|
|
424
603
|
}
|
|
425
604
|
|
|
426
605
|
return [];
|
|
@@ -525,11 +704,8 @@ function copyPlatformFiles(platformKey, results, options = {}) {
|
|
|
525
704
|
|
|
526
705
|
// Copy additional required skills based on platform
|
|
527
706
|
let requiredSkills = [];
|
|
528
|
-
if (platformKey
|
|
707
|
+
if (isClaudeCompatibleRuntime(platformKey)) {
|
|
529
708
|
requiredSkills = CLAUDE_MIGRATION_MANIFEST?.skills?.required || [];
|
|
530
|
-
} else if (platformKey === 'antigravity') {
|
|
531
|
-
// Antigravity also needs shared investigation and impact-analysis skills
|
|
532
|
-
requiredSkills = ['impact-analysis', 'debug', 'ai-multimodal', 'generate-graph'];
|
|
533
709
|
}
|
|
534
710
|
|
|
535
711
|
requiredSkills
|
|
@@ -557,7 +733,7 @@ function copyPlatformFiles(platformKey, results, options = {}) {
|
|
|
557
733
|
}
|
|
558
734
|
|
|
559
735
|
// Copy agents
|
|
560
|
-
if (platformKey
|
|
736
|
+
if (isClaudeCompatibleRuntime(platformKey)) {
|
|
561
737
|
if (fs.existsSync(agentsSourceDir)) {
|
|
562
738
|
const requiredAgents = CLAUDE_MIGRATION_MANIFEST?.agents?.required || [
|
|
563
739
|
'tester.md',
|
|
@@ -586,7 +762,12 @@ function copyPlatformFiles(platformKey, results, options = {}) {
|
|
|
586
762
|
}
|
|
587
763
|
|
|
588
764
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
589
|
-
|
|
765
|
+
if (platformKey === 'opencode') {
|
|
766
|
+
const content = fs.readFileSync(source, 'utf8');
|
|
767
|
+
fs.writeFileSync(dest, convertOpenCodeAgentContent(content, fileName), 'utf8');
|
|
768
|
+
} else {
|
|
769
|
+
fs.copyFileSync(source, dest);
|
|
770
|
+
}
|
|
590
771
|
results.dependencyChecks++;
|
|
591
772
|
results.installedDependencies++;
|
|
592
773
|
|
|
@@ -601,7 +782,7 @@ function copyPlatformFiles(platformKey, results, options = {}) {
|
|
|
601
782
|
// Copy agent reference manuals (debugger manuals, etc.) recursively
|
|
602
783
|
const refsSource = path.join(__dirname, '../src', platform.sourceDir, 'references');
|
|
603
784
|
if (fs.existsSync(refsSource)) {
|
|
604
|
-
const refsDest =
|
|
785
|
+
const refsDest = getClaudeSupportTargetDir(platformKey, 'references');
|
|
605
786
|
const refsExisted = fs.existsSync(refsDest);
|
|
606
787
|
copyRecursive(refsSource, refsDest, options);
|
|
607
788
|
|
|
@@ -622,7 +803,7 @@ function copyPlatformFiles(platformKey, results, options = {}) {
|
|
|
622
803
|
// Copy native scripts (browser-tool, docs-fetch, validate-docs)
|
|
623
804
|
const scriptsSourceDir = path.join(__dirname, '../src/claude/scripts');
|
|
624
805
|
if (fs.existsSync(scriptsSourceDir)) {
|
|
625
|
-
const scriptsDest =
|
|
806
|
+
const scriptsDest = getClaudeSupportTargetDir(platformKey, 'scripts');
|
|
626
807
|
const scriptsExisted = fs.existsSync(scriptsDest);
|
|
627
808
|
copyRecursive(scriptsSourceDir, scriptsDest, options);
|
|
628
809
|
|
|
@@ -662,6 +843,9 @@ function copyPlatformFiles(platformKey, results, options = {}) {
|
|
|
662
843
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
663
844
|
let content = fs.readFileSync(source, 'utf8');
|
|
664
845
|
content = content.replace(/\{\{SKILLS_DIR\}\}/g, platform.skillsRef);
|
|
846
|
+
if (platformKey === 'opencode') {
|
|
847
|
+
content = convertOpenCodeCommandContent(content, file);
|
|
848
|
+
}
|
|
665
849
|
fs.writeFileSync(dest, content);
|
|
666
850
|
|
|
667
851
|
if (destinationExists && shouldOverwriteManagedFiles) {
|
|
@@ -704,37 +888,99 @@ function copyRoutingFile(platformKey, results, options = {}) {
|
|
|
704
888
|
}
|
|
705
889
|
}
|
|
706
890
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
891
|
+
function createOpenCodeAgentsContent(sourceContent) {
|
|
892
|
+
const adaptedContent = sourceContent
|
|
893
|
+
.replace(/^# CLAUDE\.md/m, '# AGENTS.md')
|
|
894
|
+
.replace(
|
|
895
|
+
'Primary operating instructions for Claude Code or any AI agent using this CafeKit runtime.',
|
|
896
|
+
'Primary operating instructions for OpenCode or any AI agent using this CafeKit runtime.'
|
|
897
|
+
)
|
|
898
|
+
.replace(/Claude Code/g, 'OpenCode')
|
|
899
|
+
.replace(/CLAUDE\.md/g, 'AGENTS.md');
|
|
900
|
+
|
|
901
|
+
const openCodeRuntimeBlock = [
|
|
902
|
+
'## OpenCode Runtime Mapping',
|
|
903
|
+
'',
|
|
904
|
+
'- OpenCode project instructions live in `AGENTS.md`.',
|
|
905
|
+
'- CafeKit commands live in `.opencode/commands/` and can be run as OpenCode slash commands.',
|
|
906
|
+
'- CafeKit subagents live in `.opencode/agents/` using OpenCode frontmatter.',
|
|
907
|
+
'- CafeKit skills intentionally live in `.claude/skills/` because OpenCode reads Claude-compatible skills natively.',
|
|
908
|
+
'- Shared CafeKit support files live in `.claude/rules/`, `.claude/scripts/`, `.claude/references/`, and `.claude/runtime.json`.',
|
|
909
|
+
'- OpenCode config is merged into `opencode.json`; keep project-specific model/provider choices there.',
|
|
910
|
+
''
|
|
911
|
+
].join('\n');
|
|
912
|
+
|
|
913
|
+
return adaptedContent.replace('## Core Objective', `${openCodeRuntimeBlock}\n## Core Objective`);
|
|
914
|
+
}
|
|
714
915
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
916
|
+
function upsertCafeKitAgentsBlock(existingContent, blockContent) {
|
|
917
|
+
const start = '<!-- CAFEKIT OPENCODE START -->';
|
|
918
|
+
const end = '<!-- CAFEKIT OPENCODE END -->';
|
|
919
|
+
const managedBlock = `${start}\n${blockContent.trim()}\n${end}`;
|
|
920
|
+
const blockPattern = new RegExp(`${start}[\\s\\S]*?${end}`);
|
|
921
|
+
|
|
922
|
+
if (blockPattern.test(existingContent)) {
|
|
923
|
+
return existingContent.replace(blockPattern, managedBlock);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
const separator = existingContent.endsWith('\n') ? '\n' : '\n\n';
|
|
927
|
+
return `${existingContent}${separator}${managedBlock}\n`;
|
|
928
|
+
}
|
|
720
929
|
|
|
930
|
+
function getOpenCodeAgentsTemplateContent() {
|
|
931
|
+
const openCodeTemplate = path.join(__dirname, '../src/opencode/AGENTS.md');
|
|
932
|
+
|
|
933
|
+
if (fs.existsSync(openCodeTemplate)) {
|
|
934
|
+
return fs.readFileSync(openCodeTemplate, 'utf8');
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const claudeTemplate = path.join(__dirname, '../src/claude/CLAUDE.md');
|
|
938
|
+
if (!fs.existsSync(claudeTemplate)) {
|
|
939
|
+
return null;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
return createOpenCodeAgentsContent(fs.readFileSync(claudeTemplate, 'utf8'));
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Copy OpenCode project instructions to AGENTS.md at the project root.
|
|
946
|
+
function copyOpenCodeAgentsMdFile(platformKey, results, options = {}) {
|
|
947
|
+
if (platformKey !== 'opencode') return;
|
|
948
|
+
|
|
949
|
+
const shouldOverwriteManagedFiles = Boolean(options.upgrade);
|
|
950
|
+
const dest = 'AGENTS.md';
|
|
951
|
+
const content = getOpenCodeAgentsTemplateContent();
|
|
952
|
+
|
|
953
|
+
if (content) {
|
|
721
954
|
const destinationExists = fs.existsSync(dest);
|
|
722
955
|
|
|
723
956
|
if (destinationExists && !shouldOverwriteManagedFiles) {
|
|
724
|
-
|
|
725
|
-
|
|
957
|
+
const existingContent = fs.readFileSync(dest, 'utf8');
|
|
958
|
+
const nextContent = upsertCafeKitAgentsBlock(existingContent, content);
|
|
959
|
+
|
|
960
|
+
if (nextContent === existingContent) {
|
|
961
|
+
console.log(` → Skipped: AGENTS.md (already up to date)`);
|
|
962
|
+
results.skipped++;
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
fs.writeFileSync(dest, nextContent, 'utf8');
|
|
967
|
+
console.log(` ↻ AGENTS.md merged`);
|
|
968
|
+
results.updated++;
|
|
726
969
|
return;
|
|
727
970
|
}
|
|
728
971
|
|
|
729
|
-
fs.
|
|
972
|
+
fs.writeFileSync(dest, content, 'utf8');
|
|
730
973
|
|
|
731
974
|
if (destinationExists && shouldOverwriteManagedFiles) {
|
|
732
|
-
console.log(` ↻
|
|
975
|
+
console.log(` ↻ AGENTS.md overwritten`);
|
|
733
976
|
results.updated++;
|
|
734
977
|
} else {
|
|
735
|
-
console.log(` ✓
|
|
978
|
+
console.log(` ✓ AGENTS.md installed`);
|
|
736
979
|
results.copied++;
|
|
737
980
|
}
|
|
981
|
+
} else {
|
|
982
|
+
console.log(` ⚠ AGENTS.md template source not found`);
|
|
983
|
+
results.missingDependencies++;
|
|
738
984
|
}
|
|
739
985
|
}
|
|
740
986
|
|
|
@@ -780,6 +1026,335 @@ function copyClaudeRuntimeFiles(platformKey, results, options = {}) {
|
|
|
780
1026
|
});
|
|
781
1027
|
}
|
|
782
1028
|
|
|
1029
|
+
function removeObsoleteClaudeRuntimeFiles(platformKey, results) {
|
|
1030
|
+
if (platformKey !== 'claude') return;
|
|
1031
|
+
|
|
1032
|
+
const obsoleteFiles = CLAUDE_MIGRATION_MANIFEST?.obsolete?.runtimeFiles || [];
|
|
1033
|
+
const targetBase = path.join(PLATFORMS.claude.folder);
|
|
1034
|
+
|
|
1035
|
+
obsoleteFiles.forEach(relPath => {
|
|
1036
|
+
const targetPath = path.join(targetBase, relPath);
|
|
1037
|
+
|
|
1038
|
+
if (!fs.existsSync(targetPath)) {
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
fs.rmSync(targetPath, { force: true });
|
|
1043
|
+
console.log(` ↻ Removed obsolete runtime: ${relPath}`);
|
|
1044
|
+
results.updated++;
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function copyOpenCodeSharedRuntimeFiles(platformKey, results, options = {}) {
|
|
1049
|
+
if (platformKey !== 'opencode') return;
|
|
1050
|
+
|
|
1051
|
+
const shouldOverwriteManagedFiles = Boolean(options.upgrade);
|
|
1052
|
+
const srcBase = path.join(__dirname, '../src/claude');
|
|
1053
|
+
const runtimeFiles = ['gitignore', 'runtime.json'];
|
|
1054
|
+
|
|
1055
|
+
runtimeFiles.forEach(relPath => {
|
|
1056
|
+
const srcPath = path.join(srcBase, relPath);
|
|
1057
|
+
const targetRelPath = relPath === 'gitignore' ? '.gitignore' : relPath;
|
|
1058
|
+
const targetPath = path.join('.claude', targetRelPath);
|
|
1059
|
+
|
|
1060
|
+
if (!fs.existsSync(srcPath)) {
|
|
1061
|
+
console.log(` ⚠ Runtime file not found: ${relPath}`);
|
|
1062
|
+
results.missingDependencies++;
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
const targetExists = fs.existsSync(targetPath);
|
|
1067
|
+
const shouldCopy = shouldOverwriteManagedFiles || !targetExists;
|
|
1068
|
+
|
|
1069
|
+
if (shouldCopy) {
|
|
1070
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
1071
|
+
fs.copyFileSync(srcPath, targetPath);
|
|
1072
|
+
|
|
1073
|
+
if (targetExists) {
|
|
1074
|
+
console.log(` ↻ Shared runtime updated: ${targetRelPath}`);
|
|
1075
|
+
results.updated++;
|
|
1076
|
+
} else {
|
|
1077
|
+
console.log(` ✓ Shared runtime installed: ${targetRelPath}`);
|
|
1078
|
+
results.copied++;
|
|
1079
|
+
}
|
|
1080
|
+
} else {
|
|
1081
|
+
results.skipped++;
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function copyOpenCodeCommandTemplates(platformKey, results, options = {}) {
|
|
1087
|
+
if (platformKey !== 'opencode') return;
|
|
1088
|
+
|
|
1089
|
+
const platform = PLATFORMS[platformKey];
|
|
1090
|
+
const shouldOverwriteManagedFiles = Boolean(options.upgrade);
|
|
1091
|
+
|
|
1092
|
+
OPENCODE_COMMAND_TEMPLATES.forEach(command => {
|
|
1093
|
+
const targetPath = path.join(platform.commandsDir, command.fileName);
|
|
1094
|
+
const targetExists = fs.existsSync(targetPath);
|
|
1095
|
+
|
|
1096
|
+
if (targetExists && !shouldOverwriteManagedFiles) {
|
|
1097
|
+
console.log(` → OpenCode command exists: ${command.fileName}`);
|
|
1098
|
+
results.skipped++;
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
1103
|
+
fs.writeFileSync(targetPath, createOpenCodeSkillCommandContent(command), 'utf8');
|
|
1104
|
+
|
|
1105
|
+
if (targetExists) {
|
|
1106
|
+
console.log(` ↻ OpenCode command updated: ${command.fileName}`);
|
|
1107
|
+
results.updated++;
|
|
1108
|
+
} else {
|
|
1109
|
+
console.log(` ✓ OpenCode command installed: ${command.fileName}`);
|
|
1110
|
+
results.copied++;
|
|
1111
|
+
}
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
function readJsonWithComments(filePath) {
|
|
1116
|
+
if (!fs.existsSync(filePath)) {
|
|
1117
|
+
return {};
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
const rawContent = fs.readFileSync(filePath, 'utf8');
|
|
1121
|
+
const withoutBlockComments = rawContent.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
1122
|
+
const withoutLineComments = withoutBlockComments.replace(/(^|[^:])\/\/.*$/gm, '$1');
|
|
1123
|
+
|
|
1124
|
+
return JSON.parse(withoutLineComments);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function getOpenCodeConfigPath() {
|
|
1128
|
+
const jsonPath = 'opencode.json';
|
|
1129
|
+
const jsoncPath = 'opencode.jsonc';
|
|
1130
|
+
|
|
1131
|
+
if (fs.existsSync(jsonPath)) {
|
|
1132
|
+
return jsonPath;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
if (fs.existsSync(jsoncPath)) {
|
|
1136
|
+
return jsoncPath;
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
return jsonPath;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
function mergeOpenCodeConfig(platformKey, results) {
|
|
1143
|
+
if (platformKey !== 'opencode') return;
|
|
1144
|
+
|
|
1145
|
+
const targetPath = getOpenCodeConfigPath();
|
|
1146
|
+
const targetExists = fs.existsSync(targetPath);
|
|
1147
|
+
let config = {};
|
|
1148
|
+
|
|
1149
|
+
try {
|
|
1150
|
+
config = readJsonWithComments(targetPath);
|
|
1151
|
+
} catch (error) {
|
|
1152
|
+
console.log(` ⚠ OpenCode config not merged: ${error.message}`);
|
|
1153
|
+
results.errors++;
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
const before = JSON.stringify(config);
|
|
1158
|
+
|
|
1159
|
+
if (!config.$schema) {
|
|
1160
|
+
config.$schema = 'https://opencode.ai/config.json';
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
if (!Array.isArray(config.instructions)) {
|
|
1164
|
+
config.instructions = [];
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
if (!config.instructions.includes('AGENTS.md')) {
|
|
1168
|
+
config.instructions.push('AGENTS.md');
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
if (typeof config.permission === 'string') {
|
|
1172
|
+
if (config.permission === 'allow') {
|
|
1173
|
+
const afterStringPermission = JSON.stringify(config);
|
|
1174
|
+
if (before === afterStringPermission && targetExists) {
|
|
1175
|
+
console.log(` → OpenCode config already up to date: ${targetPath}`);
|
|
1176
|
+
results.skipped++;
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
fs.writeFileSync(targetPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
1181
|
+
console.log(` ↻ OpenCode config merged: ${targetPath}`);
|
|
1182
|
+
results.updated++;
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
console.log(` ⚠ OpenCode config permission is "${config.permission}"; preserving user setting`);
|
|
1187
|
+
} else if (!config.permission || typeof config.permission !== 'object' || Array.isArray(config.permission)) {
|
|
1188
|
+
config.permission = {};
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
if (typeof config.permission === 'object' && !Array.isArray(config.permission)) {
|
|
1192
|
+
if (!config.permission.skill || typeof config.permission.skill !== 'object' || Array.isArray(config.permission.skill)) {
|
|
1193
|
+
config.permission.skill = {};
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
if (!config.permission.skill['*']) {
|
|
1197
|
+
config.permission.skill['*'] = 'allow';
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
if (!config.permission.task || typeof config.permission.task !== 'object' || Array.isArray(config.permission.task)) {
|
|
1201
|
+
config.permission.task = {};
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
if (!config.permission.task['*']) {
|
|
1205
|
+
config.permission.task['*'] = 'allow';
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
const after = JSON.stringify(config);
|
|
1210
|
+
if (before === after && targetExists) {
|
|
1211
|
+
console.log(` → OpenCode config already up to date: ${targetPath}`);
|
|
1212
|
+
results.skipped++;
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
fs.writeFileSync(targetPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
1217
|
+
|
|
1218
|
+
if (targetExists) {
|
|
1219
|
+
console.log(` ↻ OpenCode config merged: ${targetPath}`);
|
|
1220
|
+
results.updated++;
|
|
1221
|
+
} else {
|
|
1222
|
+
console.log(` ✓ OpenCode config created: ${targetPath}`);
|
|
1223
|
+
results.copied++;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
function isValidOpenCodeModel(model) {
|
|
1228
|
+
return typeof model === 'string' && /^[^/\s]+\/[^/\s]+$/.test(model.trim());
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
function writeOpenCodeModel(model, results, sourceLabel = 'user input') {
|
|
1232
|
+
const targetPath = getOpenCodeConfigPath();
|
|
1233
|
+
let config = {};
|
|
1234
|
+
|
|
1235
|
+
try {
|
|
1236
|
+
config = readJsonWithComments(targetPath);
|
|
1237
|
+
} catch (error) {
|
|
1238
|
+
console.log(` ⚠ OpenCode model not configured: ${error.message}`);
|
|
1239
|
+
results.errors++;
|
|
1240
|
+
return false;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
if (typeof config.model === 'string' && config.model.trim()) {
|
|
1244
|
+
console.log(` → OpenCode model already configured: ${config.model}`);
|
|
1245
|
+
results.skipped++;
|
|
1246
|
+
return false;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
config.model = model.trim();
|
|
1250
|
+
fs.writeFileSync(targetPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
1251
|
+
console.log(` ✓ OpenCode model configured from ${sourceLabel}: ${config.model}`);
|
|
1252
|
+
results.updated++;
|
|
1253
|
+
return true;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
async function promptOpenCodeModel() {
|
|
1257
|
+
const rl = readline.createInterface({
|
|
1258
|
+
input: process.stdin,
|
|
1259
|
+
output: process.stdout
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
console.log('\n📝 OpenCode Model Configuration');
|
|
1263
|
+
console.log(' Optional but recommended for subagents.');
|
|
1264
|
+
console.log(' Format: provider/model-id (example: anthropic/claude-sonnet-4-20250514)');
|
|
1265
|
+
console.log();
|
|
1266
|
+
|
|
1267
|
+
return new Promise((resolve) => {
|
|
1268
|
+
rl.question('Enter OpenCode model (or press Enter to skip): ', (answer) => {
|
|
1269
|
+
rl.close();
|
|
1270
|
+
resolve(answer.trim());
|
|
1271
|
+
});
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
async function setupOpenCodeModel(platforms, results) {
|
|
1276
|
+
if (!platforms.includes('opencode')) return;
|
|
1277
|
+
|
|
1278
|
+
const envModel = process.env.OPENCODE_MODEL || process.env.OPENCODE_DEFAULT_MODEL;
|
|
1279
|
+
if (envModel) {
|
|
1280
|
+
if (isValidOpenCodeModel(envModel)) {
|
|
1281
|
+
writeOpenCodeModel(envModel, results, 'environment');
|
|
1282
|
+
return;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
console.log(` ⚠ Ignoring invalid OpenCode model from environment: ${envModel}`);
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
const model = await promptOpenCodeModel();
|
|
1289
|
+
|
|
1290
|
+
if (!model) {
|
|
1291
|
+
console.log(' ℹ Skipped OpenCode model configuration');
|
|
1292
|
+
console.log(' Set later in opencode.json as: "model": "provider/model-id"');
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
if (!isValidOpenCodeModel(model)) {
|
|
1297
|
+
console.log(' ⚠ Invalid OpenCode model format. Expected provider/model-id. Skipped.');
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
writeOpenCodeModel(model, results);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
function pruneObsoleteSettingsHooks(settings, results) {
|
|
1305
|
+
const obsoleteSubstrings = CLAUDE_MIGRATION_MANIFEST?.obsolete?.settingsHookCommandSubstrings || [];
|
|
1306
|
+
|
|
1307
|
+
if (!settings.hooks || obsoleteSubstrings.length === 0) {
|
|
1308
|
+
return settings;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
let removedCount = 0;
|
|
1312
|
+
const prunedHooks = {};
|
|
1313
|
+
|
|
1314
|
+
Object.entries(settings.hooks).forEach(([eventName, matcherEntries]) => {
|
|
1315
|
+
if (!Array.isArray(matcherEntries)) {
|
|
1316
|
+
prunedHooks[eventName] = matcherEntries;
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
const prunedEntries = [];
|
|
1321
|
+
|
|
1322
|
+
matcherEntries.forEach(entry => {
|
|
1323
|
+
if (!Array.isArray(entry?.hooks)) {
|
|
1324
|
+
prunedEntries.push(entry);
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
const remainingHooks = entry.hooks.filter(hook => {
|
|
1329
|
+
const command = hook?.command || '';
|
|
1330
|
+
const isObsolete = obsoleteSubstrings.some(substring => command.includes(substring));
|
|
1331
|
+
|
|
1332
|
+
if (isObsolete) {
|
|
1333
|
+
removedCount++;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
return !isObsolete;
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
if (remainingHooks.length > 0) {
|
|
1340
|
+
prunedEntries.push({ ...entry, hooks: remainingHooks });
|
|
1341
|
+
}
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
if (prunedEntries.length > 0) {
|
|
1345
|
+
prunedHooks[eventName] = prunedEntries;
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1348
|
+
|
|
1349
|
+
if (removedCount > 0) {
|
|
1350
|
+
console.log(` ↻ Settings: removed ${removedCount} obsolete hook(s)`);
|
|
1351
|
+
results.updated++;
|
|
1352
|
+
return { ...settings, hooks: prunedHooks };
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
return settings;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
783
1358
|
// Merge Claude settings.json
|
|
784
1359
|
function mergeClaudeSettings(platformKey, results, options = {}) {
|
|
785
1360
|
if (platformKey !== 'claude') return;
|
|
@@ -802,7 +1377,7 @@ function mergeClaudeSettings(platformKey, results, options = {}) {
|
|
|
802
1377
|
existingSettings = JSON.parse(fs.readFileSync(targetPath, 'utf8'));
|
|
803
1378
|
}
|
|
804
1379
|
|
|
805
|
-
const mergedSettings = { ...existingSettings };
|
|
1380
|
+
const mergedSettings = pruneObsoleteSettingsHooks({ ...existingSettings }, results);
|
|
806
1381
|
|
|
807
1382
|
// Merge statusLine
|
|
808
1383
|
if (managedSettings.statusLine) {
|
|
@@ -871,10 +1446,10 @@ function copyClaudeMdFile(platformKey, results, options = {}) {
|
|
|
871
1446
|
|
|
872
1447
|
// Copy rules directory (always overwrite)
|
|
873
1448
|
function copyRulesDirectory(platformKey, results, options = {}) {
|
|
874
|
-
if (platformKey
|
|
1449
|
+
if (!isClaudeCompatibleRuntime(platformKey)) return;
|
|
875
1450
|
|
|
876
1451
|
const source = path.join(__dirname, '../src/claude/rules');
|
|
877
|
-
const dest =
|
|
1452
|
+
const dest = getClaudeSupportTargetDir(platformKey, 'rules');
|
|
878
1453
|
|
|
879
1454
|
if (fs.existsSync(source)) {
|
|
880
1455
|
copyRecursive(source, dest, { upgrade: true });
|
|
@@ -1106,14 +1681,18 @@ async function main() {
|
|
|
1106
1681
|
if (platformKey === 'claude') {
|
|
1107
1682
|
copyRoutingFile(platformKey, results, installerOptions);
|
|
1108
1683
|
copyClaudeRuntimeFiles(platformKey, results, installerOptions);
|
|
1684
|
+
removeObsoleteClaudeRuntimeFiles(platformKey, results);
|
|
1109
1685
|
mergeClaudeSettings(platformKey, results, installerOptions);
|
|
1110
1686
|
copyClaudeMdFile(platformKey, results, installerOptions);
|
|
1111
1687
|
copyRulesDirectory(platformKey, results, installerOptions);
|
|
1112
1688
|
}
|
|
1113
1689
|
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1690
|
+
if (platformKey === 'opencode') {
|
|
1691
|
+
copyOpenCodeSharedRuntimeFiles(platformKey, results, installerOptions);
|
|
1692
|
+
copyOpenCodeCommandTemplates(platformKey, results, installerOptions);
|
|
1693
|
+
copyOpenCodeAgentsMdFile(platformKey, results, installerOptions);
|
|
1694
|
+
copyRulesDirectory(platformKey, results, installerOptions);
|
|
1695
|
+
mergeOpenCodeConfig(platformKey, results);
|
|
1117
1696
|
}
|
|
1118
1697
|
|
|
1119
1698
|
writePlatformVersionMetadata(platformKey, results);
|
|
@@ -1128,7 +1707,8 @@ async function main() {
|
|
|
1128
1707
|
ensureGitignore(results, installerOptions);
|
|
1129
1708
|
console.log();
|
|
1130
1709
|
|
|
1131
|
-
// Setup
|
|
1710
|
+
// Setup runtime-specific optional model/API configuration
|
|
1711
|
+
await setupOpenCodeModel(platforms, results);
|
|
1132
1712
|
await setupGeminiCLI(platforms);
|
|
1133
1713
|
|
|
1134
1714
|
// Summary
|
|
@@ -1164,7 +1744,11 @@ async function main() {
|
|
|
1164
1744
|
}
|
|
1165
1745
|
}
|
|
1166
1746
|
|
|
1167
|
-
|
|
1747
|
+
if (platforms.includes('claude')) {
|
|
1748
|
+
console.log('\n 2. Claude Code will automatically sync docs/ (Continuous Documentation)');
|
|
1749
|
+
} else {
|
|
1750
|
+
console.log('\n 2. CafeKit skills and AGENTS.md are installed for the selected runtime');
|
|
1751
|
+
}
|
|
1168
1752
|
console.log(' 3. Project API Keys are now securely isolated in .claude/.env');
|
|
1169
1753
|
if (!installerOptions.upgrade) {
|
|
1170
1754
|
console.log(' 4. To refresh managed templates later, run installer with --upgrade');
|