agileflow 2.50.0 → 2.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +82 -460
  2. package/package.json +18 -3
  3. package/scripts/agileflow-configure.js +134 -63
  4. package/scripts/agileflow-welcome.js +161 -31
  5. package/scripts/generators/agent-registry.js +2 -2
  6. package/scripts/generators/command-registry.js +6 -6
  7. package/scripts/generators/index.js +2 -6
  8. package/scripts/generators/inject-babysit.js +9 -2
  9. package/scripts/generators/inject-help.js +3 -1
  10. package/scripts/generators/inject-readme.js +7 -3
  11. package/scripts/generators/skill-registry.js +5 -5
  12. package/scripts/get-env.js +13 -12
  13. package/scripts/obtain-context.js +396 -185
  14. package/scripts/session-coordinator.sh +232 -0
  15. package/scripts/session-manager.js +512 -0
  16. package/src/core/agents/orchestrator.md +275 -0
  17. package/src/core/commands/adr.md +38 -16
  18. package/src/core/commands/agent.md +39 -22
  19. package/src/core/commands/assign.md +17 -0
  20. package/src/core/commands/auto.md +60 -46
  21. package/src/core/commands/babysit.md +302 -637
  22. package/src/core/commands/baseline.md +20 -0
  23. package/src/core/commands/blockers.md +33 -48
  24. package/src/core/commands/board.md +19 -0
  25. package/src/core/commands/changelog.md +20 -0
  26. package/src/core/commands/ci.md +17 -0
  27. package/src/core/commands/context.md +43 -40
  28. package/src/core/commands/debt.md +76 -45
  29. package/src/core/commands/deploy.md +20 -0
  30. package/src/core/commands/deps.md +40 -46
  31. package/src/core/commands/diagnose.md +24 -18
  32. package/src/core/commands/docs.md +18 -0
  33. package/src/core/commands/epic.md +31 -0
  34. package/src/core/commands/feedback.md +33 -21
  35. package/src/core/commands/handoff.md +29 -0
  36. package/src/core/commands/help.md +16 -7
  37. package/src/core/commands/impact.md +31 -61
  38. package/src/core/commands/metrics.md +17 -35
  39. package/src/core/commands/packages.md +21 -0
  40. package/src/core/commands/pr.md +15 -0
  41. package/src/core/commands/readme-sync.md +42 -9
  42. package/src/core/commands/research.md +58 -11
  43. package/src/core/commands/retro.md +42 -50
  44. package/src/core/commands/review.md +22 -27
  45. package/src/core/commands/session/end.md +53 -297
  46. package/src/core/commands/session/history.md +38 -257
  47. package/src/core/commands/session/init.md +44 -446
  48. package/src/core/commands/session/new.md +152 -0
  49. package/src/core/commands/session/resume.md +51 -447
  50. package/src/core/commands/session/status.md +32 -244
  51. package/src/core/commands/sprint.md +33 -0
  52. package/src/core/commands/status.md +18 -0
  53. package/src/core/commands/story-validate.md +32 -0
  54. package/src/core/commands/story.md +21 -6
  55. package/src/core/commands/template.md +18 -0
  56. package/src/core/commands/tests.md +22 -0
  57. package/src/core/commands/update.md +72 -58
  58. package/src/core/commands/validate-expertise.md +25 -37
  59. package/src/core/commands/velocity.md +33 -74
  60. package/src/core/commands/verify.md +16 -0
  61. package/src/core/experts/documentation/expertise.yaml +16 -2
  62. package/src/core/skills/agileflow-retro-facilitator/SKILL.md +57 -219
  63. package/src/core/skills/agileflow-retro-facilitator/cookbook/4ls.md +86 -0
  64. package/src/core/skills/agileflow-retro-facilitator/cookbook/glad-sad-mad.md +79 -0
  65. package/src/core/skills/agileflow-retro-facilitator/cookbook/start-stop-continue.md +142 -0
  66. package/src/core/skills/agileflow-retro-facilitator/prompts/action-items.md +83 -0
  67. package/src/core/skills/writing-skills/SKILL.md +352 -0
  68. package/src/core/skills/writing-skills/testing-skills-with-subagents.md +232 -0
  69. package/tools/cli/agileflow-cli.js +4 -2
  70. package/tools/cli/commands/config.js +20 -13
  71. package/tools/cli/commands/doctor.js +25 -9
  72. package/tools/cli/commands/list.js +10 -6
  73. package/tools/cli/commands/setup.js +54 -3
  74. package/tools/cli/commands/status.js +6 -8
  75. package/tools/cli/commands/uninstall.js +5 -5
  76. package/tools/cli/commands/update.js +51 -7
  77. package/tools/cli/installers/core/installer.js +8 -4
  78. package/tools/cli/installers/ide/_base-ide.js +3 -1
  79. package/tools/cli/installers/ide/claude-code.js +3 -7
  80. package/tools/cli/installers/ide/codex.js +440 -0
  81. package/tools/cli/installers/ide/manager.js +2 -6
  82. package/tools/cli/lib/content-injector.js +3 -3
  83. package/tools/cli/lib/docs-setup.js +3 -2
  84. package/tools/cli/lib/npm-utils.js +3 -3
  85. package/tools/cli/lib/ui.js +7 -7
  86. package/tools/cli/lib/version-checker.js +3 -3
  87. package/tools/postinstall.js +2 -3
