@agentic15.com/agentic15-claude-zen 4.1.6 → 4.2.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.
- package/package.json +1 -1
- package/src/cli/CommitCommand.js +19 -0
- package/src/cli/PlanCommand.js +291 -282
- package/src/cli/TaskCommand.js +305 -287
- package/templates/.claude/settings.json +1 -7
package/package.json
CHANGED
package/src/cli/CommitCommand.js
CHANGED
|
@@ -267,6 +267,11 @@ export class CommitCommand {
|
|
|
267
267
|
if (taskInTracker) {
|
|
268
268
|
taskInTracker.status = 'completed';
|
|
269
269
|
taskInTracker.completedAt = new Date().toISOString();
|
|
270
|
+
|
|
271
|
+
// Clear active task and update statistics
|
|
272
|
+
tracker.activeTask = null;
|
|
273
|
+
this.updateStatistics(tracker);
|
|
274
|
+
|
|
270
275
|
writeFileSync(trackerPath, JSON.stringify(tracker, null, 2));
|
|
271
276
|
console.log(`\n✅ Marked ${task.id} as completed`);
|
|
272
277
|
}
|
|
@@ -276,6 +281,20 @@ export class CommitCommand {
|
|
|
276
281
|
}
|
|
277
282
|
}
|
|
278
283
|
|
|
284
|
+
static updateStatistics(tracker) {
|
|
285
|
+
const completed = tracker.taskFiles.filter(t => t.status === 'completed').length;
|
|
286
|
+
const inProgress = tracker.taskFiles.filter(t => t.status === 'in_progress').length;
|
|
287
|
+
const pending = tracker.taskFiles.filter(t => t.status === 'pending').length;
|
|
288
|
+
const totalTasks = tracker.taskFiles.length;
|
|
289
|
+
|
|
290
|
+
tracker.statistics = {
|
|
291
|
+
totalTasks,
|
|
292
|
+
completed,
|
|
293
|
+
inProgress,
|
|
294
|
+
pending
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
279
298
|
static displaySummary(task, prUrl, tracker) {
|
|
280
299
|
console.log('\n┌─────────────────────────────────────────┐');
|
|
281
300
|
console.log('│ ✅ Commit Workflow Complete │');
|
package/src/cli/PlanCommand.js
CHANGED
|
@@ -1,282 +1,291 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
|
|
2
|
-
import { join } from 'path';
|
|
3
|
-
import readline from 'readline';
|
|
4
|
-
|
|
5
|
-
export class PlanCommand {
|
|
6
|
-
static async handle(description) {
|
|
7
|
-
// Check if plan already exists
|
|
8
|
-
const activePlanPath = join(process.cwd(), '.claude', 'ACTIVE-PLAN');
|
|
9
|
-
|
|
10
|
-
if (existsSync(activePlanPath)) {
|
|
11
|
-
const planId = readFileSync(activePlanPath, 'utf-8').trim();
|
|
12
|
-
const planPath = join(process.cwd(), '.claude', 'plans', planId);
|
|
13
|
-
const projectPlanPath = join(planPath, 'PROJECT-PLAN.json');
|
|
14
|
-
|
|
15
|
-
// Check if plan file exists
|
|
16
|
-
if (existsSync(projectPlanPath)) {
|
|
17
|
-
// Plan exists, check if it's locked
|
|
18
|
-
const lockedPath = join(planPath, '.plan-locked');
|
|
19
|
-
|
|
20
|
-
if (existsSync(lockedPath)) {
|
|
21
|
-
console.log('\n⚠️ Plan already locked');
|
|
22
|
-
console.log(` Plan: ${planId}\n`);
|
|
23
|
-
this.showPlanStatus(planId);
|
|
24
|
-
process.exit(0);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Plan exists but not locked - lock it
|
|
28
|
-
console.log('\n📋 Found existing plan, locking it...\n');
|
|
29
|
-
return this.lockPlan(planId);
|
|
30
|
-
} else {
|
|
31
|
-
// Requirements exist but plan not created yet
|
|
32
|
-
console.log('\n⚠️ Waiting for PROJECT-PLAN.json');
|
|
33
|
-
console.log(` Tell Claude: "Create the project plan"\n`);
|
|
34
|
-
process.exit(0);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// No plan exists - create new one
|
|
39
|
-
if (!description) {
|
|
40
|
-
// No description provided - enter interactive mode
|
|
41
|
-
console.log('\n📝 Interactive Requirements Mode');
|
|
42
|
-
console.log('━'.repeat(70));
|
|
43
|
-
console.log('Enter your project requirements below.');
|
|
44
|
-
console.log('You can paste multiple lines, URLs, or write detailed specs.');
|
|
45
|
-
console.log('Press Ctrl+D (Mac/Linux) or Ctrl+Z then Enter (Windows) when done.\n');
|
|
46
|
-
|
|
47
|
-
description = await this.promptMultilineInput();
|
|
48
|
-
|
|
49
|
-
if (!description || description.trim().length === 0) {
|
|
50
|
-
console.log('\n❌ No requirements provided\n');
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return this.generatePlan(description);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
static async promptMultilineInput() {
|
|
59
|
-
return new Promise((resolve) => {
|
|
60
|
-
const rl = readline.createInterface({
|
|
61
|
-
input: process.stdin,
|
|
62
|
-
output: process.stdout
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
let lines = [];
|
|
66
|
-
|
|
67
|
-
rl.on('line', (line) => {
|
|
68
|
-
lines.push(line);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
rl.on('close', () => {
|
|
72
|
-
resolve(lines.join('\n'));
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
static generatePlan(description) {
|
|
78
|
-
console.log('\n📋 Generating new plan...\n');
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
// Create plan ID
|
|
82
|
-
const planId = this.getNextPlanId();
|
|
83
|
-
const planPath = join(process.cwd(), '.claude', 'plans', planId);
|
|
84
|
-
|
|
85
|
-
// Create plan directory
|
|
86
|
-
mkdirSync(planPath, { recursive: true });
|
|
87
|
-
|
|
88
|
-
// Create PROJECT-REQUIREMENTS.txt
|
|
89
|
-
const requirementsPath = join(planPath, 'PROJECT-REQUIREMENTS.txt');
|
|
90
|
-
const requirementsContent = `PROJECT REQUIREMENTS
|
|
91
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
92
|
-
|
|
93
|
-
${description}
|
|
94
|
-
|
|
95
|
-
Generated: ${new Date().toISOString()}
|
|
96
|
-
PLAN ID: ${planId}
|
|
97
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
98
|
-
|
|
99
|
-
INSTRUCTIONS FOR CLAUDE
|
|
100
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
101
|
-
|
|
102
|
-
Please analyze the requirements above and create a comprehensive project plan.
|
|
103
|
-
|
|
104
|
-
1. Read the PLAN-SCHEMA.json to understand the plan structure
|
|
105
|
-
2. Read the PROJECT-PLAN-TEMPLATE.json for the format
|
|
106
|
-
3. Create a PROJECT-PLAN.json file in this directory with:
|
|
107
|
-
- Clear project/subproject/milestone hierarchy
|
|
108
|
-
- Detailed tasks with IDs (TASK-001, TASK-002, etc.)
|
|
109
|
-
- Proper dependencies between tasks
|
|
110
|
-
- Realistic time estimates
|
|
111
|
-
- Phases: design, implementation, testing, deployment
|
|
112
|
-
- Completion criteria for each task
|
|
113
|
-
|
|
114
|
-
4. Structure the plan to follow these phases:
|
|
115
|
-
- DESIGN: Architecture, UI/UX, database schema
|
|
116
|
-
- IMPLEMENTATION: Core features, API, frontend
|
|
117
|
-
- TESTING: Unit tests, integration tests, E2E tests
|
|
118
|
-
- DEPLOYMENT: Build, CI/CD, documentation
|
|
119
|
-
|
|
120
|
-
5. Ensure tasks are:
|
|
121
|
-
- Granular (2-8 hours each)
|
|
122
|
-
- Clearly defined with specific deliverables
|
|
123
|
-
- Properly sequenced with dependencies
|
|
124
|
-
- Grouped logically by feature/component
|
|
125
|
-
|
|
126
|
-
6. After creating the plan, tell the user to run:
|
|
127
|
-
npx agentic15 plan
|
|
128
|
-
|
|
129
|
-
This will lock the plan and generate the task tracker.
|
|
130
|
-
|
|
131
|
-
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
132
|
-
GENERATED: ${new Date().toISOString()}
|
|
133
|
-
`;
|
|
134
|
-
|
|
135
|
-
writeFileSync(requirementsPath, requirementsContent);
|
|
136
|
-
|
|
137
|
-
// Set as active plan
|
|
138
|
-
const claudeDir = join(process.cwd(), '.claude');
|
|
139
|
-
if (!existsSync(claudeDir)) {
|
|
140
|
-
mkdirSync(claudeDir, { recursive: true });
|
|
141
|
-
}
|
|
142
|
-
const activePlanPath = join(claudeDir, 'ACTIVE-PLAN');
|
|
143
|
-
writeFileSync(activePlanPath, planId);
|
|
144
|
-
|
|
145
|
-
console.log(`✅ Plan requirements created: ${planId}`);
|
|
146
|
-
console.log(` Location: .claude/plans/${planId}/PROJECT-REQUIREMENTS.txt\n`);
|
|
147
|
-
console.log('💡 Next steps:');
|
|
148
|
-
console.log(` 1. Tell Claude: "Create the project plan"`);
|
|
149
|
-
console.log(` 2. When Claude is done, run: npx agentic15 plan\n`);
|
|
150
|
-
} catch (error) {
|
|
151
|
-
console.log(`\n❌ Failed to generate plan: ${error.message}\n`);
|
|
152
|
-
process.exit(1);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
static lockPlan(planId) {
|
|
157
|
-
console.log(`📋 Locking plan: ${planId}\n`);
|
|
158
|
-
|
|
159
|
-
try {
|
|
160
|
-
const planPath = join(process.cwd(), '.claude', 'plans', planId);
|
|
161
|
-
const projectPlanPath = join(planPath, 'PROJECT-PLAN.json');
|
|
162
|
-
|
|
163
|
-
// Verify PROJECT-PLAN.json exists
|
|
164
|
-
if (!existsSync(projectPlanPath)) {
|
|
165
|
-
console.log('\n❌ PROJECT-PLAN.json not found');
|
|
166
|
-
console.log(' Tell Claude to create the plan first\n');
|
|
167
|
-
process.exit(1);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Read the plan
|
|
171
|
-
const plan = JSON.parse(readFileSync(projectPlanPath, 'utf-8'));
|
|
172
|
-
|
|
173
|
-
// Extract tasks from plan
|
|
174
|
-
const tasks = [];
|
|
175
|
-
this.extractTasks(plan, tasks);
|
|
176
|
-
|
|
177
|
-
// Create task files
|
|
178
|
-
const tasksDir = join(planPath, 'tasks');
|
|
179
|
-
if (!existsSync(tasksDir)) {
|
|
180
|
-
mkdirSync(tasksDir, { recursive: true });
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Write individual task files
|
|
184
|
-
tasks.forEach(task => {
|
|
185
|
-
const taskPath = join(tasksDir, `${task.id}.json`);
|
|
186
|
-
writeFileSync(taskPath, JSON.stringify(task, null, 2));
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
// Create task tracker
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (obj.
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (obj.
|
|
239
|
-
this.extractTasks(
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
|
|
2
|
+
import { join, basename } from 'path';
|
|
3
|
+
import readline from 'readline';
|
|
4
|
+
|
|
5
|
+
export class PlanCommand {
|
|
6
|
+
static async handle(description) {
|
|
7
|
+
// Check if plan already exists
|
|
8
|
+
const activePlanPath = join(process.cwd(), '.claude', 'ACTIVE-PLAN');
|
|
9
|
+
|
|
10
|
+
if (existsSync(activePlanPath)) {
|
|
11
|
+
const planId = readFileSync(activePlanPath, 'utf-8').trim();
|
|
12
|
+
const planPath = join(process.cwd(), '.claude', 'plans', planId);
|
|
13
|
+
const projectPlanPath = join(planPath, 'PROJECT-PLAN.json');
|
|
14
|
+
|
|
15
|
+
// Check if plan file exists
|
|
16
|
+
if (existsSync(projectPlanPath)) {
|
|
17
|
+
// Plan exists, check if it's locked
|
|
18
|
+
const lockedPath = join(planPath, '.plan-locked');
|
|
19
|
+
|
|
20
|
+
if (existsSync(lockedPath)) {
|
|
21
|
+
console.log('\n⚠️ Plan already locked');
|
|
22
|
+
console.log(` Plan: ${planId}\n`);
|
|
23
|
+
this.showPlanStatus(planId);
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Plan exists but not locked - lock it
|
|
28
|
+
console.log('\n📋 Found existing plan, locking it...\n');
|
|
29
|
+
return this.lockPlan(planId);
|
|
30
|
+
} else {
|
|
31
|
+
// Requirements exist but plan not created yet
|
|
32
|
+
console.log('\n⚠️ Waiting for PROJECT-PLAN.json');
|
|
33
|
+
console.log(` Tell Claude: "Create the project plan"\n`);
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// No plan exists - create new one
|
|
39
|
+
if (!description) {
|
|
40
|
+
// No description provided - enter interactive mode
|
|
41
|
+
console.log('\n📝 Interactive Requirements Mode');
|
|
42
|
+
console.log('━'.repeat(70));
|
|
43
|
+
console.log('Enter your project requirements below.');
|
|
44
|
+
console.log('You can paste multiple lines, URLs, or write detailed specs.');
|
|
45
|
+
console.log('Press Ctrl+D (Mac/Linux) or Ctrl+Z then Enter (Windows) when done.\n');
|
|
46
|
+
|
|
47
|
+
description = await this.promptMultilineInput();
|
|
48
|
+
|
|
49
|
+
if (!description || description.trim().length === 0) {
|
|
50
|
+
console.log('\n❌ No requirements provided\n');
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return this.generatePlan(description);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static async promptMultilineInput() {
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
const rl = readline.createInterface({
|
|
61
|
+
input: process.stdin,
|
|
62
|
+
output: process.stdout
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
let lines = [];
|
|
66
|
+
|
|
67
|
+
rl.on('line', (line) => {
|
|
68
|
+
lines.push(line);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
rl.on('close', () => {
|
|
72
|
+
resolve(lines.join('\n'));
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
static generatePlan(description) {
|
|
78
|
+
console.log('\n📋 Generating new plan...\n');
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
// Create plan ID
|
|
82
|
+
const planId = this.getNextPlanId();
|
|
83
|
+
const planPath = join(process.cwd(), '.claude', 'plans', planId);
|
|
84
|
+
|
|
85
|
+
// Create plan directory
|
|
86
|
+
mkdirSync(planPath, { recursive: true });
|
|
87
|
+
|
|
88
|
+
// Create PROJECT-REQUIREMENTS.txt
|
|
89
|
+
const requirementsPath = join(planPath, 'PROJECT-REQUIREMENTS.txt');
|
|
90
|
+
const requirementsContent = `PROJECT REQUIREMENTS
|
|
91
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
92
|
+
|
|
93
|
+
${description}
|
|
94
|
+
|
|
95
|
+
Generated: ${new Date().toISOString()}
|
|
96
|
+
PLAN ID: ${planId}
|
|
97
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
98
|
+
|
|
99
|
+
INSTRUCTIONS FOR CLAUDE
|
|
100
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
101
|
+
|
|
102
|
+
Please analyze the requirements above and create a comprehensive project plan.
|
|
103
|
+
|
|
104
|
+
1. Read the PLAN-SCHEMA.json to understand the plan structure
|
|
105
|
+
2. Read the PROJECT-PLAN-TEMPLATE.json for the format
|
|
106
|
+
3. Create a PROJECT-PLAN.json file in this directory with:
|
|
107
|
+
- Clear project/subproject/milestone hierarchy
|
|
108
|
+
- Detailed tasks with IDs (TASK-001, TASK-002, etc.)
|
|
109
|
+
- Proper dependencies between tasks
|
|
110
|
+
- Realistic time estimates
|
|
111
|
+
- Phases: design, implementation, testing, deployment
|
|
112
|
+
- Completion criteria for each task
|
|
113
|
+
|
|
114
|
+
4. Structure the plan to follow these phases:
|
|
115
|
+
- DESIGN: Architecture, UI/UX, database schema
|
|
116
|
+
- IMPLEMENTATION: Core features, API, frontend
|
|
117
|
+
- TESTING: Unit tests, integration tests, E2E tests
|
|
118
|
+
- DEPLOYMENT: Build, CI/CD, documentation
|
|
119
|
+
|
|
120
|
+
5. Ensure tasks are:
|
|
121
|
+
- Granular (2-8 hours each)
|
|
122
|
+
- Clearly defined with specific deliverables
|
|
123
|
+
- Properly sequenced with dependencies
|
|
124
|
+
- Grouped logically by feature/component
|
|
125
|
+
|
|
126
|
+
6. After creating the plan, tell the user to run:
|
|
127
|
+
npx agentic15 plan
|
|
128
|
+
|
|
129
|
+
This will lock the plan and generate the task tracker.
|
|
130
|
+
|
|
131
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
132
|
+
GENERATED: ${new Date().toISOString()}
|
|
133
|
+
`;
|
|
134
|
+
|
|
135
|
+
writeFileSync(requirementsPath, requirementsContent);
|
|
136
|
+
|
|
137
|
+
// Set as active plan
|
|
138
|
+
const claudeDir = join(process.cwd(), '.claude');
|
|
139
|
+
if (!existsSync(claudeDir)) {
|
|
140
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
141
|
+
}
|
|
142
|
+
const activePlanPath = join(claudeDir, 'ACTIVE-PLAN');
|
|
143
|
+
writeFileSync(activePlanPath, planId);
|
|
144
|
+
|
|
145
|
+
console.log(`✅ Plan requirements created: ${planId}`);
|
|
146
|
+
console.log(` Location: .claude/plans/${planId}/PROJECT-REQUIREMENTS.txt\n`);
|
|
147
|
+
console.log('💡 Next steps:');
|
|
148
|
+
console.log(` 1. Tell Claude: "Create the project plan"`);
|
|
149
|
+
console.log(` 2. When Claude is done, run: npx agentic15 plan\n`);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.log(`\n❌ Failed to generate plan: ${error.message}\n`);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
static lockPlan(planId) {
|
|
157
|
+
console.log(`📋 Locking plan: ${planId}\n`);
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const planPath = join(process.cwd(), '.claude', 'plans', planId);
|
|
161
|
+
const projectPlanPath = join(planPath, 'PROJECT-PLAN.json');
|
|
162
|
+
|
|
163
|
+
// Verify PROJECT-PLAN.json exists
|
|
164
|
+
if (!existsSync(projectPlanPath)) {
|
|
165
|
+
console.log('\n❌ PROJECT-PLAN.json not found');
|
|
166
|
+
console.log(' Tell Claude to create the plan first\n');
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Read the plan
|
|
171
|
+
const plan = JSON.parse(readFileSync(projectPlanPath, 'utf-8'));
|
|
172
|
+
|
|
173
|
+
// Extract tasks from plan
|
|
174
|
+
const tasks = [];
|
|
175
|
+
this.extractTasks(plan, tasks);
|
|
176
|
+
|
|
177
|
+
// Create task files
|
|
178
|
+
const tasksDir = join(planPath, 'tasks');
|
|
179
|
+
if (!existsSync(tasksDir)) {
|
|
180
|
+
mkdirSync(tasksDir, { recursive: true });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Write individual task files
|
|
184
|
+
tasks.forEach(task => {
|
|
185
|
+
const taskPath = join(tasksDir, `${task.id}.json`);
|
|
186
|
+
writeFileSync(taskPath, JSON.stringify(task, null, 2));
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Create task tracker
|
|
190
|
+
const projectName = basename(process.cwd());
|
|
191
|
+
const tracker = {
|
|
192
|
+
planId,
|
|
193
|
+
projectName,
|
|
194
|
+
activeTask: null,
|
|
195
|
+
lockedAt: new Date().toISOString(),
|
|
196
|
+
statistics: {
|
|
197
|
+
totalTasks: tasks.length,
|
|
198
|
+
completed: 0,
|
|
199
|
+
inProgress: 0,
|
|
200
|
+
pending: tasks.length
|
|
201
|
+
},
|
|
202
|
+
taskFiles: tasks.map(task => ({
|
|
203
|
+
id: task.id,
|
|
204
|
+
title: task.title,
|
|
205
|
+
phase: task.phase || 'implementation',
|
|
206
|
+
status: 'pending',
|
|
207
|
+
description: task.description
|
|
208
|
+
}))
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const trackerPath = join(planPath, 'TASK-TRACKER.json');
|
|
212
|
+
writeFileSync(trackerPath, JSON.stringify(tracker, null, 2));
|
|
213
|
+
|
|
214
|
+
// Mark as locked
|
|
215
|
+
const lockedPath = join(planPath, '.plan-locked');
|
|
216
|
+
writeFileSync(lockedPath, new Date().toISOString());
|
|
217
|
+
|
|
218
|
+
console.log('✅ Plan locked successfully\n');
|
|
219
|
+
this.showPlanStatus(planId);
|
|
220
|
+
console.log('💡 Next step: npx agentic15 task next\n');
|
|
221
|
+
} catch (error) {
|
|
222
|
+
console.log(`\n❌ Failed to lock plan: ${error.message}\n`);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
static extractTasks(obj, tasks) {
|
|
228
|
+
// Recursively extract all tasks from the plan structure
|
|
229
|
+
if (obj.tasks && Array.isArray(obj.tasks)) {
|
|
230
|
+
tasks.push(...obj.tasks);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Check nested structures
|
|
234
|
+
if (obj.milestones && Array.isArray(obj.milestones)) {
|
|
235
|
+
obj.milestones.forEach(milestone => this.extractTasks(milestone, tasks));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (obj.subprojects && Array.isArray(obj.subprojects)) {
|
|
239
|
+
obj.subprojects.forEach(subproject => this.extractTasks(subproject, tasks));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (obj.projects && Array.isArray(obj.projects)) {
|
|
243
|
+
obj.projects.forEach(project => this.extractTasks(project, tasks));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Handle singular 'project' at root level (v2.0 schema)
|
|
247
|
+
if (obj.project && typeof obj.project === 'object') {
|
|
248
|
+
this.extractTasks(obj.project, tasks);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
static getNextPlanId() {
|
|
253
|
+
const plansDir = join(process.cwd(), '.claude', 'plans');
|
|
254
|
+
|
|
255
|
+
if (!existsSync(plansDir)) {
|
|
256
|
+
mkdirSync(plansDir, { recursive: true });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const existingPlans = readdirSync(plansDir)
|
|
260
|
+
.filter(name => name.match(/^plan-\d{3}-/i))
|
|
261
|
+
.map(name => parseInt(name.match(/^plan-(\d{3})-/i)[1]))
|
|
262
|
+
.filter(num => !isNaN(num));
|
|
263
|
+
|
|
264
|
+
const nextNum = existingPlans.length > 0 ? Math.max(...existingPlans) + 1 : 1;
|
|
265
|
+
|
|
266
|
+
return `plan-${String(nextNum).padStart(3, '0')}-generated`;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
static showPlanStatus(planId) {
|
|
270
|
+
const trackerPath = join(process.cwd(), '.claude', 'plans', planId, 'TASK-TRACKER.json');
|
|
271
|
+
|
|
272
|
+
if (!existsSync(trackerPath)) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
const tracker = JSON.parse(readFileSync(trackerPath, 'utf-8'));
|
|
278
|
+
|
|
279
|
+
const total = tracker.taskFiles.length;
|
|
280
|
+
const completed = tracker.taskFiles.filter(t => t.status === 'completed').length;
|
|
281
|
+
const pending = tracker.taskFiles.filter(t => t.status === 'pending').length;
|
|
282
|
+
|
|
283
|
+
console.log('📊 Plan Status:');
|
|
284
|
+
console.log(` Total Tasks: ${total}`);
|
|
285
|
+
console.log(` Completed: ${completed}`);
|
|
286
|
+
console.log(` Pending: ${pending}\n`);
|
|
287
|
+
} catch (e) {
|
|
288
|
+
// Ignore errors
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
package/src/cli/TaskCommand.js
CHANGED
|
@@ -1,287 +1,305 @@
|
|
|
1
|
-
import { execSync } from 'child_process';
|
|
2
|
-
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
3
|
-
import { join } from 'path';
|
|
4
|
-
import { GitHubClient } from '../core/GitHubClient.js';
|
|
5
|
-
import { GitHubConfig } from '../core/GitHubConfig.js';
|
|
6
|
-
import { TaskIssueMapper } from '../core/TaskIssueMapper.js';
|
|
7
|
-
|
|
8
|
-
export class TaskCommand {
|
|
9
|
-
static async handle(action, taskId) {
|
|
10
|
-
switch (action) {
|
|
11
|
-
case 'start':
|
|
12
|
-
return this.startTask(taskId);
|
|
13
|
-
case 'next':
|
|
14
|
-
return this.startNext();
|
|
15
|
-
case 'status':
|
|
16
|
-
return this.showStatus();
|
|
17
|
-
default:
|
|
18
|
-
console.log(`\n❌ Unknown action: ${action}`);
|
|
19
|
-
console.log(' Valid actions: start, next, status\n');
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
static async startTask(taskId) {
|
|
25
|
-
if (!taskId) {
|
|
26
|
-
console.log('\n❌ Task ID required for "start" action');
|
|
27
|
-
console.log(' Usage: agentic15 task start TASK-001\n');
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Verify git remote is configured
|
|
32
|
-
this.validateGitRemote();
|
|
33
|
-
|
|
34
|
-
// Load task tracker
|
|
35
|
-
const tracker = this.loadTracker();
|
|
36
|
-
const task = tracker.taskFiles.find(t => t.id === taskId);
|
|
37
|
-
|
|
38
|
-
if (!task) {
|
|
39
|
-
console.log(`\n❌ Task not found: ${taskId}\n`);
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (task.status === 'completed') {
|
|
44
|
-
console.log(`\n⚠️ Task ${taskId} is already completed\n`);
|
|
45
|
-
process.exit(1);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Check if another task is in progress
|
|
49
|
-
const inProgress = tracker.taskFiles.find(t => t.status === 'in_progress');
|
|
50
|
-
if (inProgress && inProgress.id !== taskId) {
|
|
51
|
-
console.log(`\n⚠️ Task ${inProgress.id} is already in progress`);
|
|
52
|
-
console.log(` Complete it first with: agentic15 commit\n`);
|
|
53
|
-
process.exit(1);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Create feature branch
|
|
57
|
-
const branchName = `feature/${taskId.toLowerCase()}`;
|
|
58
|
-
try {
|
|
59
|
-
execSync(`git checkout -b ${branchName}`, { stdio: 'inherit' });
|
|
60
|
-
} catch (error) {
|
|
61
|
-
// Branch might already exist
|
|
62
|
-
try {
|
|
63
|
-
execSync(`git checkout ${branchName}`, { stdio: 'inherit' });
|
|
64
|
-
} catch (e) {
|
|
65
|
-
console.log(`\n❌ Failed to create/checkout branch: ${branchName}\n`);
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Update task status
|
|
71
|
-
task.status = 'in_progress';
|
|
72
|
-
task.startedAt = new Date().toISOString();
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (task.
|
|
191
|
-
console.log(
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { GitHubClient } from '../core/GitHubClient.js';
|
|
5
|
+
import { GitHubConfig } from '../core/GitHubConfig.js';
|
|
6
|
+
import { TaskIssueMapper } from '../core/TaskIssueMapper.js';
|
|
7
|
+
|
|
8
|
+
export class TaskCommand {
|
|
9
|
+
static async handle(action, taskId) {
|
|
10
|
+
switch (action) {
|
|
11
|
+
case 'start':
|
|
12
|
+
return this.startTask(taskId);
|
|
13
|
+
case 'next':
|
|
14
|
+
return this.startNext();
|
|
15
|
+
case 'status':
|
|
16
|
+
return this.showStatus();
|
|
17
|
+
default:
|
|
18
|
+
console.log(`\n❌ Unknown action: ${action}`);
|
|
19
|
+
console.log(' Valid actions: start, next, status\n');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static async startTask(taskId) {
|
|
25
|
+
if (!taskId) {
|
|
26
|
+
console.log('\n❌ Task ID required for "start" action');
|
|
27
|
+
console.log(' Usage: agentic15 task start TASK-001\n');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Verify git remote is configured
|
|
32
|
+
this.validateGitRemote();
|
|
33
|
+
|
|
34
|
+
// Load task tracker
|
|
35
|
+
const tracker = this.loadTracker();
|
|
36
|
+
const task = tracker.taskFiles.find(t => t.id === taskId);
|
|
37
|
+
|
|
38
|
+
if (!task) {
|
|
39
|
+
console.log(`\n❌ Task not found: ${taskId}\n`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (task.status === 'completed') {
|
|
44
|
+
console.log(`\n⚠️ Task ${taskId} is already completed\n`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check if another task is in progress
|
|
49
|
+
const inProgress = tracker.taskFiles.find(t => t.status === 'in_progress');
|
|
50
|
+
if (inProgress && inProgress.id !== taskId) {
|
|
51
|
+
console.log(`\n⚠️ Task ${inProgress.id} is already in progress`);
|
|
52
|
+
console.log(` Complete it first with: agentic15 commit\n`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Create feature branch
|
|
57
|
+
const branchName = `feature/${taskId.toLowerCase()}`;
|
|
58
|
+
try {
|
|
59
|
+
execSync(`git checkout -b ${branchName}`, { stdio: 'inherit' });
|
|
60
|
+
} catch (error) {
|
|
61
|
+
// Branch might already exist
|
|
62
|
+
try {
|
|
63
|
+
execSync(`git checkout ${branchName}`, { stdio: 'inherit' });
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.log(`\n❌ Failed to create/checkout branch: ${branchName}\n`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Update task status
|
|
71
|
+
task.status = 'in_progress';
|
|
72
|
+
task.startedAt = new Date().toISOString();
|
|
73
|
+
|
|
74
|
+
// Update tracker metadata
|
|
75
|
+
tracker.activeTask = task.id;
|
|
76
|
+
this.updateStatistics(tracker);
|
|
77
|
+
this.saveTracker(tracker);
|
|
78
|
+
|
|
79
|
+
// Create GitHub issue if enabled
|
|
80
|
+
let githubIssue = null;
|
|
81
|
+
const config = new GitHubConfig(process.cwd());
|
|
82
|
+
if (config.isAutoCreateEnabled()) {
|
|
83
|
+
githubIssue = await this.createGitHubIssue(task, config);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Display task details
|
|
87
|
+
this.displayTaskDetails(task, githubIssue, tracker);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static async startNext() {
|
|
91
|
+
const tracker = this.loadTracker();
|
|
92
|
+
|
|
93
|
+
// Find next pending task
|
|
94
|
+
const nextTask = tracker.taskFiles.find(t => t.status === 'pending');
|
|
95
|
+
|
|
96
|
+
if (!nextTask) {
|
|
97
|
+
console.log('\n✅ No more pending tasks!\n');
|
|
98
|
+
const completed = tracker.taskFiles.filter(t => t.status === 'completed').length;
|
|
99
|
+
const total = tracker.taskFiles.length;
|
|
100
|
+
console.log(` Progress: ${completed}/${total} tasks completed\n`);
|
|
101
|
+
process.exit(0);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log(`\n▶️ Auto-starting next task: ${nextTask.id}\n`);
|
|
105
|
+
return this.startTask(nextTask.id);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
static showStatus() {
|
|
109
|
+
const tracker = this.loadTracker();
|
|
110
|
+
|
|
111
|
+
const inProgress = tracker.taskFiles.find(t => t.status === 'in_progress');
|
|
112
|
+
const completed = tracker.taskFiles.filter(t => t.status === 'completed').length;
|
|
113
|
+
const pending = tracker.taskFiles.filter(t => t.status === 'pending').length;
|
|
114
|
+
const total = tracker.taskFiles.length;
|
|
115
|
+
|
|
116
|
+
console.log('\n📊 Task Status\n');
|
|
117
|
+
console.log(` Plan: ${tracker.planId}`);
|
|
118
|
+
console.log(` Progress: ${completed}/${total} completed (${pending} pending)\n`);
|
|
119
|
+
|
|
120
|
+
if (inProgress) {
|
|
121
|
+
console.log(` 🔄 Currently working on: ${inProgress.id}`);
|
|
122
|
+
console.log(` 📌 ${inProgress.title}`);
|
|
123
|
+
|
|
124
|
+
// Show changed files
|
|
125
|
+
try {
|
|
126
|
+
const diff = execSync('git diff --name-only', { encoding: 'utf-8' });
|
|
127
|
+
if (diff.trim()) {
|
|
128
|
+
console.log(`\n 📝 Changed files:`);
|
|
129
|
+
diff.trim().split('\n').forEach(file => {
|
|
130
|
+
console.log(` - ${file}`);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
} catch (e) {
|
|
134
|
+
// Ignore
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
console.log(' ℹ️ No task currently in progress');
|
|
138
|
+
console.log(` Run: agentic15 task next\n`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
console.log('');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
static async createGitHubIssue(task, config) {
|
|
145
|
+
try {
|
|
146
|
+
const client = new GitHubClient(
|
|
147
|
+
config.getToken(),
|
|
148
|
+
config.getRepoInfo().owner,
|
|
149
|
+
config.getRepoInfo().repo
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
if (!client.isConfigured()) {
|
|
153
|
+
console.log('\n⚠️ GitHub not configured. Skipping issue creation.');
|
|
154
|
+
console.log(' Run: agentic15 auth setup\n');
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Load full task details
|
|
159
|
+
const taskPath = this.getTaskPath(task.id);
|
|
160
|
+
const taskData = JSON.parse(readFileSync(taskPath, 'utf-8'));
|
|
161
|
+
|
|
162
|
+
const title = TaskIssueMapper.taskToIssueTitle(taskData);
|
|
163
|
+
const body = TaskIssueMapper.taskToIssueBody(taskData);
|
|
164
|
+
const labels = TaskIssueMapper.taskStatusToLabels(taskData.status || 'pending', taskData.phase);
|
|
165
|
+
const issueNumber = await client.createIssue(title, body, labels);
|
|
166
|
+
|
|
167
|
+
if (issueNumber) {
|
|
168
|
+
// Save issue number to task
|
|
169
|
+
taskData.githubIssue = issueNumber;
|
|
170
|
+
writeFileSync(taskPath, JSON.stringify(taskData, null, 2));
|
|
171
|
+
|
|
172
|
+
console.log(`\n✓ Created GitHub issue #${issueNumber}`);
|
|
173
|
+
const repoInfo = config.getRepoInfo();
|
|
174
|
+
console.log(` https://github.com/${repoInfo.owner}/${repoInfo.repo}/issues/${issueNumber}\n`);
|
|
175
|
+
|
|
176
|
+
return issueNumber;
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.log(`\n⚠️ Failed to create GitHub issue: ${error.message}\n`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
static displayTaskDetails(task, githubIssue, tracker) {
|
|
186
|
+
console.log(`\n✅ Started task: ${task.id}`);
|
|
187
|
+
console.log(`📋 Plan: ${tracker.planId}\n`);
|
|
188
|
+
console.log(`📌 ${task.title}`);
|
|
189
|
+
|
|
190
|
+
if (task.description) {
|
|
191
|
+
console.log(`📝 ${task.description}\n`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (task.phase) {
|
|
195
|
+
console.log(`🔧 Phase: ${task.phase}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (githubIssue) {
|
|
199
|
+
const config = new GitHubConfig(process.cwd());
|
|
200
|
+
const repoInfo = config.getRepoInfo();
|
|
201
|
+
console.log(`🔗 GitHub Issue: https://github.com/${repoInfo.owner}/${repoInfo.repo}/issues/${githubIssue}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Load full task for completion criteria
|
|
205
|
+
try {
|
|
206
|
+
const taskPath = this.getTaskPath(task.id);
|
|
207
|
+
const taskData = JSON.parse(readFileSync(taskPath, 'utf-8'));
|
|
208
|
+
|
|
209
|
+
if (taskData.completionCriteria && taskData.completionCriteria.length > 0) {
|
|
210
|
+
console.log(`\n✓ Completion criteria:`);
|
|
211
|
+
taskData.completionCriteria.forEach((criteria, idx) => {
|
|
212
|
+
console.log(` ${idx + 1}. ${criteria}`);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
} catch (e) {
|
|
216
|
+
// Ignore
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
console.log(`\n💡 Next steps:`);
|
|
220
|
+
console.log(` 1. Tell Claude: "Write code for ${task.id}"`);
|
|
221
|
+
console.log(` 2. When done: agentic15 commit\n`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
static loadTracker() {
|
|
225
|
+
const activePlanPath = join(process.cwd(), '.claude', 'ACTIVE-PLAN');
|
|
226
|
+
|
|
227
|
+
if (!existsSync(activePlanPath)) {
|
|
228
|
+
console.log('\n❌ No active plan found');
|
|
229
|
+
console.log(' Run: agentic15 plan "project description"\n');
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const planId = readFileSync(activePlanPath, 'utf-8').trim();
|
|
234
|
+
const trackerPath = join(process.cwd(), '.claude', 'plans', planId, 'TASK-TRACKER.json');
|
|
235
|
+
|
|
236
|
+
if (!existsSync(trackerPath)) {
|
|
237
|
+
console.log('\n❌ Task tracker not found');
|
|
238
|
+
console.log(' Run: agentic15 plan\n');
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return JSON.parse(readFileSync(trackerPath, 'utf-8'));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
static saveTracker(tracker) {
|
|
246
|
+
const activePlanPath = join(process.cwd(), '.claude', 'ACTIVE-PLAN');
|
|
247
|
+
const planId = readFileSync(activePlanPath, 'utf-8').trim();
|
|
248
|
+
const trackerPath = join(process.cwd(), '.claude', 'plans', planId, 'TASK-TRACKER.json');
|
|
249
|
+
|
|
250
|
+
writeFileSync(trackerPath, JSON.stringify(tracker, null, 2));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
static updateStatistics(tracker) {
|
|
254
|
+
const completed = tracker.taskFiles.filter(t => t.status === 'completed').length;
|
|
255
|
+
const inProgress = tracker.taskFiles.filter(t => t.status === 'in_progress').length;
|
|
256
|
+
const pending = tracker.taskFiles.filter(t => t.status === 'pending').length;
|
|
257
|
+
const totalTasks = tracker.taskFiles.length;
|
|
258
|
+
|
|
259
|
+
tracker.statistics = {
|
|
260
|
+
totalTasks,
|
|
261
|
+
completed,
|
|
262
|
+
inProgress,
|
|
263
|
+
pending
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
static getTaskPath(taskId) {
|
|
268
|
+
const activePlanPath = join(process.cwd(), '.claude', 'ACTIVE-PLAN');
|
|
269
|
+
const planId = readFileSync(activePlanPath, 'utf-8').trim();
|
|
270
|
+
return join(process.cwd(), '.claude', 'plans', planId, 'tasks', `${taskId}.json`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
static validateGitRemote() {
|
|
274
|
+
try {
|
|
275
|
+
// Check if git remote origin exists
|
|
276
|
+
const remote = execSync('git remote get-url origin', { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
277
|
+
|
|
278
|
+
if (!remote || remote.length === 0) {
|
|
279
|
+
throw new Error('No remote URL');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Validate it's a GitHub URL
|
|
283
|
+
if (!remote.includes('github.com')) {
|
|
284
|
+
console.log('\n⚠️ Warning: Remote is not a GitHub repository');
|
|
285
|
+
console.log(` Remote URL: ${remote}`);
|
|
286
|
+
console.log(' GitHub integration features may not work.\n');
|
|
287
|
+
}
|
|
288
|
+
} catch (error) {
|
|
289
|
+
console.log('\n❌ Git remote "origin" is not configured');
|
|
290
|
+
console.log('\n Before starting tasks, you must link your project to a GitHub repository:');
|
|
291
|
+
console.log('\n 1. Create a GitHub repository:');
|
|
292
|
+
console.log(' gh repo create OWNER/REPO --public (or --private)');
|
|
293
|
+
console.log('\n 2. Link it to your local project:');
|
|
294
|
+
console.log(' git remote add origin https://github.com/OWNER/REPO.git');
|
|
295
|
+
console.log('\n 3. Push your initial code:');
|
|
296
|
+
console.log(' git branch -M main');
|
|
297
|
+
console.log(' git add .');
|
|
298
|
+
console.log(' git commit -m "Initial commit"');
|
|
299
|
+
console.log(' git push -u origin main');
|
|
300
|
+
console.log('\n 4. Then start your task:');
|
|
301
|
+
console.log(' npx agentic15 task next\n');
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
@@ -34,16 +34,12 @@
|
|
|
34
34
|
"Edit(./.claude/hooks/**)",
|
|
35
35
|
"Write(./.claude/hooks/**)",
|
|
36
36
|
"Edit(./.claude/TASK-TRACKER.json)",
|
|
37
|
-
"Read(./.claude/POST-INSTALL.md)",
|
|
38
|
-
"Read(./docs/**)",
|
|
39
37
|
"Bash(agentic15:*)",
|
|
40
38
|
"Bash(npx agentic15:*)",
|
|
41
39
|
"Bash(git:*)",
|
|
42
40
|
"Bash(gh:*)",
|
|
43
41
|
"Bash(npm run task:*)",
|
|
44
42
|
"Bash(npm run plan:*)",
|
|
45
|
-
"Bash(curl:*)",
|
|
46
|
-
"Bash(wget:*)",
|
|
47
43
|
"Bash(rm -rf:*)",
|
|
48
44
|
"Bash(sudo:*)",
|
|
49
45
|
"Bash(npm install:*)",
|
|
@@ -58,9 +54,7 @@
|
|
|
58
54
|
"Bash(node ./scripts/**)",
|
|
59
55
|
"Bash(node ./Agent/db/**)",
|
|
60
56
|
"Bash(bash ./scripts/**)",
|
|
61
|
-
"Bash(sh ./scripts/**)"
|
|
62
|
-
"WebFetch",
|
|
63
|
-
"WebSearch"
|
|
57
|
+
"Bash(sh ./scripts/**)"
|
|
64
58
|
]
|
|
65
59
|
},
|
|
66
60
|
"sandbox": {
|