@jojonax/codex-copilot 1.5.5 → 1.6.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/LICENSE +21 -21
- package/README.md +144 -44
- package/bin/cli.js +189 -182
- package/package.json +39 -39
- package/src/commands/evolve.js +316 -316
- package/src/commands/fix.js +447 -447
- package/src/commands/init.js +298 -298
- package/src/commands/reset.js +61 -61
- package/src/commands/retry.js +190 -190
- package/src/commands/run.js +958 -958
- package/src/commands/skip.js +62 -62
- package/src/commands/status.js +95 -95
- package/src/commands/usage.js +361 -361
- package/src/utils/automator.js +279 -279
- package/src/utils/checkpoint.js +246 -246
- package/src/utils/detect-prd.js +137 -137
- package/src/utils/git.js +393 -388
- package/src/utils/github.js +489 -486
- package/src/utils/json.js +220 -220
- package/src/utils/logger.js +41 -41
- package/src/utils/prompt.js +49 -49
- package/src/utils/provider.js +773 -769
- package/src/utils/self-heal.js +330 -330
- package/src/utils/shell-bootstrap.js +404 -0
- package/src/utils/update-check.js +103 -103
package/src/commands/init.js
CHANGED
|
@@ -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
|
+
}
|