@jojonax/codex-copilot 1.5.5 → 1.6.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.
@@ -1,298 +1,298 @@
1
- /**
2
- * codex-copilot init - Initialize project
3
- *
4
- * 1. Auto-detect PRD document
5
- * 2. Let user confirm/select PRD
6
- * 3. Select AI provider (Codex, Claude Code, Cursor, etc.)
7
- * 4. Generate task decomposition prompt
8
- * 5. Create .codex-copilot/ directory structure
9
- */
10
-
11
- import { mkdirSync, writeFileSync, readFileSync, existsSync } from 'fs';
12
- import { resolve } from 'path';
13
- import { detectPRD, readPRD } from '../utils/detect-prd.js';
14
- import { log } from '../utils/logger.js';
15
- import { ask, confirm, select, closePrompt } from '../utils/prompt.js';
16
- import { git } from '../utils/git.js';
17
- import { github } from '../utils/github.js';
18
- import { provider } from '../utils/provider.js';
19
-
20
- export async function init(projectDir) {
21
- log.title('📋 Initializing Codex-Copilot');
22
- log.blank();
23
-
24
- // ===== Pre-flight checks =====
25
- log.step('Checking environment...');
26
-
27
- // Check if in a git repo
28
- try {
29
- git.currentBranch(projectDir);
30
- log.info('Git repository ✓');
31
- } catch {
32
- log.error('Not a Git repository. Run: git init');
33
- closePrompt();
34
- process.exit(1);
35
- }
36
-
37
- // Check gh CLI
38
- if (!github.checkGhAuth()) {
39
- log.error('GitHub CLI not authenticated. Run: gh auth login');
40
- closePrompt();
41
- process.exit(1);
42
- }
43
- log.info('GitHub CLI ✓');
44
-
45
- // Check GitHub remote
46
- try {
47
- const repo = git.getRepoInfo(projectDir);
48
- log.info(`GitHub repo: ${repo.owner}/${repo.repo} ✓`);
49
- } catch (err) {
50
- log.error(err.message);
51
- closePrompt();
52
- process.exit(1);
53
- }
54
-
55
- log.blank();
56
-
57
- // ===== Detect PRD =====
58
- log.step('Scanning for PRD documents...');
59
- const candidates = detectPRD(projectDir);
60
-
61
- let prdPath;
62
-
63
- if (candidates.length === 0) {
64
- log.warn('No PRD document detected automatically');
65
- const manualPath = await ask('Enter PRD file path (relative or absolute):');
66
- prdPath = resolve(projectDir, manualPath);
67
- // Prevent path traversal
68
- if (!prdPath.startsWith(resolve(projectDir)) && !manualPath.startsWith('/')) {
69
- log.error('PRD path is outside the project directory');
70
- closePrompt();
71
- process.exit(1);
72
- }
73
- if (!existsSync(prdPath)) {
74
- log.error(`File not found: ${prdPath}`);
75
- closePrompt();
76
- process.exit(1);
77
- }
78
- } else if (candidates.length === 1) {
79
- prdPath = candidates[0].path;
80
- log.info(`Found PRD: ${candidates[0].relativePath}`);
81
- const ok = await confirm('Use this file?');
82
- if (!ok) {
83
- const manualPath = await ask('Enter PRD file path:');
84
- prdPath = resolve(projectDir, manualPath);
85
- }
86
- } else {
87
- log.info(`Found ${candidates.length} candidate PRD files:`);
88
- const choice = await select('Select the PRD to use:', candidates.map(c => ({
89
- label: `${c.relativePath} (score: ${c.score})`,
90
- value: c.path,
91
- })));
92
- prdPath = choice.value;
93
- }
94
-
95
- log.info(`Using PRD: ${prdPath}`);
96
- log.blank();
97
-
98
- // ===== Read PRD =====
99
- const prdContent = readPRD(prdPath);
100
- log.info(`PRD size: ${(prdContent.length / 1024).toFixed(1)} KB`);
101
-
102
- // ===== Select AI Provider =====
103
- log.step('Select AI coding tool...');
104
-
105
- // Check versions of installed CLI tools (non-blocking)
106
- log.info('Checking tool versions...');
107
- let versionCache = {};
108
- try {
109
- versionCache = provider.checkAllVersions();
110
- const updatable = Object.entries(versionCache).filter(([, v]) => v.updateAvailable);
111
- if (updatable.length > 0) {
112
- for (const [id, v] of updatable) {
113
- log.warn(`${provider.getProvider(id).name}: v${v.current} → v${v.latest} update available`);
114
- }
115
- } else if (Object.keys(versionCache).length > 0) {
116
- log.info('All detected tools are up to date ✓');
117
- }
118
- } catch {
119
- // Version check failed — continue normally
120
- }
121
-
122
- log.blank();
123
- const providerChoices = provider.buildProviderChoices(versionCache);
124
- const selectedProvider = await select('Which AI tool will you use?', providerChoices);
125
- const providerId = selectedProvider.value;
126
- const providerInfo = provider.getProvider(providerId);
127
- log.info(`Selected: ${providerInfo.name}`);
128
-
129
- // Offer update if newer version available
130
- const vi = versionCache[providerId];
131
- if (vi && vi.updateAvailable) {
132
- log.blank();
133
- log.warn(`${providerInfo.name} v${vi.current} → v${vi.latest} available`);
134
- const doUpdate = await confirm(`Update ${providerInfo.name} now?`);
135
- if (doUpdate) {
136
- provider.updateProvider(providerId);
137
- }
138
- }
139
-
140
- log.blank();
141
-
142
- // ===== Create .codex-copilot directory =====
143
- log.step('Creating .codex-copilot/ directory...');
144
- const copilotDir = resolve(projectDir, '.codex-copilot');
145
- mkdirSync(copilotDir, { recursive: true });
146
-
147
- // Write config file
148
- const config = {
149
- provider: providerId,
150
- prd_path: prdPath,
151
- base_branch: git.currentBranch(projectDir) || 'main',
152
- max_review_rounds: 2,
153
- review_poll_interval: 60,
154
- review_wait_timeout: 600,
155
- created_at: new Date().toISOString(),
156
- };
157
- writeFileSync(resolve(copilotDir, 'config.json'), JSON.stringify(config, null, 2));
158
-
159
- // Write initial state (matches checkpoint.js schema)
160
- const state = {
161
- current_task: 0,
162
- phase: null,
163
- phase_step: null,
164
- current_pr: null,
165
- review_round: 0,
166
- branch: null,
167
- status: 'initialized',
168
- last_updated: new Date().toISOString(),
169
- };
170
- writeFileSync(resolve(copilotDir, 'state.json'), JSON.stringify(state, null, 2));
171
-
172
- // Write CodeX instruction template
173
- const instructions = `# Codex-Copilot Development Instructions
174
-
175
- ## Role
176
- You are an efficient automated development agent responsible for completing feature development according to task descriptions.
177
-
178
- ## Rules
179
- 1. **Follow the project tech stack**: Use the existing frameworks, tools, and code style
180
- 2. **Test first**: Ensure code compiles/runs before finishing
181
- 3. **Git conventions**: Commit message format \`feat(task-N): brief description\` or \`fix(task-N): brief description\`
182
- 4. **Don't modify unrelated files**: Only change files required by the current task
183
- 5. **Commit when done**: \`git add -A && git commit -m "..."\`
184
-
185
- ## Rules for fixing reviews
186
- 1. Read each review comment carefully
187
- 2. Distinguish between must-fix vs. suggestions
188
- 3. If you disagree with a comment, explain why in the commit message
189
- 4. Ensure fixes don't introduce new issues
190
- `;
191
- writeFileSync(resolve(copilotDir, 'codex-instructions.md'), instructions);
192
-
193
- // Add .gitignore entries
194
- const gitignorePath = resolve(projectDir, '.gitignore');
195
- const gitignoreEntry = '\n# Codex-Copilot state\n.codex-copilot/state.json\n';
196
- if (existsSync(gitignorePath)) {
197
- const content = readFileSync(gitignorePath, 'utf-8');
198
- if (!content.includes('.codex-copilot/state.json')) {
199
- writeFileSync(gitignorePath, content + gitignoreEntry);
200
- }
201
- }
202
-
203
- log.info('.codex-copilot/ directory created');
204
- log.blank();
205
-
206
- // ===== Generate task decomposition prompt =====
207
- log.step('Generating task decomposition prompt...');
208
-
209
- const parsePrompt = buildParsePrompt(prdContent, copilotDir);
210
- const promptPath = resolve(copilotDir, 'parse-prd-prompt.md');
211
- writeFileSync(promptPath, parsePrompt);
212
-
213
- log.info('Task decomposition prompt saved to: .codex-copilot/parse-prd-prompt.md');
214
- log.blank();
215
-
216
- // ===== Guide user to next steps =====
217
- log.title('✅ Initialization complete!');
218
- log.blank();
219
- log.info(`AI Provider: ${providerInfo.name}`);
220
- log.blank();
221
-
222
- if (providerInfo.type === 'cli') {
223
- // CLI provider: offer auto-decompose
224
- const autoparse = await confirm(`Auto-invoke ${providerInfo.name} to decompose PRD?`);
225
- if (autoparse) {
226
- log.step(`Invoking ${providerInfo.name} to decompose PRD...`);
227
- const result = await provider.executePrompt(providerId, promptPath, projectDir);
228
- if (result.ok) {
229
- log.info('PRD decomposition complete!');
230
- }
231
- } else {
232
- showManualInstructions(providerInfo);
233
- }
234
- } else {
235
- // IDE provider: show manual instructions
236
- showManualInstructions(providerInfo);
237
- }
238
-
239
- closePrompt();
240
- }
241
-
242
- function showManualInstructions(providerInfo) {
243
- log.info('Next steps:');
244
- log.blank();
245
- console.log(' ┌───────────────────────────────────────────────────┐');
246
- console.log(` │ 1. Open ${providerInfo.name.padEnd(40)}│`);
247
- console.log(' │ 2. Paste the file content: │');
248
- console.log(' │ .codex-copilot/parse-prd-prompt.md │');
249
- console.log(' │ 3. AI will generate .codex-copilot/tasks.json │');
250
- console.log(' │ 4. Then run: codex-copilot run │');
251
- console.log(' └───────────────────────────────────────────────────┘');
252
- log.blank();
253
- }
254
-
255
- function buildParsePrompt(prdContent, copilotDir) {
256
- return `Read the following PRD document and break it down into independent development tasks.
257
-
258
- ## Requirements
259
- 1. Each task should be completable in a single PR (moderate granularity — not too large, not too small)
260
- 2. Each task must have clear acceptance criteria
261
- 3. Tasks should be ordered by dependency (dependent tasks first)
262
- 4. The first task is usually "Project initialization / base framework setup"
263
-
264
- ## Output Format
265
- Write the result to \`.codex-copilot/tasks.json\` in the following format:
266
-
267
- \`\`\`json
268
- {
269
- "project": "Project Name",
270
- "total": 5,
271
- "tasks": [
272
- {
273
- "id": 1,
274
- "title": "Task title (brief)",
275
- "description": "Detailed task description with specific features and technical details",
276
- "acceptance": ["Acceptance criteria 1", "Acceptance criteria 2"],
277
- "branch": "feature/001-task-slug",
278
- "status": "pending",
279
- "depends_on": []
280
- },
281
- {
282
- "id": 2,
283
- "title": "...",
284
- "description": "...",
285
- "acceptance": ["..."],
286
- "branch": "feature/002-task-slug",
287
- "status": "pending",
288
- "depends_on": [1]
289
- }
290
- ]
291
- }
292
- \`\`\`
293
-
294
- ## PRD Document Content
295
-
296
- ${prdContent}
297
- `;
298
- }
1
+ /**
2
+ * codex-copilot init - Initialize project
3
+ *
4
+ * 1. Auto-detect PRD document
5
+ * 2. Let user confirm/select PRD
6
+ * 3. Select AI provider (Codex, Claude Code, Cursor, etc.)
7
+ * 4. Generate task decomposition prompt
8
+ * 5. Create .codex-copilot/ directory structure
9
+ */
10
+
11
+ import { mkdirSync, writeFileSync, readFileSync, existsSync } from 'fs';
12
+ import { resolve } from 'path';
13
+ import { detectPRD, readPRD } from '../utils/detect-prd.js';
14
+ import { log } from '../utils/logger.js';
15
+ import { ask, confirm, select, closePrompt } from '../utils/prompt.js';
16
+ import { git } from '../utils/git.js';
17
+ import { github } from '../utils/github.js';
18
+ import { provider } from '../utils/provider.js';
19
+
20
+ export async function init(projectDir) {
21
+ log.title('📋 Initializing Codex-Copilot');
22
+ log.blank();
23
+
24
+ // ===== Pre-flight checks =====
25
+ log.step('Checking environment...');
26
+
27
+ // Check if in a git repo
28
+ try {
29
+ git.currentBranch(projectDir);
30
+ log.info('Git repository ✓');
31
+ } catch {
32
+ log.error('Not a Git repository. Run: git init');
33
+ closePrompt();
34
+ process.exit(1);
35
+ }
36
+
37
+ // Check gh CLI
38
+ if (!github.checkGhAuth()) {
39
+ log.error('GitHub CLI not authenticated. Run: gh auth login');
40
+ closePrompt();
41
+ process.exit(1);
42
+ }
43
+ log.info('GitHub CLI ✓');
44
+
45
+ // Check GitHub remote
46
+ try {
47
+ const repo = git.getRepoInfo(projectDir);
48
+ log.info(`GitHub repo: ${repo.owner}/${repo.repo} ✓`);
49
+ } catch (err) {
50
+ log.error(err.message);
51
+ closePrompt();
52
+ process.exit(1);
53
+ }
54
+
55
+ log.blank();
56
+
57
+ // ===== Detect PRD =====
58
+ log.step('Scanning for PRD documents...');
59
+ const candidates = detectPRD(projectDir);
60
+
61
+ let prdPath;
62
+
63
+ if (candidates.length === 0) {
64
+ log.warn('No PRD document detected automatically');
65
+ const manualPath = await ask('Enter PRD file path (relative or absolute):');
66
+ prdPath = resolve(projectDir, manualPath);
67
+ // Prevent path traversal
68
+ if (!prdPath.startsWith(resolve(projectDir)) && !manualPath.startsWith('/')) {
69
+ log.error('PRD path is outside the project directory');
70
+ closePrompt();
71
+ process.exit(1);
72
+ }
73
+ if (!existsSync(prdPath)) {
74
+ log.error(`File not found: ${prdPath}`);
75
+ closePrompt();
76
+ process.exit(1);
77
+ }
78
+ } else if (candidates.length === 1) {
79
+ prdPath = candidates[0].path;
80
+ log.info(`Found PRD: ${candidates[0].relativePath}`);
81
+ const ok = await confirm('Use this file?');
82
+ if (!ok) {
83
+ const manualPath = await ask('Enter PRD file path:');
84
+ prdPath = resolve(projectDir, manualPath);
85
+ }
86
+ } else {
87
+ log.info(`Found ${candidates.length} candidate PRD files:`);
88
+ const choice = await select('Select the PRD to use:', candidates.map(c => ({
89
+ label: `${c.relativePath} (score: ${c.score})`,
90
+ value: c.path,
91
+ })));
92
+ prdPath = choice.value;
93
+ }
94
+
95
+ log.info(`Using PRD: ${prdPath}`);
96
+ log.blank();
97
+
98
+ // ===== Read PRD =====
99
+ const prdContent = readPRD(prdPath);
100
+ log.info(`PRD size: ${(prdContent.length / 1024).toFixed(1)} KB`);
101
+
102
+ // ===== Select AI Provider =====
103
+ log.step('Select AI coding tool...');
104
+
105
+ // Check versions of installed CLI tools (non-blocking)
106
+ log.info('Checking tool versions...');
107
+ let versionCache = {};
108
+ try {
109
+ versionCache = provider.checkAllVersions();
110
+ const updatable = Object.entries(versionCache).filter(([, v]) => v.updateAvailable);
111
+ if (updatable.length > 0) {
112
+ for (const [id, v] of updatable) {
113
+ log.warn(`${provider.getProvider(id).name}: v${v.current} → v${v.latest} update available`);
114
+ }
115
+ } else if (Object.keys(versionCache).length > 0) {
116
+ log.info('All detected tools are up to date ✓');
117
+ }
118
+ } catch {
119
+ // Version check failed — continue normally
120
+ }
121
+
122
+ log.blank();
123
+ const providerChoices = provider.buildProviderChoices(versionCache);
124
+ const selectedProvider = await select('Which AI tool will you use?', providerChoices);
125
+ const providerId = selectedProvider.value;
126
+ const providerInfo = provider.getProvider(providerId);
127
+ log.info(`Selected: ${providerInfo.name}`);
128
+
129
+ // Offer update if newer version available
130
+ const vi = versionCache[providerId];
131
+ if (vi && vi.updateAvailable) {
132
+ log.blank();
133
+ log.warn(`${providerInfo.name} v${vi.current} → v${vi.latest} available`);
134
+ const doUpdate = await confirm(`Update ${providerInfo.name} now?`);
135
+ if (doUpdate) {
136
+ provider.updateProvider(providerId);
137
+ }
138
+ }
139
+
140
+ log.blank();
141
+
142
+ // ===== Create .codex-copilot directory =====
143
+ log.step('Creating .codex-copilot/ directory...');
144
+ const copilotDir = resolve(projectDir, '.codex-copilot');
145
+ mkdirSync(copilotDir, { recursive: true });
146
+
147
+ // Write config file
148
+ const config = {
149
+ provider: providerId,
150
+ prd_path: prdPath,
151
+ base_branch: git.currentBranch(projectDir) || 'main',
152
+ max_review_rounds: 2,
153
+ review_poll_interval: 60,
154
+ review_wait_timeout: 600,
155
+ created_at: new Date().toISOString(),
156
+ };
157
+ writeFileSync(resolve(copilotDir, 'config.json'), JSON.stringify(config, null, 2));
158
+
159
+ // Write initial state (matches checkpoint.js schema)
160
+ const state = {
161
+ current_task: 0,
162
+ phase: null,
163
+ phase_step: null,
164
+ current_pr: null,
165
+ review_round: 0,
166
+ branch: null,
167
+ status: 'initialized',
168
+ last_updated: new Date().toISOString(),
169
+ };
170
+ writeFileSync(resolve(copilotDir, 'state.json'), JSON.stringify(state, null, 2));
171
+
172
+ // Write CodeX instruction template
173
+ const instructions = `# Codex-Copilot Development Instructions
174
+
175
+ ## Role
176
+ You are an efficient automated development agent responsible for completing feature development according to task descriptions.
177
+
178
+ ## Rules
179
+ 1. **Follow the project tech stack**: Use the existing frameworks, tools, and code style
180
+ 2. **Test first**: Ensure code compiles/runs before finishing
181
+ 3. **Git conventions**: Commit message format \`feat(task-N): brief description\` or \`fix(task-N): brief description\`
182
+ 4. **Don't modify unrelated files**: Only change files required by the current task
183
+ 5. **Commit when done**: \`git add -A && git commit -m "..."\`
184
+
185
+ ## Rules for fixing reviews
186
+ 1. Read each review comment carefully
187
+ 2. Distinguish between must-fix vs. suggestions
188
+ 3. If you disagree with a comment, explain why in the commit message
189
+ 4. Ensure fixes don't introduce new issues
190
+ `;
191
+ writeFileSync(resolve(copilotDir, 'codex-instructions.md'), instructions);
192
+
193
+ // Add .gitignore entries
194
+ const gitignorePath = resolve(projectDir, '.gitignore');
195
+ const gitignoreEntry = '\n# Codex-Copilot state\n.codex-copilot/state.json\n';
196
+ if (existsSync(gitignorePath)) {
197
+ const content = readFileSync(gitignorePath, 'utf-8');
198
+ if (!content.includes('.codex-copilot/state.json')) {
199
+ writeFileSync(gitignorePath, content + gitignoreEntry);
200
+ }
201
+ }
202
+
203
+ log.info('.codex-copilot/ directory created');
204
+ log.blank();
205
+
206
+ // ===== Generate task decomposition prompt =====
207
+ log.step('Generating task decomposition prompt...');
208
+
209
+ const parsePrompt = buildParsePrompt(prdContent, copilotDir);
210
+ const promptPath = resolve(copilotDir, 'parse-prd-prompt.md');
211
+ writeFileSync(promptPath, parsePrompt);
212
+
213
+ log.info('Task decomposition prompt saved to: .codex-copilot/parse-prd-prompt.md');
214
+ log.blank();
215
+
216
+ // ===== Guide user to next steps =====
217
+ log.title('✅ Initialization complete!');
218
+ log.blank();
219
+ log.info(`AI Provider: ${providerInfo.name}`);
220
+ log.blank();
221
+
222
+ if (providerInfo.type === 'cli') {
223
+ // CLI provider: offer auto-decompose
224
+ const autoparse = await confirm(`Auto-invoke ${providerInfo.name} to decompose PRD?`);
225
+ if (autoparse) {
226
+ log.step(`Invoking ${providerInfo.name} to decompose PRD...`);
227
+ const result = await provider.executePrompt(providerId, promptPath, projectDir);
228
+ if (result.ok) {
229
+ log.info('PRD decomposition complete!');
230
+ }
231
+ } else {
232
+ showManualInstructions(providerInfo);
233
+ }
234
+ } else {
235
+ // IDE provider: show manual instructions
236
+ showManualInstructions(providerInfo);
237
+ }
238
+
239
+ closePrompt();
240
+ }
241
+
242
+ function showManualInstructions(providerInfo) {
243
+ log.info('Next steps:');
244
+ log.blank();
245
+ console.log(' ┌───────────────────────────────────────────────────┐');
246
+ console.log(` │ 1. Open ${providerInfo.name.padEnd(40)}│`);
247
+ console.log(' │ 2. Paste the file content: │');
248
+ console.log(' │ .codex-copilot/parse-prd-prompt.md │');
249
+ console.log(' │ 3. AI will generate .codex-copilot/tasks.json │');
250
+ console.log(' │ 4. Then run: codex-copilot run │');
251
+ console.log(' └───────────────────────────────────────────────────┘');
252
+ log.blank();
253
+ }
254
+
255
+ function buildParsePrompt(prdContent, copilotDir) {
256
+ return `Read the following PRD document and break it down into independent development tasks.
257
+
258
+ ## Requirements
259
+ 1. Each task should be completable in a single PR (moderate granularity — not too large, not too small)
260
+ 2. Each task must have clear acceptance criteria
261
+ 3. Tasks should be ordered by dependency (dependent tasks first)
262
+ 4. The first task is usually "Project initialization / base framework setup"
263
+
264
+ ## Output Format
265
+ Write the result to \`.codex-copilot/tasks.json\` in the following format:
266
+
267
+ \`\`\`json
268
+ {
269
+ "project": "Project Name",
270
+ "total": 5,
271
+ "tasks": [
272
+ {
273
+ "id": 1,
274
+ "title": "Task title (brief)",
275
+ "description": "Detailed task description with specific features and technical details",
276
+ "acceptance": ["Acceptance criteria 1", "Acceptance criteria 2"],
277
+ "branch": "feature/001-task-slug",
278
+ "status": "pending",
279
+ "depends_on": []
280
+ },
281
+ {
282
+ "id": 2,
283
+ "title": "...",
284
+ "description": "...",
285
+ "acceptance": ["..."],
286
+ "branch": "feature/002-task-slug",
287
+ "status": "pending",
288
+ "depends_on": [1]
289
+ }
290
+ ]
291
+ }
292
+ \`\`\`
293
+
294
+ ## PRD Document Content
295
+
296
+ ${prdContent}
297
+ `;
298
+ }