agile-context-engineering 0.3.0 → 0.5.1
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/.claude-plugin/marketplace.json +18 -0
- package/.claude-plugin/plugin.json +10 -0
- package/CHANGELOG.md +7 -1
- package/LICENSE +51 -51
- package/README.md +330 -318
- package/agents/ace-code-discovery-analyst.md +245 -245
- package/agents/ace-code-integration-analyst.md +248 -248
- package/agents/ace-code-reviewer.md +375 -375
- package/agents/ace-product-owner.md +365 -361
- package/agents/ace-project-researcher.md +606 -606
- package/agents/ace-research-synthesizer.md +228 -228
- package/agents/ace-technical-application-architect.md +315 -315
- package/agents/ace-wiki-mapper.md +449 -445
- package/bin/install.js +605 -195
- package/hooks/ace-check-update.js +71 -62
- package/hooks/ace-statusline.js +107 -89
- package/hooks/hooks.json +14 -0
- package/package.json +7 -5
- package/shared/lib/ace-core.js +361 -0
- package/shared/lib/ace-core.test.js +308 -0
- package/shared/lib/ace-github.js +753 -0
- package/shared/lib/ace-story.js +400 -0
- package/shared/lib/ace-story.test.js +250 -0
- package/{agile-context-engineering → shared}/utils/questioning.xml +110 -110
- package/{agile-context-engineering → shared}/utils/ui-formatting.md +299 -299
- package/{commands/ace/execute-story.md → skills/execute-story/SKILL.md} +116 -138
- package/skills/execute-story/script.js +291 -0
- package/skills/execute-story/script.test.js +261 -0
- package/{agile-context-engineering/templates/product/story.xml → skills/execute-story/story-template.xml} +451 -451
- package/skills/execute-story/walkthrough-template.xml +255 -0
- package/{agile-context-engineering/workflows/execute-story.xml → skills/execute-story/workflow.xml} +1221 -1219
- package/skills/help/SKILL.md +71 -0
- package/skills/help/script.js +315 -0
- package/skills/help/script.test.js +183 -0
- package/{agile-context-engineering/workflows/help.xml → skills/help/workflow.xml} +544 -533
- package/{commands/ace/init-coding-standards.md → skills/init-coding-standards/SKILL.md} +91 -83
- package/{agile-context-engineering/templates/wiki/coding-standards.xml → skills/init-coding-standards/coding-standards-template.xml} +531 -531
- package/skills/init-coding-standards/script.js +50 -0
- package/skills/init-coding-standards/script.test.js +70 -0
- package/{agile-context-engineering/workflows/init-coding-standards.xml → skills/init-coding-standards/workflow.xml} +381 -386
- package/skills/map-cross-cutting/SKILL.md +126 -0
- package/{agile-context-engineering/templates/wiki → skills/map-cross-cutting}/system-cross-cutting.xml +197 -197
- package/skills/map-cross-cutting/workflow.xml +330 -0
- package/skills/map-guide/SKILL.md +126 -0
- package/{agile-context-engineering/templates/wiki → skills/map-guide}/guide.xml +137 -137
- package/skills/map-guide/workflow.xml +320 -0
- package/skills/map-pattern/SKILL.md +125 -0
- package/{agile-context-engineering/templates/wiki → skills/map-pattern}/pattern.xml +159 -159
- package/skills/map-pattern/workflow.xml +331 -0
- package/{commands/ace/map-story.md → skills/map-story/SKILL.md} +180 -165
- package/{agile-context-engineering/templates/wiki → skills/map-story/templates}/decizions.xml +115 -115
- package/skills/map-story/templates/guide.xml +137 -0
- package/skills/map-story/templates/pattern.xml +159 -0
- package/skills/map-story/templates/system-cross-cutting.xml +197 -0
- package/{agile-context-engineering/templates/wiki → skills/map-story/templates}/system.xml +381 -381
- package/{agile-context-engineering/templates/wiki → skills/map-story/templates}/tech-debt-index.xml +125 -125
- package/{agile-context-engineering/templates/wiki → skills/map-story/templates}/walkthrough.xml +255 -255
- package/{agile-context-engineering/workflows/map-story.xml → skills/map-story/workflow.xml} +1046 -1046
- package/{commands/ace/map-subsystem.md → skills/map-subsystem/SKILL.md} +155 -140
- package/skills/map-subsystem/script.js +51 -0
- package/skills/map-subsystem/script.test.js +68 -0
- package/skills/map-subsystem/templates/decizions.xml +115 -0
- package/skills/map-subsystem/templates/guide.xml +137 -0
- package/{agile-context-engineering/templates/wiki → skills/map-subsystem/templates}/module-discovery.xml +174 -174
- package/skills/map-subsystem/templates/pattern.xml +159 -0
- package/{agile-context-engineering/templates/wiki → skills/map-subsystem/templates}/subsystem-architecture.xml +343 -343
- package/{agile-context-engineering/templates/wiki → skills/map-subsystem/templates}/subsystem-structure.xml +234 -234
- package/skills/map-subsystem/templates/system-cross-cutting.xml +197 -0
- package/skills/map-subsystem/templates/system.xml +381 -0
- package/skills/map-subsystem/templates/walkthrough.xml +255 -0
- package/{agile-context-engineering/workflows/map-subsystem.xml → skills/map-subsystem/workflow.xml} +1173 -1178
- package/skills/map-sys-doc/SKILL.md +125 -0
- package/skills/map-sys-doc/system.xml +381 -0
- package/skills/map-sys-doc/workflow.xml +336 -0
- package/{commands/ace/map-system.md → skills/map-system/SKILL.md} +103 -92
- package/skills/map-system/script.js +75 -0
- package/skills/map-system/script.test.js +73 -0
- package/{agile-context-engineering/templates/wiki → skills/map-system/templates}/system-architecture.xml +254 -254
- package/{agile-context-engineering/templates/wiki → skills/map-system/templates}/system-structure.xml +177 -177
- package/{agile-context-engineering/templates/wiki → skills/map-system/templates}/testing-framework.xml +283 -283
- package/{agile-context-engineering/templates/wiki → skills/map-system/templates}/wiki-readme.xml +296 -296
- package/{agile-context-engineering/workflows/map-system.xml → skills/map-system/workflow.xml} +667 -672
- package/{commands/ace/map-walkthrough.md → skills/map-walkthrough/SKILL.md} +140 -127
- package/skills/map-walkthrough/walkthrough.xml +255 -0
- package/{agile-context-engineering/workflows/map-walkthrough.xml → skills/map-walkthrough/workflow.xml} +457 -457
- package/{commands/ace/plan-backlog.md → skills/plan-backlog/SKILL.md} +93 -83
- package/{agile-context-engineering/templates/product/product-backlog.xml → skills/plan-backlog/product-backlog-template.xml} +231 -231
- package/skills/plan-backlog/script.js +121 -0
- package/skills/plan-backlog/script.test.js +83 -0
- package/{agile-context-engineering/workflows/plan-backlog.xml → skills/plan-backlog/workflow.xml} +1348 -1356
- package/{commands/ace/plan-feature.md → skills/plan-feature/SKILL.md} +99 -89
- package/{agile-context-engineering/templates/product/feature.xml → skills/plan-feature/feature-template.xml} +361 -361
- package/skills/plan-feature/script.js +131 -0
- package/skills/plan-feature/script.test.js +80 -0
- package/{agile-context-engineering/workflows/plan-feature.xml → skills/plan-feature/workflow.xml} +1487 -1495
- package/{commands/ace/plan-product-vision.md → skills/plan-product-vision/SKILL.md} +91 -81
- package/{agile-context-engineering/templates/product/product-vision.xml → skills/plan-product-vision/product-vision-template.xml} +227 -227
- package/skills/plan-product-vision/script.js +51 -0
- package/skills/plan-product-vision/script.test.js +69 -0
- package/{agile-context-engineering/workflows/plan-product-vision.xml → skills/plan-product-vision/workflow.xml} +337 -342
- package/{commands/ace/plan-story.md → skills/plan-story/SKILL.md} +139 -159
- package/skills/plan-story/script.js +295 -0
- package/skills/plan-story/script.test.js +240 -0
- package/skills/plan-story/story-template.xml +458 -0
- package/{agile-context-engineering/workflows/plan-story.xml → skills/plan-story/workflow.xml} +1301 -944
- package/{commands/ace/research-external-solution.md → skills/research-external-solution/SKILL.md} +120 -138
- package/{agile-context-engineering/templates/product/external-solution.xml → skills/research-external-solution/external-solution-template.xml} +832 -832
- package/skills/research-external-solution/script.js +229 -0
- package/skills/research-external-solution/script.test.js +134 -0
- package/{agile-context-engineering/workflows/research-external-solution.xml → skills/research-external-solution/workflow.xml} +657 -659
- package/{commands/ace/research-integration-solution.md → skills/research-integration-solution/SKILL.md} +121 -135
- package/{agile-context-engineering/templates/product/story-integration-solution.xml → skills/research-integration-solution/integration-solution-template.xml} +1015 -1015
- package/skills/research-integration-solution/script.js +223 -0
- package/skills/research-integration-solution/script.test.js +134 -0
- package/{agile-context-engineering/workflows/research-integration-solution.xml → skills/research-integration-solution/workflow.xml} +711 -713
- package/{commands/ace/research-story-wiki.md → skills/research-story-wiki/SKILL.md} +101 -116
- package/skills/research-story-wiki/script.js +223 -0
- package/skills/research-story-wiki/script.test.js +138 -0
- package/{agile-context-engineering/templates/product/story-wiki.xml → skills/research-story-wiki/story-wiki-template.xml} +194 -194
- package/{agile-context-engineering/workflows/research-story-wiki.xml → skills/research-story-wiki/workflow.xml} +473 -475
- package/{commands/ace/research-technical-solution.md → skills/research-technical-solution/SKILL.md} +131 -147
- package/skills/research-technical-solution/script.js +223 -0
- package/skills/research-technical-solution/script.test.js +134 -0
- package/{agile-context-engineering/templates/product/story-technical-solution.xml → skills/research-technical-solution/technical-solution-template.xml} +1025 -1025
- package/{agile-context-engineering/workflows/research-technical-solution.xml → skills/research-technical-solution/workflow.xml} +761 -763
- package/{commands/ace/review-story.md → skills/review-story/SKILL.md} +99 -109
- package/skills/review-story/script.js +249 -0
- package/skills/review-story/script.test.js +169 -0
- package/skills/review-story/story-template.xml +451 -0
- package/{agile-context-engineering/workflows/review-story.xml → skills/review-story/workflow.xml} +279 -281
- package/{commands/ace/update.md → skills/update/SKILL.md} +65 -56
- package/{agile-context-engineering/workflows/update.xml → skills/update/workflow.xml} +33 -18
- package/agile-context-engineering/src/ace-tools.js +0 -2881
- package/agile-context-engineering/src/ace-tools.test.js +0 -1089
- package/agile-context-engineering/templates/_command.md +0 -54
- package/agile-context-engineering/templates/_workflow.xml +0 -17
- package/agile-context-engineering/templates/config.json +0 -0
- package/agile-context-engineering/templates/product/integration-solution.xml +0 -0
- package/commands/ace/help.md +0 -93
package/bin/install.js
CHANGED
|
@@ -4,6 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const readline = require('readline');
|
|
6
6
|
const os = require('os');
|
|
7
|
+
const { execSync } = require('child_process');
|
|
7
8
|
|
|
8
9
|
const VERSION = require('../package.json').version;
|
|
9
10
|
|
|
@@ -25,22 +26,38 @@ const RUNTIMES = {
|
|
|
25
26
|
name: 'Claude Code',
|
|
26
27
|
description: "Anthropic's Claude Code CLI",
|
|
27
28
|
globalDir: '.claude',
|
|
28
|
-
|
|
29
|
+
supportsPlugin: true,
|
|
30
|
+
},
|
|
31
|
+
codex: {
|
|
32
|
+
name: 'Codex',
|
|
33
|
+
description: "OpenAI's Codex CLI",
|
|
34
|
+
globalDir: '.codex',
|
|
29
35
|
agentsDir: 'agents',
|
|
30
|
-
|
|
36
|
+
supportsPlugin: false,
|
|
31
37
|
},
|
|
32
38
|
opencode: {
|
|
33
39
|
name: 'Crush',
|
|
34
40
|
description: 'Crush AI coding assistant (formerly OpenCode)',
|
|
35
41
|
globalDir: '.opencode',
|
|
36
|
-
commandsDir: 'commands',
|
|
37
42
|
agentsDir: 'agents',
|
|
38
|
-
|
|
43
|
+
supportsPlugin: false,
|
|
39
44
|
},
|
|
40
45
|
};
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
const
|
|
47
|
+
const MARKETPLACE_NAME = 'ace-marketplace';
|
|
48
|
+
const CODEX_CONFIG_BEGIN = '# ACE Agent Configuration - managed by agile-context-engineering installer';
|
|
49
|
+
const CODEX_CONFIG_END = '# End ACE Agent Configuration';
|
|
50
|
+
|
|
51
|
+
const CODEX_AGENT_SANDBOX = {
|
|
52
|
+
'ace-code-reviewer': 'read-only',
|
|
53
|
+
'ace-code-discovery-analyst': 'read-only',
|
|
54
|
+
'ace-code-integration-analyst': 'read-only',
|
|
55
|
+
'ace-project-researcher': 'read-only',
|
|
56
|
+
'ace-research-synthesizer': 'workspace-write',
|
|
57
|
+
'ace-product-owner': 'workspace-write',
|
|
58
|
+
'ace-technical-application-architect': 'workspace-write',
|
|
59
|
+
'ace-wiki-mapper': 'workspace-write',
|
|
60
|
+
};
|
|
44
61
|
|
|
45
62
|
function log(message, color = '') {
|
|
46
63
|
console.log(`${color}${message}${colors.reset}`);
|
|
@@ -63,6 +80,7 @@ function parseArgs() {
|
|
|
63
80
|
const args = process.argv.slice(2);
|
|
64
81
|
const flags = {
|
|
65
82
|
claude: args.includes('--claude'),
|
|
83
|
+
codex: args.includes('--codex'),
|
|
66
84
|
opencode: args.includes('--opencode'),
|
|
67
85
|
all: args.includes('--all'),
|
|
68
86
|
global: args.includes('--global'),
|
|
@@ -86,22 +104,17 @@ function readSettings(settingsPath) {
|
|
|
86
104
|
return {};
|
|
87
105
|
}
|
|
88
106
|
|
|
89
|
-
// Build hook command with proper quoting for the target directory
|
|
90
|
-
function buildHookCommand(targetDir, hookFile) {
|
|
91
|
-
const hookPath = path.join(targetDir, 'hooks', hookFile);
|
|
92
|
-
return `node "${hookPath.replace(/\\/g, '/')}"`;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
107
|
function showHelp() {
|
|
96
108
|
log(`
|
|
97
109
|
Usage: npx agile-context-engineering [options]
|
|
98
110
|
|
|
99
111
|
Options:
|
|
100
112
|
--claude Install for Claude Code only
|
|
113
|
+
--codex Install for Codex only
|
|
101
114
|
--opencode Install for Crush (formerly OpenCode)
|
|
102
115
|
--all Install for all supported runtimes
|
|
103
|
-
--global Install globally (~/.claude, ~/.opencode)
|
|
104
|
-
--local Install locally (.claude, .opencode)
|
|
116
|
+
--global Install globally (~/.claude, ~/.codex, ~/.opencode)
|
|
117
|
+
--local Install locally (.claude, .codex, .opencode)
|
|
105
118
|
--force-statusline Replace existing statusline configuration
|
|
106
119
|
-h, --help Show this help message
|
|
107
120
|
-v, --version Show version number
|
|
@@ -109,6 +122,7 @@ Options:
|
|
|
109
122
|
Examples:
|
|
110
123
|
npx agile-context-engineering # Interactive installation
|
|
111
124
|
npx agile-context-engineering --claude --local # Claude Code, local install
|
|
125
|
+
npx agile-context-engineering --codex --global # Codex, global install
|
|
112
126
|
npx agile-context-engineering --opencode --global # Crush (formerly OpenCode), global install
|
|
113
127
|
npx agile-context-engineering --all --global # All runtimes, global install
|
|
114
128
|
`);
|
|
@@ -178,20 +192,25 @@ function getBasePath(runtime, scope) {
|
|
|
178
192
|
const cwd = process.cwd();
|
|
179
193
|
const config = RUNTIMES[runtime];
|
|
180
194
|
|
|
195
|
+
if (runtime === 'codex' && scope === 'global' && process.env.CODEX_HOME) {
|
|
196
|
+
return path.resolve(process.env.CODEX_HOME);
|
|
197
|
+
}
|
|
198
|
+
|
|
181
199
|
return scope === 'global'
|
|
182
200
|
? path.join(home, config.globalDir)
|
|
183
201
|
: path.join(cwd, config.globalDir);
|
|
184
202
|
}
|
|
185
203
|
|
|
186
204
|
// File extensions that contain path references needing runtime transformation
|
|
187
|
-
const TRANSFORMABLE_EXTENSIONS = new Set(['.md', '.xml']);
|
|
205
|
+
const TRANSFORMABLE_EXTENSIONS = new Set(['.md', '.xml', '.js']);
|
|
188
206
|
|
|
189
207
|
// Transform file content for a target runtime (replaces .claude/ paths with target runtime paths)
|
|
190
208
|
function transformForRuntime(content, runtime) {
|
|
191
209
|
if (runtime === 'claude') return content; // Source files already use .claude paths
|
|
192
210
|
const targetDir = RUNTIMES[runtime].globalDir; // e.g. '.opencode'
|
|
193
|
-
|
|
194
|
-
|
|
211
|
+
return content
|
|
212
|
+
.replace(/\.claude\//g, `${targetDir}/`)
|
|
213
|
+
.replace(/\.claudeignore\b/g, `${targetDir}ignore`);
|
|
195
214
|
}
|
|
196
215
|
|
|
197
216
|
// Copy directory recursively, optionally transforming text file content for the target runtime
|
|
@@ -211,7 +230,6 @@ function copyDir(src, dest, runtime) {
|
|
|
211
230
|
} else {
|
|
212
231
|
const ext = path.extname(entry.name).toLowerCase();
|
|
213
232
|
if (runtime !== 'claude' && TRANSFORMABLE_EXTENSIONS.has(ext)) {
|
|
214
|
-
// Transform path references for non-Claude runtimes
|
|
215
233
|
const content = fs.readFileSync(srcPath, 'utf-8');
|
|
216
234
|
fs.writeFileSync(destPath, transformForRuntime(content, runtime), 'utf-8');
|
|
217
235
|
} else {
|
|
@@ -221,109 +239,566 @@ function copyDir(src, dest, runtime) {
|
|
|
221
239
|
}
|
|
222
240
|
}
|
|
223
241
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
const basePath = getBasePath(runtime, scope);
|
|
228
|
-
const commandsPath = path.join(basePath, config.commandsDir);
|
|
229
|
-
const agentsPath = path.join(basePath, config.agentsDir);
|
|
230
|
-
const acePath = path.join(basePath, ACE_DIR_NAME);
|
|
242
|
+
function toPosixPath(filePath) {
|
|
243
|
+
return filePath.replace(/\\/g, '/');
|
|
244
|
+
}
|
|
231
245
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const srcUtils = path.join(packageDir, 'agile-context-engineering', 'utils');
|
|
237
|
-
const srcWorkflows = path.join(packageDir, 'agile-context-engineering', 'workflows');
|
|
238
|
-
const srcTools = path.join(packageDir, 'agile-context-engineering', 'src');
|
|
246
|
+
function replaceAceInvocations(content, codexStyle) {
|
|
247
|
+
if (!codexStyle) return content;
|
|
248
|
+
return content.replace(/\/ace:([a-z0-9-]+)/g, '$ace-$1');
|
|
249
|
+
}
|
|
239
250
|
|
|
240
|
-
|
|
241
|
-
|
|
251
|
+
function transformCodexSkillContent(content, skillName, skillDir) {
|
|
252
|
+
let transformed = content
|
|
253
|
+
.replace(/\.claude\//g, '.codex/')
|
|
254
|
+
.replace(/\.claudeignore\b/g, '.codexignore')
|
|
255
|
+
.replace(/\$\{CLAUDE_SKILL_DIR\}/g, toPosixPath(skillDir))
|
|
256
|
+
.replace(/"\$CLAUDE_SKILL_DIR\//g, `"${toPosixPath(skillDir)}/`)
|
|
257
|
+
.replace(/\$CLAUDE_SKILL_DIR\//g, `${toPosixPath(skillDir)}/`)
|
|
258
|
+
.replace(/CLAUDE_SKILL_DIR/g, 'CODEX_SKILL_DIR');
|
|
259
|
+
|
|
260
|
+
transformed = replaceAceInvocations(transformed, true);
|
|
261
|
+
|
|
262
|
+
if (path.basename(skillDir) === skillName && content.includes('---')) {
|
|
263
|
+
transformed = transformed.replace(/^name:\s*.+$/m, `name: ace-${skillName}`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return transformed;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function codexSkillAdapter(skillName, skillDir) {
|
|
270
|
+
const posixSkillDir = toPosixPath(skillDir);
|
|
271
|
+
return `<codex_skill_adapter>
|
|
272
|
+
## Codex Invocation
|
|
273
|
+
- Invoke this skill by mentioning \`$ace-${skillName}\`.
|
|
274
|
+
- Treat any user text after \`$ace-${skillName}\` as the command arguments.
|
|
275
|
+
- In examples copied from Claude Code, \`/ace:${skillName}\` means \`$ace-${skillName}\`.
|
|
276
|
+
|
|
277
|
+
## Runtime Compatibility
|
|
278
|
+
- This repository is authored as a Claude Code plugin. In Codex, the installer copies it to \`${posixSkillDir}\`.
|
|
279
|
+
- Claude's \`!\` resource expansion does not run in Codex. Before following the workflow, manually read the supporting files referenced near the top of this SKILL.md.
|
|
280
|
+
- When a workflow says \`AskUserQuestion\`, use Codex \`request_user_input\` if available; otherwise ask the user directly and continue with a reasonable default only when the choice is low risk.
|
|
281
|
+
- When a workflow says \`Task(..., subagent_type="X")\`, use \`spawn_agent(agent_type="X", message="...")\` only when the user explicitly requested sub-agents. Otherwise complete the work inline in the current agent.
|
|
282
|
+
- Prefer commands that use the absolute skill path above. It keeps Windows and Linux shells from depending on a runtime-specific skill directory environment variable.
|
|
283
|
+
</codex_skill_adapter>
|
|
284
|
+
|
|
285
|
+
`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function copySkillsForCodex(srcSkills, destSkills) {
|
|
289
|
+
if (!fs.existsSync(srcSkills)) return 0;
|
|
290
|
+
fs.mkdirSync(destSkills, { recursive: true });
|
|
291
|
+
|
|
292
|
+
for (const entry of fs.readdirSync(destSkills, { withFileTypes: true })) {
|
|
293
|
+
if (entry.isDirectory() && entry.name.startsWith('ace-')) {
|
|
294
|
+
fs.rmSync(path.join(destSkills, entry.name), { recursive: true });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
242
297
|
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
298
|
+
let count = 0;
|
|
299
|
+
for (const entry of fs.readdirSync(srcSkills, { withFileTypes: true })) {
|
|
300
|
+
if (!entry.isDirectory()) continue;
|
|
301
|
+
const skillName = entry.name;
|
|
302
|
+
const srcDir = path.join(srcSkills, skillName);
|
|
303
|
+
const destDir = path.join(destSkills, `ace-${skillName}`);
|
|
304
|
+
copyCodexSkillDir(srcDir, destDir, skillName);
|
|
305
|
+
count += 1;
|
|
306
|
+
}
|
|
307
|
+
return count;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function copyCodexSkillDir(src, dest, skillName) {
|
|
311
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
312
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
313
|
+
const srcPath = path.join(src, entry.name);
|
|
314
|
+
const destPath = path.join(dest, entry.name);
|
|
315
|
+
if (entry.isDirectory()) {
|
|
316
|
+
copyCodexSkillDir(srcPath, destPath, skillName);
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
321
|
+
if (TRANSFORMABLE_EXTENSIONS.has(ext)) {
|
|
322
|
+
let content = fs.readFileSync(srcPath, 'utf-8');
|
|
323
|
+
content = transformCodexSkillContent(content, skillName, dest);
|
|
324
|
+
if (entry.name === 'SKILL.md') {
|
|
325
|
+
content = content.replace(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/, (match, body) => {
|
|
326
|
+
const lines = body.split(/\r?\n/);
|
|
327
|
+
const filtered = lines.filter(line =>
|
|
328
|
+
!/^(allowed-tools|argument-hint|disable-model-invocation|model|effort):/.test(line.trim())
|
|
329
|
+
);
|
|
330
|
+
const nameIndex = filtered.findIndex(line => /^name:/.test(line));
|
|
331
|
+
if (nameIndex >= 0) {
|
|
332
|
+
filtered[nameIndex] = `name: ace-${skillName}`;
|
|
333
|
+
} else {
|
|
334
|
+
filtered.unshift(`name: ace-${skillName}`);
|
|
335
|
+
}
|
|
336
|
+
return `---\n${filtered.join('\n')}\n---\n\n`;
|
|
337
|
+
});
|
|
338
|
+
content = content.replace(/^(---\r?\n[\s\S]*?\r?\n---\r?\n)/, `$1\n${codexSkillAdapter(skillName, dest)}`);
|
|
339
|
+
}
|
|
340
|
+
fs.writeFileSync(destPath, content, 'utf-8');
|
|
341
|
+
} else {
|
|
342
|
+
fs.copyFileSync(srcPath, destPath);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function extractFrontmatter(content) {
|
|
348
|
+
const match = content.match(/^\s*(?:<!--[\s\S]*?-->\s*)*---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
|
|
349
|
+
if (!match) return { frontmatter: '', body: content };
|
|
350
|
+
return { frontmatter: match[1], body: content.slice(match[0].length) };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function extractFrontmatterField(frontmatter, field) {
|
|
354
|
+
const re = new RegExp(`^${field}:\\s*(.*)$`, 'm');
|
|
355
|
+
const match = frontmatter.match(re);
|
|
356
|
+
return match ? match[1].trim().replace(/^["']|["']$/g, '') : '';
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function generateCodexAgentToml(agentName, sourceContent) {
|
|
360
|
+
const content = replaceAceInvocations(
|
|
361
|
+
sourceContent
|
|
362
|
+
.replace(/\.claude\//g, '.codex/')
|
|
363
|
+
.replace(/\.claudeignore\b/g, '.codexignore')
|
|
364
|
+
.replace(/CLAUDE_SKILL_DIR/g, 'CODEX_SKILL_DIR'),
|
|
365
|
+
true
|
|
366
|
+
);
|
|
367
|
+
const { frontmatter, body } = extractFrontmatter(content);
|
|
368
|
+
const name = extractFrontmatterField(frontmatter, 'name') || agentName;
|
|
369
|
+
const description = extractFrontmatterField(frontmatter, 'description') || `ACE agent ${name}`;
|
|
370
|
+
const sandbox = CODEX_AGENT_SANDBOX[name] || 'workspace-write';
|
|
371
|
+
return [
|
|
372
|
+
`name = ${JSON.stringify(name)}`,
|
|
373
|
+
`description = ${JSON.stringify(description)}`,
|
|
374
|
+
`sandbox_mode = ${JSON.stringify(sandbox)}`,
|
|
375
|
+
`developer_instructions = '''`,
|
|
376
|
+
body.trim(),
|
|
377
|
+
`'''`,
|
|
378
|
+
'',
|
|
379
|
+
].join('\n');
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function installCodexAgents(configDir, srcAgents) {
|
|
383
|
+
if (!fs.existsSync(srcAgents)) return 0;
|
|
384
|
+
const agentsDir = path.join(configDir, 'agents');
|
|
385
|
+
fs.mkdirSync(agentsDir, { recursive: true });
|
|
386
|
+
|
|
387
|
+
for (const file of fs.readdirSync(agentsDir)) {
|
|
388
|
+
if (file.startsWith('ace-') && file.endsWith('.toml')) {
|
|
389
|
+
fs.rmSync(path.join(agentsDir, file), { force: true });
|
|
390
|
+
}
|
|
247
391
|
}
|
|
392
|
+
|
|
393
|
+
const agents = [];
|
|
394
|
+
for (const file of fs.readdirSync(srcAgents)) {
|
|
395
|
+
if (!file.startsWith('ace-') || !file.endsWith('.md')) continue;
|
|
396
|
+
const agentName = file.slice(0, -3);
|
|
397
|
+
const content = fs.readFileSync(path.join(srcAgents, file), 'utf-8');
|
|
398
|
+
const { frontmatter } = extractFrontmatter(content);
|
|
399
|
+
const description = extractFrontmatterField(frontmatter, 'description') || `ACE agent ${agentName}`;
|
|
400
|
+
fs.writeFileSync(path.join(agentsDir, `${agentName}.toml`), generateCodexAgentToml(agentName, content), 'utf-8');
|
|
401
|
+
agents.push({ name: agentName, description });
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
mergeCodexAgentConfig(path.join(configDir, 'config.toml'), agents, agentsDir);
|
|
405
|
+
return agents.length;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function mergeCodexAgentConfig(configPath, agents, agentsDir) {
|
|
409
|
+
const eol = os.EOL;
|
|
410
|
+
let existing = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf-8') : '';
|
|
411
|
+
const blockPattern = new RegExp(`${escapeRegex(CODEX_CONFIG_BEGIN)}[\\s\\S]*?${escapeRegex(CODEX_CONFIG_END)}\\r?\\n?`, 'm');
|
|
412
|
+
existing = existing.replace(blockPattern, '').trimEnd();
|
|
413
|
+
|
|
414
|
+
const lines = [CODEX_CONFIG_BEGIN, ''];
|
|
415
|
+
for (const agent of agents) {
|
|
416
|
+
lines.push(`[agents.${agent.name}]`);
|
|
417
|
+
lines.push(`description = ${JSON.stringify(agent.description)}`);
|
|
418
|
+
lines.push(`config_file = ${JSON.stringify(`${toPosixPath(agentsDir)}/${agent.name}.toml`)}`);
|
|
419
|
+
lines.push('');
|
|
420
|
+
}
|
|
421
|
+
lines.push(CODEX_CONFIG_END);
|
|
422
|
+
|
|
423
|
+
const prefix = existing ? `${existing}${eol}${eol}` : '';
|
|
424
|
+
fs.writeFileSync(configPath, `${prefix}${lines.join(eol)}${eol}`, 'utf-8');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function escapeRegex(value) {
|
|
428
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Check if claude CLI is available
|
|
432
|
+
function hasClaudeCli() {
|
|
433
|
+
try {
|
|
434
|
+
execSync('claude --version', { encoding: 'utf8', stdio: 'pipe', windowsHide: true });
|
|
435
|
+
return true;
|
|
436
|
+
} catch {
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Run a claude CLI command, return { success, output }
|
|
442
|
+
function runClaude(args) {
|
|
443
|
+
try {
|
|
444
|
+
const output = execSync(`claude ${args}`, { encoding: 'utf8', stdio: 'pipe', windowsHide: true, timeout: 30000 });
|
|
445
|
+
return { success: true, output: output.trim() };
|
|
446
|
+
} catch (e) {
|
|
447
|
+
return { success: false, output: (e.stderr || e.message || '').trim() };
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Clean up old standalone ACE installation from ~/.claude/
|
|
452
|
+
function cleanLegacyInstall(basePath) {
|
|
453
|
+
const dirsToClean = [
|
|
454
|
+
path.join(basePath, 'skills'),
|
|
455
|
+
path.join(basePath, 'shared'),
|
|
456
|
+
path.join(basePath, '.claude-plugin'),
|
|
457
|
+
path.join(basePath, 'agile-context-engineering'), // pre-plugin legacy
|
|
458
|
+
];
|
|
459
|
+
const commandsAce = path.join(basePath, 'commands', 'ace');
|
|
460
|
+
if (fs.existsSync(commandsAce)) {
|
|
461
|
+
dirsToClean.push(commandsAce);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
let cleaned = false;
|
|
465
|
+
for (const dir of dirsToClean) {
|
|
466
|
+
if (fs.existsSync(dir)) {
|
|
467
|
+
fs.rmSync(dir, { recursive: true });
|
|
468
|
+
cleaned = true;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Clean ace-* agents
|
|
473
|
+
const agentsPath = path.join(basePath, 'agents');
|
|
248
474
|
if (fs.existsSync(agentsPath)) {
|
|
249
|
-
// Only remove ace-* agent files, preserve non-ACE agents
|
|
250
475
|
for (const f of fs.readdirSync(agentsPath)) {
|
|
251
476
|
if (f.startsWith('ace-')) {
|
|
252
477
|
fs.rmSync(path.join(agentsPath, f), { recursive: true });
|
|
478
|
+
cleaned = true;
|
|
253
479
|
}
|
|
254
480
|
}
|
|
255
481
|
}
|
|
256
|
-
|
|
257
|
-
|
|
482
|
+
|
|
483
|
+
// Clean legacy hooks from settings.json
|
|
484
|
+
const settingsPath = path.join(basePath, 'settings.json');
|
|
485
|
+
if (fs.existsSync(settingsPath)) {
|
|
486
|
+
try {
|
|
487
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
488
|
+
let modified = false;
|
|
489
|
+
|
|
490
|
+
// Remove ACE SessionStart hooks (now handled by plugin hooks.json)
|
|
491
|
+
if (settings.hooks?.SessionStart) {
|
|
492
|
+
const before = settings.hooks.SessionStart.length;
|
|
493
|
+
settings.hooks.SessionStart = settings.hooks.SessionStart.filter(entry =>
|
|
494
|
+
!(entry.hooks && entry.hooks.some(h => h.command && h.command.includes('ace-')))
|
|
495
|
+
);
|
|
496
|
+
if (settings.hooks.SessionStart.length === 0) {
|
|
497
|
+
delete settings.hooks.SessionStart;
|
|
498
|
+
}
|
|
499
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
500
|
+
delete settings.hooks;
|
|
501
|
+
}
|
|
502
|
+
if (settings.hooks?.SessionStart?.length !== before) {
|
|
503
|
+
modified = true;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (modified) {
|
|
508
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
509
|
+
}
|
|
510
|
+
} catch {}
|
|
258
511
|
}
|
|
259
512
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
fs.mkdirSync(agentsPath, { recursive: true });
|
|
263
|
-
fs.mkdirSync(acePath, { recursive: true });
|
|
513
|
+
return cleaned;
|
|
514
|
+
}
|
|
264
515
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
516
|
+
// Install ACE for Claude Code using the plugin marketplace system
|
|
517
|
+
function installForClaude(scope, packageDir, flags) {
|
|
518
|
+
const basePath = getBasePath('claude', scope);
|
|
519
|
+
|
|
520
|
+
log(`\nInstalling ACE for Claude Code (plugin marketplace)...`, colors.cyan);
|
|
521
|
+
log(` Scope: ${scope}`, colors.dim);
|
|
522
|
+
|
|
523
|
+
// Step 1: Clean old standalone install
|
|
524
|
+
const hadLegacy = cleanLegacyInstall(basePath);
|
|
525
|
+
if (hadLegacy) {
|
|
526
|
+
log(` ✓ Cleaned legacy standalone installation`, colors.green);
|
|
269
527
|
}
|
|
270
528
|
|
|
271
|
-
//
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
529
|
+
// Step 2: Clean old cached plugin versions
|
|
530
|
+
const aceCacheDir = path.join(basePath, 'plugins', 'cache', MARKETPLACE_NAME, 'ace');
|
|
531
|
+
if (fs.existsSync(aceCacheDir)) {
|
|
532
|
+
const cachedVersions = fs.readdirSync(aceCacheDir);
|
|
533
|
+
if (cachedVersions.length > 0) {
|
|
534
|
+
for (const ver of cachedVersions) {
|
|
535
|
+
fs.rmSync(path.join(aceCacheDir, ver), { recursive: true });
|
|
536
|
+
}
|
|
537
|
+
log(` ✓ Cleaned ${cachedVersions.length} old cached version(s)`, colors.green);
|
|
538
|
+
}
|
|
275
539
|
}
|
|
276
540
|
|
|
277
|
-
//
|
|
278
|
-
if (
|
|
279
|
-
|
|
280
|
-
log(`
|
|
541
|
+
// Step 3: Check for claude CLI
|
|
542
|
+
if (!hasClaudeCli()) {
|
|
543
|
+
log(` ✗ Claude CLI not found in PATH`, colors.red);
|
|
544
|
+
log(` Install Claude Code first: https://code.claude.com/docs/en/quickstart`, colors.dim);
|
|
545
|
+
return { success: false, path: basePath };
|
|
281
546
|
}
|
|
282
547
|
|
|
283
|
-
//
|
|
284
|
-
if
|
|
285
|
-
|
|
286
|
-
|
|
548
|
+
// Step 4: Add/update marketplace from this package directory
|
|
549
|
+
// First check if marketplace already exists
|
|
550
|
+
const listResult = runClaude('plugin marketplace list --json');
|
|
551
|
+
let marketplaceExists = false;
|
|
552
|
+
if (listResult.success) {
|
|
553
|
+
try {
|
|
554
|
+
// Check output for our marketplace name
|
|
555
|
+
marketplaceExists = listResult.output.includes(MARKETPLACE_NAME);
|
|
556
|
+
} catch {}
|
|
287
557
|
}
|
|
288
558
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
559
|
+
const packageDirUnix = packageDir.replace(/\\/g, '/');
|
|
560
|
+
|
|
561
|
+
if (marketplaceExists) {
|
|
562
|
+
// Update existing marketplace to pick up changes
|
|
563
|
+
const updateResult = runClaude(`plugin marketplace update ${MARKETPLACE_NAME}`);
|
|
564
|
+
if (updateResult.success) {
|
|
565
|
+
log(` ✓ Updated ACE marketplace`, colors.green);
|
|
566
|
+
} else {
|
|
567
|
+
// If update fails, remove and re-add
|
|
568
|
+
runClaude(`plugin marketplace remove ${MARKETPLACE_NAME}`);
|
|
569
|
+
const addResult = runClaude(`plugin marketplace add "${packageDirUnix}"`);
|
|
570
|
+
if (addResult.success) {
|
|
571
|
+
log(` ✓ Re-added ACE marketplace`, colors.green);
|
|
572
|
+
} else {
|
|
573
|
+
log(` ✗ Failed to add marketplace: ${addResult.output}`, colors.red);
|
|
574
|
+
return { success: false, path: basePath };
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
} else {
|
|
578
|
+
const addResult = runClaude(`plugin marketplace add "${packageDirUnix}"`);
|
|
579
|
+
if (addResult.success) {
|
|
580
|
+
log(` ✓ Added ACE marketplace`, colors.green);
|
|
581
|
+
} else {
|
|
582
|
+
log(` ✗ Failed to add marketplace: ${addResult.output}`, colors.red);
|
|
583
|
+
return { success: false, path: basePath };
|
|
584
|
+
}
|
|
293
585
|
}
|
|
294
586
|
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
587
|
+
// Step 5: Install or update the ACE plugin
|
|
588
|
+
const pluginId = `ace@${MARKETPLACE_NAME}`;
|
|
589
|
+
const scopeFlag = scope === 'global' ? '--scope user' : `--scope ${scope}`;
|
|
590
|
+
|
|
591
|
+
// Try install first; if already installed, try update
|
|
592
|
+
const installResult = runClaude(`plugin install ${pluginId} ${scopeFlag}`);
|
|
593
|
+
if (installResult.success) {
|
|
594
|
+
log(` ✓ ACE plugin installed`, colors.green);
|
|
595
|
+
} else if (installResult.output.includes('already installed') || installResult.output.includes('already enabled')) {
|
|
596
|
+
const updateResult = runClaude(`plugin update ${pluginId} ${scopeFlag}`);
|
|
597
|
+
if (updateResult.success) {
|
|
598
|
+
log(` ✓ ACE plugin updated`, colors.green);
|
|
599
|
+
} else {
|
|
600
|
+
log(` ⚠ Plugin update note: ${updateResult.output}`, colors.yellow);
|
|
601
|
+
}
|
|
602
|
+
} else {
|
|
603
|
+
log(` ✗ Failed to install plugin: ${installResult.output}`, colors.red);
|
|
604
|
+
return { success: false, path: basePath };
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Step 6: Configure statusline (not part of plugin hooks — goes in settings.json)
|
|
608
|
+
configureStatusline(basePath, flags);
|
|
609
|
+
|
|
610
|
+
return { success: true, path: basePath };
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Configure the ACE statusline in settings.json
|
|
614
|
+
function configureStatusline(basePath, flags) {
|
|
615
|
+
const settingsPath = path.join(basePath, 'settings.json');
|
|
616
|
+
const settings = readSettings(settingsPath);
|
|
617
|
+
|
|
618
|
+
// Statusline is a settings.json config, not a plugin hook, so CLAUDE_PLUGIN_ROOT
|
|
619
|
+
// is not available. We write a thin wrapper to ~/.claude/hooks/ that finds the
|
|
620
|
+
// installed plugin's statusline script at runtime.
|
|
621
|
+
const wrapperPath = path.join(basePath, 'hooks', 'ace-statusline-wrapper.js');
|
|
622
|
+
const wrapperDir = path.join(basePath, 'hooks');
|
|
623
|
+
if (!fs.existsSync(wrapperDir)) {
|
|
624
|
+
fs.mkdirSync(wrapperDir, { recursive: true });
|
|
299
625
|
}
|
|
300
626
|
|
|
301
|
-
//
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
627
|
+
// Write a thin wrapper that finds the ace plugin statusline script
|
|
628
|
+
fs.writeFileSync(wrapperPath, `#!/usr/bin/env node
|
|
629
|
+
// ACE statusline wrapper — finds the installed plugin's statusline script
|
|
630
|
+
const fs = require('fs');
|
|
631
|
+
const path = require('path');
|
|
632
|
+
const home = require('os').homedir();
|
|
633
|
+
|
|
634
|
+
// Search plugin cache for ace plugin's statusline
|
|
635
|
+
const cacheDir = path.join(home, '.claude', 'plugins', 'cache');
|
|
636
|
+
let scriptPath = null;
|
|
637
|
+
|
|
638
|
+
function findScript(dir, depth) {
|
|
639
|
+
if (depth > 5 || !fs.existsSync(dir)) return false;
|
|
640
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
641
|
+
const full = path.join(dir, entry.name);
|
|
642
|
+
if (entry.isDirectory()) {
|
|
643
|
+
if (findScript(full, depth + 1)) return true;
|
|
644
|
+
} else if (entry.name === 'ace-statusline.js' && dir.includes('ace')) {
|
|
645
|
+
scriptPath = full;
|
|
646
|
+
return true;
|
|
308
647
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
648
|
+
}
|
|
649
|
+
return false;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (fs.existsSync(cacheDir)) {
|
|
653
|
+
findScript(cacheDir, 0);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (scriptPath) {
|
|
657
|
+
// Pipe stdin through to the actual script
|
|
658
|
+
const { spawn } = require('child_process');
|
|
659
|
+
const child = spawn(process.execPath, [scriptPath], {
|
|
660
|
+
stdio: ['pipe', 'inherit', 'inherit'],
|
|
661
|
+
windowsHide: true
|
|
662
|
+
});
|
|
663
|
+
process.stdin.pipe(child.stdin);
|
|
664
|
+
child.on('exit', (code) => process.exit(code || 0));
|
|
665
|
+
} else {
|
|
666
|
+
// Fallback: basic statusline
|
|
667
|
+
let input = '';
|
|
668
|
+
process.stdin.setEncoding('utf8');
|
|
669
|
+
process.stdin.on('data', chunk => input += chunk);
|
|
670
|
+
process.stdin.on('end', () => {
|
|
671
|
+
try {
|
|
672
|
+
const data = JSON.parse(input);
|
|
673
|
+
const model = data.model?.display_name || 'Claude';
|
|
674
|
+
const dir = path.basename(data.workspace?.current_dir || process.cwd());
|
|
675
|
+
process.stdout.write(model + ' | ' + dir);
|
|
676
|
+
} catch {}
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
`, 'utf-8');
|
|
680
|
+
|
|
681
|
+
const statuslineCmd = `node "${wrapperPath.replace(/\\/g, '/')}"`;
|
|
682
|
+
|
|
683
|
+
const hasExisting = settings.statusLine != null;
|
|
684
|
+
const isAceStatusline = hasExisting && settings.statusLine.command &&
|
|
685
|
+
(settings.statusLine.command.includes('ace-statusline') || settings.statusLine.command.includes('ace-'));
|
|
686
|
+
|
|
687
|
+
if (!hasExisting || flags.forceStatusline) {
|
|
688
|
+
settings.statusLine = { type: 'command', command: statuslineCmd };
|
|
689
|
+
log(` ✓ Configured statusline`, colors.green);
|
|
690
|
+
} else if (isAceStatusline) {
|
|
691
|
+
settings.statusLine = { type: 'command', command: statuslineCmd };
|
|
692
|
+
log(` ✓ Updated statusline`, colors.green);
|
|
693
|
+
} else {
|
|
694
|
+
log(` ⚠ Skipping statusline (already configured, use --force-statusline to replace)`, colors.yellow);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Write settings
|
|
698
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Install ACE for Crush (legacy copy approach — no plugin system)
|
|
702
|
+
function installForCrush(scope, packageDir) {
|
|
703
|
+
const config = RUNTIMES.opencode;
|
|
704
|
+
const basePath = getBasePath('opencode', scope);
|
|
705
|
+
const agentsPath = path.join(basePath, config.agentsDir);
|
|
706
|
+
|
|
707
|
+
const srcSkills = path.join(packageDir, 'skills');
|
|
708
|
+
const srcShared = path.join(packageDir, 'shared');
|
|
709
|
+
const srcPlugin = path.join(packageDir, '.claude-plugin');
|
|
710
|
+
const srcAgents = path.join(packageDir, 'agents');
|
|
711
|
+
|
|
712
|
+
log(`\nInstalling ACE for ${config.name} (legacy copy)...`, colors.cyan);
|
|
713
|
+
log(` Target: ${basePath}`, colors.dim);
|
|
714
|
+
|
|
715
|
+
// Clean previous installation
|
|
716
|
+
const skillsPath = path.join(basePath, 'skills');
|
|
717
|
+
const sharedPath = path.join(basePath, 'shared');
|
|
718
|
+
const pluginPath = path.join(basePath, '.claude-plugin');
|
|
719
|
+
const legacyPath = path.join(basePath, 'agile-context-engineering');
|
|
720
|
+
|
|
721
|
+
for (const p of [skillsPath, sharedPath, pluginPath, legacyPath]) {
|
|
722
|
+
if (fs.existsSync(p)) fs.rmSync(p, { recursive: true });
|
|
723
|
+
}
|
|
724
|
+
if (fs.existsSync(agentsPath)) {
|
|
725
|
+
for (const f of fs.readdirSync(agentsPath)) {
|
|
726
|
+
if (f.startsWith('ace-')) fs.rmSync(path.join(agentsPath, f), { recursive: true });
|
|
313
727
|
}
|
|
314
|
-
log(` ✓ Hooks installed`, colors.green);
|
|
315
728
|
}
|
|
316
729
|
|
|
317
|
-
|
|
318
|
-
|
|
730
|
+
fs.mkdirSync(agentsPath, { recursive: true });
|
|
731
|
+
|
|
732
|
+
if (fs.existsSync(srcSkills)) {
|
|
733
|
+
copyDir(srcSkills, skillsPath, 'opencode');
|
|
734
|
+
log(` ✓ Skills installed`, colors.green);
|
|
735
|
+
}
|
|
736
|
+
if (fs.existsSync(srcShared)) {
|
|
737
|
+
copyDir(srcShared, sharedPath, 'opencode');
|
|
738
|
+
log(` ✓ Shared libs installed`, colors.green);
|
|
739
|
+
}
|
|
740
|
+
if (fs.existsSync(srcPlugin)) {
|
|
741
|
+
copyDir(srcPlugin, pluginPath, 'opencode');
|
|
742
|
+
log(` ✓ Plugin manifest installed`, colors.green);
|
|
743
|
+
}
|
|
744
|
+
if (fs.existsSync(srcAgents)) {
|
|
745
|
+
copyDir(srcAgents, agentsPath, 'opencode');
|
|
746
|
+
log(` ✓ Agents installed`, colors.green);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Write VERSION file
|
|
750
|
+
const versionFile = path.join(sharedPath, 'VERSION');
|
|
751
|
+
if (!fs.existsSync(sharedPath)) fs.mkdirSync(sharedPath, { recursive: true });
|
|
319
752
|
fs.writeFileSync(versionFile, VERSION, 'utf-8');
|
|
320
753
|
|
|
321
754
|
// Copy CHANGELOG.md
|
|
322
755
|
const changelogSrc = path.join(packageDir, 'CHANGELOG.md');
|
|
323
|
-
const changelogDest = path.join(
|
|
756
|
+
const changelogDest = path.join(sharedPath, 'CHANGELOG.md');
|
|
757
|
+
if (fs.existsSync(changelogSrc)) {
|
|
758
|
+
fs.copyFileSync(changelogSrc, changelogDest);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
return basePath;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// Install ACE for Codex as native Codex skills + agent TOML config
|
|
765
|
+
function installForCodex(scope, packageDir) {
|
|
766
|
+
const config = RUNTIMES.codex;
|
|
767
|
+
const basePath = getBasePath('codex', scope);
|
|
768
|
+
const skillsPath = path.join(basePath, 'skills');
|
|
769
|
+
const sharedPath = path.join(basePath, 'shared');
|
|
770
|
+
|
|
771
|
+
const srcSkills = path.join(packageDir, 'skills');
|
|
772
|
+
const srcShared = path.join(packageDir, 'shared');
|
|
773
|
+
const srcAgents = path.join(packageDir, 'agents');
|
|
774
|
+
|
|
775
|
+
log(`\nInstalling ACE for ${config.name} (native skills)...`, colors.cyan);
|
|
776
|
+
log(` Target: ${basePath}`, colors.dim);
|
|
777
|
+
|
|
778
|
+
fs.mkdirSync(basePath, { recursive: true });
|
|
779
|
+
|
|
780
|
+
const skillCount = copySkillsForCodex(srcSkills, skillsPath);
|
|
781
|
+
log(` ✓ ${skillCount} skills installed`, colors.green);
|
|
782
|
+
|
|
783
|
+
if (fs.existsSync(sharedPath)) {
|
|
784
|
+
fs.rmSync(sharedPath, { recursive: true });
|
|
785
|
+
}
|
|
786
|
+
if (fs.existsSync(srcShared)) {
|
|
787
|
+
copyDir(srcShared, sharedPath, 'codex');
|
|
788
|
+
log(` ✓ Shared libs installed`, colors.green);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const agentCount = installCodexAgents(basePath, srcAgents);
|
|
792
|
+
log(` ✓ ${agentCount} agents configured`, colors.green);
|
|
793
|
+
|
|
794
|
+
const versionFile = path.join(sharedPath, 'VERSION');
|
|
795
|
+
if (!fs.existsSync(sharedPath)) fs.mkdirSync(sharedPath, { recursive: true });
|
|
796
|
+
fs.writeFileSync(versionFile, VERSION, 'utf-8');
|
|
797
|
+
|
|
798
|
+
const changelogSrc = path.join(packageDir, 'CHANGELOG.md');
|
|
799
|
+
const changelogDest = path.join(sharedPath, 'CHANGELOG.md');
|
|
324
800
|
if (fs.existsSync(changelogSrc)) {
|
|
325
801
|
fs.copyFileSync(changelogSrc, changelogDest);
|
|
326
|
-
log(` ✓ CHANGELOG.md installed`, colors.green);
|
|
327
802
|
}
|
|
328
803
|
|
|
329
804
|
return basePath;
|
|
@@ -346,44 +821,41 @@ async function main() {
|
|
|
346
821
|
|
|
347
822
|
banner();
|
|
348
823
|
|
|
349
|
-
// Determine package directory (where this script is located)
|
|
350
824
|
const packageDir = path.join(__dirname, '..');
|
|
351
825
|
|
|
352
826
|
let runtimes = [];
|
|
353
827
|
let scope = null;
|
|
354
828
|
|
|
355
|
-
|
|
356
|
-
const hasRuntimeFlag = flags.claude || flags.opencode || flags.all;
|
|
829
|
+
const hasRuntimeFlag = flags.claude || flags.codex || flags.opencode || flags.all;
|
|
357
830
|
const hasScopeFlag = flags.global || flags.local;
|
|
358
831
|
const isInteractive = !hasRuntimeFlag && !hasScopeFlag;
|
|
359
832
|
|
|
360
833
|
if (isInteractive) {
|
|
361
834
|
const rl = createPrompt();
|
|
362
835
|
|
|
363
|
-
// Ask for runtime selection (multiple choice)
|
|
364
836
|
runtimes = await askMultiple(rl, '\nWhich runtime(s) do you want to install ACE for?', [
|
|
365
837
|
{ label: 'Claude Code', value: 'claude', description: "Anthropic's Claude Code CLI" },
|
|
838
|
+
{ label: 'Codex', value: 'codex', description: "OpenAI's Codex CLI" },
|
|
366
839
|
{ label: 'Crush', value: 'opencode', description: 'Crush AI coding assistant (formerly OpenCode)' },
|
|
367
840
|
]);
|
|
368
841
|
|
|
369
|
-
// Ask for scope
|
|
370
842
|
scope = await ask(rl, '\nWhere should ACE be installed?', [
|
|
371
|
-
{ label: 'Global', value: 'global', description: 'Install in home directory (~/.claude, ~/.opencode)' },
|
|
372
|
-
{ label: 'Local', value: 'local', description: 'Install in current project (.claude, .opencode)' },
|
|
843
|
+
{ label: 'Global', value: 'global', description: 'Install in home directory (~/.claude, ~/.codex, ~/.opencode)' },
|
|
844
|
+
{ label: 'Local', value: 'local', description: 'Install in current project (.claude, .codex, .opencode)' },
|
|
373
845
|
]);
|
|
374
846
|
|
|
375
847
|
rl.close();
|
|
376
848
|
} else {
|
|
377
|
-
// Non-interactive mode
|
|
378
849
|
if (flags.all) {
|
|
379
|
-
runtimes = ['claude', 'opencode'];
|
|
850
|
+
runtimes = ['claude', 'codex', 'opencode'];
|
|
380
851
|
} else {
|
|
381
852
|
if (flags.claude) runtimes.push('claude');
|
|
853
|
+
if (flags.codex) runtimes.push('codex');
|
|
382
854
|
if (flags.opencode) runtimes.push('opencode');
|
|
383
855
|
}
|
|
384
856
|
|
|
385
857
|
if (runtimes.length === 0) {
|
|
386
|
-
log('Error: No runtime specified. Use --claude, --opencode (Crush), or --all', colors.red);
|
|
858
|
+
log('Error: No runtime specified. Use --claude, --codex, --opencode (Crush), or --all', colors.red);
|
|
387
859
|
process.exit(1);
|
|
388
860
|
}
|
|
389
861
|
|
|
@@ -399,116 +871,54 @@ async function main() {
|
|
|
399
871
|
const installedPaths = [];
|
|
400
872
|
|
|
401
873
|
for (const runtime of runtimes) {
|
|
402
|
-
|
|
403
|
-
|
|
874
|
+
if (runtime === 'claude') {
|
|
875
|
+
const result = installForClaude(scope, packageDir, flags);
|
|
876
|
+
installedPaths.push({ runtime, name: 'Claude Code', ...result });
|
|
877
|
+
} else if (runtime === 'codex') {
|
|
878
|
+
const p = installForCodex(scope, packageDir);
|
|
879
|
+
installedPaths.push({ runtime, name: RUNTIMES[runtime].name, path: p, success: true });
|
|
880
|
+
} else {
|
|
881
|
+
const p = installForCrush(scope, packageDir);
|
|
882
|
+
installedPaths.push({ runtime, name: RUNTIMES[runtime].name, path: p, success: true });
|
|
883
|
+
}
|
|
404
884
|
}
|
|
405
885
|
|
|
406
|
-
|
|
407
|
-
for (const { runtime, path: basePath } of installedPaths) {
|
|
408
|
-
if (runtime !== 'claude') continue;
|
|
409
|
-
|
|
410
|
-
const settingsPath = path.join(basePath, 'settings.json');
|
|
411
|
-
const settings = readSettings(settingsPath);
|
|
412
|
-
|
|
413
|
-
const statuslineCommand = buildHookCommand(basePath, 'ace-statusline.js');
|
|
414
|
-
const updateCheckCommand = buildHookCommand(basePath, 'ace-check-update.js');
|
|
886
|
+
const anyFailed = installedPaths.some(p => !p.success);
|
|
415
887
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
const hasAceUpdateHook = settings.hooks.SessionStart.some(entry =>
|
|
421
|
-
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('ace-check-update'))
|
|
422
|
-
);
|
|
423
|
-
if (!hasAceUpdateHook) {
|
|
424
|
-
settings.hooks.SessionStart.push({
|
|
425
|
-
hooks: [{ type: 'command', command: updateCheckCommand }]
|
|
426
|
-
});
|
|
427
|
-
log(` ✓ Configured update check hook`, colors.green);
|
|
428
|
-
}
|
|
888
|
+
// Show success message
|
|
889
|
+
log(`\n${'═'.repeat(50)}`, anyFailed ? colors.yellow : colors.green);
|
|
890
|
+
log(` ACE installation ${anyFailed ? 'completed with warnings' : 'complete'}!`, (anyFailed ? colors.yellow : colors.green) + colors.bright);
|
|
891
|
+
log(`${'═'.repeat(50)}`, anyFailed ? colors.yellow : colors.green);
|
|
429
892
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
settings.statusLine.command.includes('ace-statusline');
|
|
434
|
-
|
|
435
|
-
if (!hasExisting || flags.forceStatusline) {
|
|
436
|
-
// No existing statusline or force flag — install ACE statusline
|
|
437
|
-
settings.statusLine = { type: 'command', command: statuslineCommand };
|
|
438
|
-
log(` ✓ Configured statusline`, colors.green);
|
|
439
|
-
} else if (isAceStatusline) {
|
|
440
|
-
// Already ACE statusline — update path
|
|
441
|
-
settings.statusLine = { type: 'command', command: statuslineCommand };
|
|
442
|
-
log(` ✓ Updated statusline`, colors.green);
|
|
443
|
-
} else if (isInteractive) {
|
|
444
|
-
// Existing non-ACE statusline in interactive mode — ask user
|
|
445
|
-
const existingCmd = settings.statusLine.command || settings.statusLine.url || '(custom)';
|
|
446
|
-
log(`\n ⚠ Existing statusline detected`, colors.yellow);
|
|
447
|
-
log(` Current: ${existingCmd}`, colors.dim);
|
|
448
|
-
log(`\n ACE statusline shows:`, colors.cyan);
|
|
449
|
-
log(` • Model name`, colors.dim);
|
|
450
|
-
log(` • Current task (from todo list)`, colors.dim);
|
|
451
|
-
log(` • Context window usage (color-coded)`, colors.dim);
|
|
452
|
-
log(` • Update notifications`, colors.dim);
|
|
453
|
-
|
|
454
|
-
const rl = createPrompt();
|
|
455
|
-
const choice = await ask(rl, '\n What would you like to do?', [
|
|
456
|
-
{ label: 'Keep existing statusline', value: 'keep' },
|
|
457
|
-
{ label: 'Replace with ACE statusline', value: 'replace' },
|
|
458
|
-
]);
|
|
459
|
-
rl.close();
|
|
460
|
-
|
|
461
|
-
if (choice === 'replace') {
|
|
462
|
-
settings.statusLine = { type: 'command', command: statuslineCommand };
|
|
463
|
-
log(` ✓ Configured statusline`, colors.green);
|
|
464
|
-
} else {
|
|
465
|
-
log(` ⚠ Skipping statusline (kept existing)`, colors.yellow);
|
|
466
|
-
}
|
|
893
|
+
for (const { name: runtimeName, success } of installedPaths) {
|
|
894
|
+
if (success) {
|
|
895
|
+
log(` ✓ ${runtimeName}: installed`, colors.green);
|
|
467
896
|
} else {
|
|
468
|
-
|
|
469
|
-
log(` ⚠ Skipping statusline (already configured, use --force-statusline to replace)`, colors.yellow);
|
|
897
|
+
log(` ✗ ${runtimeName}: failed (see errors above)`, colors.red);
|
|
470
898
|
}
|
|
471
|
-
|
|
472
|
-
// Write settings
|
|
473
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
474
899
|
}
|
|
475
900
|
|
|
476
|
-
|
|
477
|
-
log(
|
|
478
|
-
log(`
|
|
479
|
-
log(
|
|
480
|
-
|
|
481
|
-
log(
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
log(` $
|
|
489
|
-
log(`
|
|
490
|
-
log(`
|
|
491
|
-
|
|
492
|
-
log(` ${ACE_DIR_NAME}/`, colors.dim);
|
|
493
|
-
log(` templates/ Project & artifact templates`, colors.dim);
|
|
494
|
-
log(` utils/ Formatting & utility guides`, colors.dim);
|
|
495
|
-
log(` workflows/ Workflow definitions`, colors.dim);
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
log(`\nAvailable commands:`, colors.cyan);
|
|
499
|
-
log(` /ace:help Check project status and next steps`, colors.dim);
|
|
500
|
-
log(` /ace:plan-project Plan your project with epics and features`, colors.dim);
|
|
501
|
-
log(` /ace:plan-epic Plan an epic with features and stories`, colors.dim);
|
|
502
|
-
log(` /ace:plan-feature Plan a feature with stories`, colors.dim);
|
|
503
|
-
log(` /ace:plan-story Plan a story with tasks`, colors.dim);
|
|
504
|
-
log(` /ace:refine-story Refine a story for execution`, colors.dim);
|
|
505
|
-
log(` /ace:execute-story Execute a story`, colors.dim);
|
|
506
|
-
log(` /ace:verify-story Verify a completed story`, colors.dim);
|
|
901
|
+
log(`\nAvailable skills:`, colors.cyan);
|
|
902
|
+
log(` /ace:help Check project status and next steps`, colors.dim);
|
|
903
|
+
log(` /ace:plan-product-vision Create or update the product vision`, colors.dim);
|
|
904
|
+
log(` /ace:plan-backlog Plan the product backlog`, colors.dim);
|
|
905
|
+
log(` /ace:plan-feature Plan a feature with stories`, colors.dim);
|
|
906
|
+
log(` /ace:plan-story Plan a story specification`, colors.dim);
|
|
907
|
+
log(` /ace:execute-story Execute a planned story`, colors.dim);
|
|
908
|
+
log(` /ace:map-system Map system-wide architecture`, colors.dim);
|
|
909
|
+
log(` /ace:map-subsystem Map a subsystem's internals`, colors.dim);
|
|
910
|
+
log(` /ace:init-coding-standards Generate coding standards`, colors.dim);
|
|
911
|
+
if (runtimes.includes('codex')) {
|
|
912
|
+
log(`\nCodex skill names use $ace-* instead of /ace:*:`, colors.cyan);
|
|
913
|
+
log(` $ace-help Check project status and next steps`, colors.dim);
|
|
914
|
+
log(` $ace-plan-story Plan a story specification`, colors.dim);
|
|
915
|
+
log(` $ace-execute-story Execute a planned story`, colors.dim);
|
|
916
|
+
}
|
|
507
917
|
|
|
508
918
|
log(`\nGet started:`, colors.cyan);
|
|
509
|
-
log(` 1.
|
|
510
|
-
log(` 2. Run /ace:help
|
|
511
|
-
log(` 3. Run /ace:plan-
|
|
919
|
+
log(` 1. Restart your AI coding assistant`, colors.dim);
|
|
920
|
+
log(` 2. Run /ace:help in Claude/Crush or $ace-help in Codex`, colors.dim);
|
|
921
|
+
log(` 3. Run /ace:plan-product-vision or $ace-plan-product-vision to define your product\n`, colors.dim);
|
|
512
922
|
}
|
|
513
923
|
|
|
514
924
|
main().catch((err) => {
|