@@ -2,14 +2,24 @@
2
2
  * AgileFlow CLI - Update Command
3
3
  *
4
4
  * Updates an existing AgileFlow installation.
5
+ * Includes self-update capability to always use the latest CLI.
5
6
  */
6
7
 
7
8
  const chalk = require('chalk');
8
9
  const path = require('node:path');
10
+ const { spawnSync } = require('node:child_process');
9
11
  const semver = require('semver');
10
12
  const { Installer } = require('../installers/core/installer');
11
13
  const { IdeManager } = require('../installers/ide/manager');
12
- const { displayLogo, displaySection, success, warning, error, info, confirm } = require('../lib/ui');
14
+ const {
15
+ displayLogo,
16
+ displaySection,
17
+ success,
18
+ warning,
19
+ error,
20
+ info,
21
+ confirm,
22
+ } = require('../lib/ui');
13
23
  const { createDocsStructure, getDocsFolderName } = require('../lib/docs-setup');
14
24
  const { getLatestVersion } = require('../lib/npm-utils');
15
25
 
@@ -22,8 +32,10 @@ module.exports = {
22
32
  options: [
23
33
  ['-d, --directory <path>', 'Project directory (default: current directory)'],
24
34
  ['--force', 'Force reinstall (skip prompts; overwrites local changes)'],
35
+ ['--no-self-update', 'Skip automatic CLI self-update check'],
36
+ ['--self-updated', 'Internal flag: indicates CLI was already self-updated'],
25
37
  ],
26
- action: async (options) => {
38
+ action: async options => {
27
39
  try {
28
40
  const directory = path.resolve(options.directory || '.');
29
41
 
@@ -60,14 +72,44 @@ module.exports = {
60
72
  console.log(chalk.bold('Latest (npm):'), npmLatestVersion);
61
73
  }
62
74
 
63
- // Check if CLI itself is outdated
64
- if (npmLatestVersion && semver.lt(localCliVersion, npmLatestVersion)) {
75
+ // Self-update: if CLI is outdated and we haven't already self-updated, re-run with latest
76
+ const shouldSelfUpdate = options.selfUpdate !== false && !options.selfUpdated;
77
+ if (npmLatestVersion && semver.lt(localCliVersion, npmLatestVersion) && shouldSelfUpdate) {
78
+ console.log();
79
+ info(`Updating CLI from v${localCliVersion} to v${npmLatestVersion}...`);
80
+ console.log(chalk.dim(' Fetching latest version from npm...\n'));
81
+
82
+ // Build the command with all current options forwarded
83
+ const args = ['agileflow@latest', 'update', '--self-updated'];
84
+ if (options.directory) args.push('-d', options.directory);
85
+ if (options.force) args.push('--force');
86
+
87
+ const result = spawnSync('npx', args, {
88
+ stdio: 'inherit',
89
+ cwd: process.cwd(),
90
+ shell: process.platform === 'win32',
91
+ });
92
+
93
+ // Exit with the same code as the spawned process
94
+ process.exit(result.status ?? 0);
95
+ }
96
+
97
+ // If we self-updated, show confirmation
98
+ if (options.selfUpdated) {
99
+ success(`CLI updated to v${localCliVersion}`);
100
+ console.log();
101
+ }
102
+
103
+ // Check if CLI itself is still outdated (only if self-update was disabled)
104
+ if (npmLatestVersion && semver.lt(localCliVersion, npmLatestVersion) && !shouldSelfUpdate) {
65
105
  console.log();
66
106
  warning('Your CLI is outdated!');
67
107
  console.log(chalk.dim(` To update your installation, run:\n`));
68
108
  console.log(chalk.cyan(` npx agileflow@latest update\n`));
69
109
 
70
- const useOutdated = options.force ? true : await confirm('Continue with outdated CLI anyway?');
110
+ const useOutdated = options.force
111
+ ? true
112
+ : await confirm('Continue with outdated CLI anyway?');
71
113
  if (!useOutdated) {
72
114
  console.log(chalk.dim('\nUpdate cancelled\n'));
73
115
  process.exit(0);
@@ -137,12 +179,14 @@ module.exports = {
137
179
 
138
180
  // Create/update docs structure (idempotent - only creates missing files)
139
181
  displaySection('Updating Documentation Structure', `Folder: ${config.docsFolder}/`);
140
- const docsResult = await createDocsStructure(directory, config.docsFolder, { updateGitignore: false });
182
+ const docsResult = await createDocsStructure(directory, config.docsFolder, {
183
+ updateGitignore: false,
184
+ });
141
185
 
142
186
  if (!docsResult.success) {
143
187
  warning('Failed to update docs structure');
144
188
  if (docsResult.errors.length > 0) {
145
- docsResult.errors.forEach((err) => error(` ${err}`));
189
+ docsResult.errors.forEach(err => error(` ${err}`));
146
190
  }
147
191
  }
148
192
 
@@ -161,7 +161,11 @@ class Installer {
161
161
 
162
162
  // Create manifest
163
163
  spinner.text = 'Creating manifest...';
164
- await this.createManifest(cfgDir, { ides, userName, agileflowFolder, docsFolder }, { force: effectiveForce });
164
+ await this.createManifest(
165
+ cfgDir,
166
+ { ides, userName, agileflowFolder, docsFolder },
167
+ { force: effectiveForce }
168
+ );
165
169
 
166
170
  // Persist file index (used for safe future updates)
167
171
  spinner.text = 'Writing file index...';
@@ -578,21 +582,21 @@ class Installer {
578
582
  const agentsDir = path.join(agileflowDir, 'agents');
579
583
  if (await fs.pathExists(agentsDir)) {
580
584
  const files = await fs.readdir(agentsDir);
581
- counts.agents = files.filter((f) => f.endsWith('.md')).length;
585
+ counts.agents = files.filter(f => f.endsWith('.md')).length;
582
586
  }
583
587
 
584
588
  // Count commands
585
589
  const commandsDir = path.join(agileflowDir, 'commands');
586
590
  if (await fs.pathExists(commandsDir)) {
587
591
  const files = await fs.readdir(commandsDir);
588
- counts.commands = files.filter((f) => f.endsWith('.md')).length;
592
+ counts.commands = files.filter(f => f.endsWith('.md')).length;
589
593
  }
590
594
 
591
595
  // Count skills
592
596
  const skillsDir = path.join(agileflowDir, 'skills');
593
597
  if (await fs.pathExists(skillsDir)) {
594
598
  const entries = await fs.readdir(skillsDir, { withFileTypes: true });
595
- counts.skills = entries.filter((e) => e.isDirectory()).length;
599
+ counts.skills = entries.filter(e => e.isDirectory()).length;
596
600
  }
597
601
 
598
602
  return counts;
@@ -96,7 +96,9 @@ class BaseIdeSetup {
96
96
  const agileflowPath = path.join(projectDir, this.configDir, 'commands', folderName);
97
97
  if (await fs.pathExists(agileflowPath)) {
98
98
  await fs.remove(agileflowPath);
99
- console.log(chalk.dim(` Removed old ${folderName} configuration from ${this.displayName}`));
99
+ console.log(
100
+ chalk.dim(` Removed old ${folderName} configuration from ${this.displayName}`)
101
+ );
100
102
  }
101
103
  }
102
104
  }
@@ -114,12 +114,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
114
114
  // ALSO install agents as spawnable subagents (.claude/agents/agileflow/)
115
115
  // This allows Task tool to spawn them with subagent_type: "agileflow-ui"
116
116
  const spawnableAgentsDir = path.join(claudeDir, 'agents', 'agileflow');
117
- await this.installCommandsRecursive(
118
- agentsSource,
119
- spawnableAgentsDir,
120
- agileflowDir,
121
- false
122
- );
117
+ await this.installCommandsRecursive(agentsSource, spawnableAgentsDir, agileflowDir, false);
123
118
  console.log(chalk.dim(` - Spawnable agents: .claude/agents/agileflow/`));
124
119
 
125
120
  // Install skills (.claude/skills/)
@@ -140,7 +135,8 @@ class ClaudeCodeSetup extends BaseIdeSetup {
140
135
  }
141
136
 
142
137
  const totalCommands = commandResult.commands + agentResult.commands;
143
- const totalSubdirs = commandResult.subdirs + (agentResult.commands > 0 ? 1 : 0) + agentResult.subdirs;
138
+ const totalSubdirs =
139
+ commandResult.subdirs + (agentResult.commands > 0 ? 1 : 0) + agentResult.subdirs;
144
140
 
145
141
  console.log(chalk.green(` ✓ ${this.displayName} configured:`));
146
142
  console.log(chalk.dim(` - ${totalCommands} commands installed`));
@@ -0,0 +1,440 @@
1
+ /**
2
+ * AgileFlow CLI - OpenAI Codex CLI Installer
3
+ *
4
+ * Installs AgileFlow for OpenAI Codex CLI:
5
+ * - Agents become Codex Skills (.codex/skills/agileflow-NAME/)
6
+ * - Commands become Codex Prompts (~/.codex/prompts/agileflow-NAME.md)
7
+ * - AGENTS.md provides project instructions at repo root
8
+ *
9
+ * @see https://developers.openai.com/codex/
10
+ * @see ADR-0002: Codex CLI Integration Strategy
11
+ */
12
+
13
+ const path = require('node:path');
14
+ const os = require('node:os');
15
+ const fs = require('fs-extra');
16
+ const chalk = require('chalk');
17
+ const yaml = require('js-yaml');
18
+ const { BaseIdeSetup } = require('./_base-ide');
19
+
20
+ /**
21
+ * OpenAI Codex CLI setup handler
22
+ */
23
+ class CodexSetup extends BaseIdeSetup {
24
+ constructor() {
25
+ super('codex', 'OpenAI Codex CLI', false);
26
+ // Per-repo config directory
27
+ this.configDir = '.codex';
28
+ // User-level Codex home (can be overridden by $CODEX_HOME)
29
+ this.codexHome = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
30
+ }
31
+
32
+ /**
33
+ * Get the Codex home directory
34
+ * @returns {string} Path to ~/.codex or $CODEX_HOME
35
+ */
36
+ getCodexHome() {
37
+ return this.codexHome;
38
+ }
39
+
40
+ /**
41
+ * Detect if Codex CLI is installed/configured
42
+ * @param {string} projectDir - Project directory
43
+ * @returns {Promise<boolean>}
44
+ */
45
+ async detect(projectDir) {
46
+ // Check if Codex home exists (user has Codex CLI)
47
+ const codexHomeExists = await this.exists(this.codexHome);
48
+ // Check if project has .codex/ or AGENTS.md
49
+ const projectCodexExists = await this.exists(path.join(projectDir, this.configDir));
50
+ const agentsMdExists = await this.exists(path.join(projectDir, 'AGENTS.md'));
51
+
52
+ return codexHomeExists || projectCodexExists || agentsMdExists;
53
+ }
54
+
55
+ /**
56
+ * Convert an AgileFlow agent markdown file to Codex SKILL.md format
57
+ * @param {string} content - Original agent markdown content
58
+ * @param {string} agentName - Agent name (e.g., 'database')
59
+ * @returns {string} Codex SKILL.md content
60
+ */
61
+ convertAgentToSkill(content, agentName) {
62
+ // Extract frontmatter if present
63
+ let description = `AgileFlow ${agentName} agent`;
64
+ let model = 'default';
65
+
66
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
67
+ if (frontmatterMatch) {
68
+ try {
69
+ const frontmatter = yaml.load(frontmatterMatch[1]);
70
+ if (frontmatter.description) {
71
+ description = frontmatter.description;
72
+ }
73
+ if (frontmatter.model) {
74
+ model = frontmatter.model;
75
+ }
76
+ } catch (e) {
77
+ // Ignore YAML parse errors
78
+ }
79
+ }
80
+
81
+ // Create SKILL.md with YAML frontmatter
82
+ const skillFrontmatter = yaml
83
+ .dump({
84
+ name: `agileflow-${agentName}`,
85
+ description: description,
86
+ version: '1.0.0',
87
+ })
88
+ .trim();
89
+
90
+ // Remove original frontmatter from content
91
+ let bodyContent = content.replace(/^---\n[\s\S]*?\n---\n*/, '');
92
+
93
+ // Add Codex-specific header
94
+ const codexHeader = `# AgileFlow: ${agentName.charAt(0).toUpperCase() + agentName.slice(1)} Agent
95
+
96
+ > Invoke with \`$agileflow-${agentName}\` or via \`/skills\`
97
+
98
+ `;
99
+
100
+ // Replace Claude-specific references
101
+ bodyContent = bodyContent
102
+ .replace(/Claude Code/gi, 'Codex CLI')
103
+ .replace(/CLAUDE\.md/g, 'AGENTS.md')
104
+ .replace(/\.claude\//g, '.codex/')
105
+ .replace(/Task tool/gi, 'skill invocation');
106
+
107
+ return `---
108
+ ${skillFrontmatter}
109
+ ---
110
+
111
+ ${codexHeader}${bodyContent}`;
112
+ }
113
+
114
+ /**
115
+ * Convert an AgileFlow command markdown file to Codex prompt format
116
+ * @param {string} content - Original command markdown content
117
+ * @param {string} commandName - Command name (e.g., 'board')
118
+ * @returns {string} Codex prompt content
119
+ */
120
+ convertCommandToPrompt(content, commandName) {
121
+ // Extract description from frontmatter if present
122
+ let description = `AgileFlow ${commandName} command`;
123
+
124
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
125
+ if (frontmatterMatch) {
126
+ try {
127
+ const frontmatter = yaml.load(frontmatterMatch[1]);
128
+ if (frontmatter.description) {
129
+ description = frontmatter.description;
130
+ }
131
+ } catch (e) {
132
+ // Ignore YAML parse errors
133
+ }
134
+ }
135
+
136
+ // Remove original frontmatter from content
137
+ let bodyContent = content.replace(/^---\n[\s\S]*?\n---\n*/, '');
138
+
139
+ // Replace Claude-specific references
140
+ bodyContent = bodyContent
141
+ .replace(/Claude Code/gi, 'Codex CLI')
142
+ .replace(/CLAUDE\.md/g, 'AGENTS.md')
143
+ .replace(/\.claude\//g, '.codex/')
144
+ .replace(/\/agileflow:/g, '$agileflow-');
145
+
146
+ // Add Codex prompt header
147
+ const header = `# AgileFlow: ${commandName}
148
+
149
+ > ${description}
150
+
151
+ ## Instructions
152
+
153
+ `;
154
+
155
+ // Add input placeholder at the end
156
+ const footer = `
157
+
158
+ ## Context
159
+
160
+ {{input}}
161
+ `;
162
+
163
+ return `${header}${bodyContent}${footer}`;
164
+ }
165
+
166
+ /**
167
+ * Install AgileFlow agents as Codex skills
168
+ * @param {string} projectDir - Project directory
169
+ * @param {string} agileflowDir - AgileFlow installation directory
170
+ * @returns {Promise<number>} Number of skills installed
171
+ */
172
+ async installSkills(projectDir, agileflowDir) {
173
+ const agentsSource = path.join(agileflowDir, 'agents');
174
+ const skillsTarget = path.join(projectDir, this.configDir, 'skills');
175
+
176
+ if (!(await this.exists(agentsSource))) {
177
+ return 0;
178
+ }
179
+
180
+ let skillCount = 0;
181
+ const agents = await this.scanDirectory(agentsSource, '.md');
182
+
183
+ for (const agent of agents) {
184
+ const content = await this.readFile(agent.path);
185
+ const skillContent = this.convertAgentToSkill(content, agent.name);
186
+
187
+ // Create skill directory: .codex/skills/agileflow-{name}/
188
+ const skillDir = path.join(skillsTarget, `agileflow-${agent.name}`);
189
+ await this.ensureDir(skillDir);
190
+
191
+ // Write SKILL.md
192
+ await this.writeFile(path.join(skillDir, 'SKILL.md'), skillContent);
193
+ skillCount++;
194
+ }
195
+
196
+ return skillCount;
197
+ }
198
+
199
+ /**
200
+ * Install AgileFlow commands as Codex prompts (user-level)
201
+ * @param {string} agileflowDir - AgileFlow installation directory
202
+ * @returns {Promise<number>} Number of prompts installed
203
+ */
204
+ async installPrompts(agileflowDir) {
205
+ const commandsSource = path.join(agileflowDir, 'commands');
206
+ const promptsTarget = path.join(this.codexHome, 'prompts');
207
+
208
+ if (!(await this.exists(commandsSource))) {
209
+ return 0;
210
+ }
211
+
212
+ await this.ensureDir(promptsTarget);
213
+
214
+ let promptCount = 0;
215
+ const commands = await this.scanDirectory(commandsSource, '.md');
216
+
217
+ for (const command of commands) {
218
+ const content = await this.readFile(command.path);
219
+ const promptContent = this.convertCommandToPrompt(content, command.name);
220
+
221
+ // Write prompt: ~/.codex/prompts/agileflow-{name}.md
222
+ const promptPath = path.join(promptsTarget, `agileflow-${command.name}.md`);
223
+ await this.writeFile(promptPath, promptContent);
224
+ promptCount++;
225
+ }
226
+
227
+ return promptCount;
228
+ }
229
+
230
+ /**
231
+ * Generate AGENTS.md scaffolding for the project
232
+ * @param {string} projectDir - Project directory
233
+ * @returns {Promise<boolean>} Whether AGENTS.md was created
234
+ */
235
+ async generateAgentsMd(projectDir) {
236
+ const agentsMdPath = path.join(projectDir, 'AGENTS.md');
237
+
238
+ // Don't overwrite existing AGENTS.md
239
+ if (await this.exists(agentsMdPath)) {
240
+ console.log(chalk.dim(` - AGENTS.md already exists (preserved)`));
241
+ return false;
242
+ }
243
+
244
+ const content = `# AGENTS.md
245
+
246
+ > Project instructions for Codex CLI with AgileFlow integration
247
+
248
+ ## Project Commands
249
+
250
+ \`\`\`bash
251
+ # Run tests
252
+ npm test
253
+
254
+ # Lint code
255
+ npm run lint
256
+
257
+ # Build project
258
+ npm run build
259
+ \`\`\`
260
+
261
+ ## Conventions
262
+
263
+ - Prefer running tests before claiming success
264
+ - Keep changes small and reviewable
265
+ - Use AgileFlow skills when relevant (see below)
266
+ - Check \`${this.docsFolder}/09-agents/status.json\` for current work status
267
+
268
+ ## When to Use AgileFlow Skills
269
+
270
+ Invoke skills with \`$skill-name\` or list available skills with \`/skills\`.
271
+
272
+ | Task Type | Skill to Use |
273
+ |-----------|--------------|
274
+ | Architecture decisions | \`$agileflow-adr-writer\` |
275
+ | Database work | \`$agileflow-database\` |
276
+ | API endpoints | \`$agileflow-api\` |
277
+ | UI components | \`$agileflow-ui\` |
278
+ | Testing | \`$agileflow-testing\` |
279
+ | CI/CD setup | \`$agileflow-ci\` |
280
+ | Security review | \`$agileflow-security\` |
281
+ | Performance | \`$agileflow-performance\` |
282
+ | Documentation | \`$agileflow-documentation\` |
283
+
284
+ ## AgileFlow Status
285
+
286
+ - **Stories**: \`${this.docsFolder}/09-agents/status.json\`
287
+ - **Research**: \`${this.docsFolder}/10-research/\`
288
+ - **Decisions**: \`${this.docsFolder}/03-decisions/\`
289
+ - **Epics**: \`${this.docsFolder}/05-epics/\`
290
+
291
+ ## Security Recommendations
292
+
293
+ For safe operation, configure Codex with:
294
+
295
+ \`\`\`toml
296
+ # ~/.codex/config.toml
297
+ approval_policy = "on-request"
298
+ sandbox_mode = "workspace-write"
299
+ \`\`\`
300
+
301
+ ---
302
+
303
+ *Generated by AgileFlow - https://github.com/projectquestorg/AgileFlow*
304
+ `;
305
+
306
+ await this.writeFile(agentsMdPath, content);
307
+ return true;
308
+ }
309
+
310
+ /**
311
+ * Generate AGENTS.override.md template for subdirectories
312
+ * @param {string} targetDir - Directory to create override in
313
+ * @returns {Promise<boolean>} Whether file was created
314
+ */
315
+ async generateAgentsOverride(targetDir) {
316
+ const overridePath = path.join(targetDir, 'AGENTS.override.md');
317
+
318
+ // Don't overwrite existing override
319
+ if (await this.exists(overridePath)) {
320
+ return false;
321
+ }
322
+
323
+ const content = `# AGENTS.override.md
324
+
325
+ > Override instructions for this subdirectory
326
+
327
+ ## Directory-Specific Context
328
+
329
+ This directory contains: [describe purpose]
330
+
331
+ ## Additional Commands
332
+
333
+ \`\`\`bash
334
+ # Directory-specific commands here
335
+ \`\`\`
336
+
337
+ ## Directory Conventions
338
+
339
+ - [List any patterns specific to this directory]
340
+
341
+ ---
342
+
343
+ *This file overrides AGENTS.md for this subdirectory*
344
+ `;
345
+
346
+ await this.writeFile(overridePath, content);
347
+ return true;
348
+ }
349
+
350
+ /**
351
+ * Setup Codex CLI configuration
352
+ * @param {string} projectDir - Project directory
353
+ * @param {string} agileflowDir - AgileFlow installation directory
354
+ * @param {Object} options - Setup options
355
+ */
356
+ async setup(projectDir, agileflowDir, options = {}) {
357
+ console.log(chalk.hex('#e8683a')(` Setting up ${this.displayName}...`));
358
+
359
+ // Clean up old installation first
360
+ await this.cleanup(projectDir);
361
+
362
+ // Ensure .codex directory exists
363
+ await this.ensureDir(path.join(projectDir, this.configDir));
364
+
365
+ // 1. Install agents as skills (per-repo)
366
+ const skillCount = await this.installSkills(projectDir, agileflowDir);
367
+ if (skillCount > 0) {
368
+ console.log(chalk.dim(` - ${skillCount} skills installed to .codex/skills/`));
369
+ }
370
+
371
+ // 2. Install commands as prompts (user-level)
372
+ const promptCount = await this.installPrompts(agileflowDir);
373
+ if (promptCount > 0) {
374
+ console.log(chalk.dim(` - ${promptCount} prompts installed to ~/.codex/prompts/`));
375
+ }
376
+
377
+ // 3. Generate AGENTS.md if it doesn't exist
378
+ const agentsMdCreated = await this.generateAgentsMd(projectDir);
379
+ if (agentsMdCreated) {
380
+ console.log(chalk.dim(` - AGENTS.md created at project root`));
381
+ }
382
+
383
+ console.log(chalk.green(` ✓ ${this.displayName} configured`));
384
+ console.log(chalk.dim(` - Skills: .codex/skills/agileflow-*/`));
385
+ console.log(chalk.dim(` - Prompts: ~/.codex/prompts/agileflow-*.md`));
386
+ console.log(chalk.dim(` - Instructions: AGENTS.md`));
387
+
388
+ return {
389
+ success: true,
390
+ skills: skillCount,
391
+ prompts: promptCount,
392
+ agentsMd: agentsMdCreated,
393
+ };
394
+ }
395
+
396
+ /**
397
+ * Cleanup Codex configuration
398
+ * @param {string} projectDir - Project directory
399
+ * @param {Object} options - Cleanup options
400
+ * @param {boolean} options.removeAgentsMd - Also remove AGENTS.md (requires confirmation)
401
+ */
402
+ async cleanup(projectDir, options = {}) {
403
+ // Clean up per-repo skills
404
+ const skillsDir = path.join(projectDir, this.configDir, 'skills');
405
+ if (await this.exists(skillsDir)) {
406
+ const entries = await fs.readdir(skillsDir);
407
+ for (const entry of entries) {
408
+ if (entry.startsWith('agileflow-')) {
409
+ await fs.remove(path.join(skillsDir, entry));
410
+ }
411
+ }
412
+ console.log(chalk.dim(` Cleaned up AgileFlow skills from .codex/skills/`));
413
+ }
414
+
415
+ // Clean up user-level prompts
416
+ const promptsDir = path.join(this.codexHome, 'prompts');
417
+ if (await this.exists(promptsDir)) {
418
+ const entries = await fs.readdir(promptsDir);
419
+ for (const entry of entries) {
420
+ if (entry.startsWith('agileflow-')) {
421
+ await fs.remove(path.join(promptsDir, entry));
422
+ }
423
+ }
424
+ console.log(chalk.dim(` Cleaned up AgileFlow prompts from ~/.codex/prompts/`));
425
+ }
426
+
427
+ // Optionally remove AGENTS.md if explicitly requested
428
+ if (options.removeAgentsMd) {
429
+ const agentsMdPath = path.join(projectDir, 'AGENTS.md');
430
+ if (await this.exists(agentsMdPath)) {
431
+ await fs.remove(agentsMdPath);
432
+ console.log(chalk.dim(` Removed AGENTS.md`));
433
+ }
434
+ } else {
435
+ console.log(chalk.dim(` Note: AGENTS.md preserved (use removeAgentsMd option to delete)`));
436
+ }
437
+ }
438
+ }
439
+
440
+ module.exports = { CodexSetup };
@@ -52,12 +52,8 @@ class IdeManager {
52
52
  const ideDir = __dirname;
53
53
 
54
54
  try {
55
- const files = fs.readdirSync(ideDir).filter((file) => {
56
- return (
57
- file.endsWith('.js') &&
58
- !file.startsWith('_') &&
59
- file !== 'manager.js'
60
- );
55
+ const files = fs.readdirSync(ideDir).filter(file => {
56
+ return file.endsWith('.js') && !file.startsWith('_') && file !== 'manager.js';
61
57
  });
62
58
 
63
59
  files.sort();
@@ -44,7 +44,7 @@ function generateAgentList(agentsDir) {
44
44
  name: frontmatter.name || path.basename(file, '.md'),
45
45
  description: frontmatter.description || '',
46
46
  tools: tools,
47
- model: frontmatter.model || 'haiku'
47
+ model: frontmatter.model || 'haiku',
48
48
  });
49
49
  } catch (err) {
50
50
  // Silently skip files with parsing errors
@@ -98,7 +98,7 @@ function generateCommandList(commandsDir) {
98
98
  commands.push({
99
99
  name: cmdName,
100
100
  description: frontmatter.description || '',
101
- argumentHint: frontmatter['argument-hint'] || ''
101
+ argumentHint: frontmatter['argument-hint'] || '',
102
102
  });
103
103
  } catch (err) {
104
104
  // Silently skip files with parsing errors - they might be generated files
@@ -149,5 +149,5 @@ function injectContent(templateContent, agentsDir, commandsDir) {
149
149
  module.exports = {
150
150
  generateAgentList,
151
151
  generateCommandList,
152
- injectContent
152
+ injectContent,
153
153
  };
@@ -25,7 +25,8 @@ try {
25
25
  );
26
26
  } catch (e) {
27
27
  // Fallback if README not found
28
- packageReadme = '# AgileFlow\n\nSee https://github.com/projectquestorg/AgileFlow for documentation.';
28
+ packageReadme =
29
+ '# AgileFlow\n\nSee https://github.com/projectquestorg/AgileFlow for documentation.';
29
30
  }
30
31
 
31
32
  /**
@@ -386,7 +387,7 @@ Document your CI/CD workflows and configuration here.
386
387
 
387
388
  if (fs.existsSync(gitignorePath)) {
388
389
  const existingGitignore = await fs.readFile(gitignorePath, 'utf8');
389
- const newEntries = gitignoreEntries.filter((entry) => !existingGitignore.includes(entry));
390
+ const newEntries = gitignoreEntries.filter(entry => !existingGitignore.includes(entry));
390
391
  if (newEntries.length > 0) {
391
392
  await fs.appendFile(gitignorePath, '\n' + newEntries.join('\n') + '\n', 'utf8');
392
393
  console.log(chalk.yellow(` ↻ Updated .gitignore with ${newEntries.length} entries`));