@tmddev/tmd 0.1.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/LICENSE +201 -0
- package/README.md +424 -0
- package/bin/tmd.js +3 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +92 -0
- package/dist/commands/act.d.ts +3 -0
- package/dist/commands/act.js +210 -0
- package/dist/commands/check.d.ts +3 -0
- package/dist/commands/check.js +183 -0
- package/dist/commands/do.d.ts +3 -0
- package/dist/commands/do.js +310 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.js +56 -0
- package/dist/commands/plan.d.ts +3 -0
- package/dist/commands/plan.js +89 -0
- package/dist/commands/show.d.ts +2 -0
- package/dist/commands/show.js +69 -0
- package/dist/commands/skills.d.ts +3 -0
- package/dist/commands/skills.js +243 -0
- package/dist/types.d.ts +79 -0
- package/dist/types.js +5 -0
- package/dist/utils/act-processing.d.ts +64 -0
- package/dist/utils/act-processing.js +222 -0
- package/dist/utils/analysis.d.ts +34 -0
- package/dist/utils/analysis.js +159 -0
- package/dist/utils/comparison.d.ts +34 -0
- package/dist/utils/comparison.js +217 -0
- package/dist/utils/language-validator.d.ts +11 -0
- package/dist/utils/language-validator.js +39 -0
- package/dist/utils/openspec.d.ts +5 -0
- package/dist/utils/openspec.js +91 -0
- package/dist/utils/paths.d.ts +10 -0
- package/dist/utils/paths.js +33 -0
- package/dist/utils/skills.d.ts +3 -0
- package/dist/utils/skills.js +248 -0
- package/dist/utils/skillssh.d.ts +12 -0
- package/dist/utils/skillssh.js +135 -0
- package/dist/utils/standardization.d.ts +26 -0
- package/dist/utils/standardization.js +106 -0
- package/dist/utils/task-chain.d.ts +35 -0
- package/dist/utils/task-chain.js +146 -0
- package/dist/utils/task-id.d.ts +5 -0
- package/dist/utils/task-id.js +15 -0
- package/dist/utils/task-status.d.ts +6 -0
- package/dist/utils/task-status.js +61 -0
- package/dist/utils/task-validator.d.ts +42 -0
- package/dist/utils/task-validator.js +178 -0
- package/dist/utils/tasks.d.ts +27 -0
- package/dist/utils/tasks.js +125 -0
- package/dist/utils/templates.d.ts +12 -0
- package/dist/utils/templates.js +143 -0
- package/package.json +84 -0
- package/scripts/postinstall.js +92 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { existsSync, appendFileSync, readFileSync } from 'fs';
|
|
2
|
+
import { ensureDir, getDoDir, getPlanDir } from '../utils/paths.js';
|
|
3
|
+
import { executionTemplate, renderTemplate } from '../utils/templates.js';
|
|
4
|
+
import { updateTaskStatus as updatePhaseStatus } from '../utils/task-status.js';
|
|
5
|
+
import { executeSkill } from '../utils/skills.js';
|
|
6
|
+
import { extractTasks, updateTaskStatus, detectSkillReference } from '../utils/tasks.js';
|
|
7
|
+
import { executeConcurrently } from '../utils/task-chain.js';
|
|
8
|
+
import { writeFileSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
function displayTasks(tasks, showProgress = true) {
|
|
12
|
+
if (tasks.length === 0) {
|
|
13
|
+
console.log(chalk.yellow(' No tasks found in plan.md'));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const completed = tasks.filter(t => t.completed).length;
|
|
17
|
+
const total = tasks.length;
|
|
18
|
+
const percentage = total > 0 ? Math.round((completed / total) * 100) : 0;
|
|
19
|
+
if (showProgress) {
|
|
20
|
+
console.log(chalk.blue(`\n Task Progress: ${String(completed)}/${String(total)} (${String(percentage)}%)`));
|
|
21
|
+
}
|
|
22
|
+
console.log(chalk.blue('\n Tasks:'));
|
|
23
|
+
for (const task of tasks) {
|
|
24
|
+
const checkbox = task.completed ? chalk.green('[x]') : chalk.gray('[ ]');
|
|
25
|
+
const index = chalk.dim(`[${String(task.index)}]`);
|
|
26
|
+
console.log(` ${checkbox} ${index} ${task.content}`);
|
|
27
|
+
// Show dependencies if present
|
|
28
|
+
if (task.dependencies && task.dependencies.length > 0) {
|
|
29
|
+
const deps = task.dependencies.map(d => `task-${String(d)}`).join(', ');
|
|
30
|
+
console.log(chalk.gray(` ā depends: ${deps}`));
|
|
31
|
+
}
|
|
32
|
+
// Show skill reference if present
|
|
33
|
+
const skillRef = detectSkillReference(task.content);
|
|
34
|
+
if (skillRef) {
|
|
35
|
+
console.log(chalk.gray(` ā skill: ${skillRef}`));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function recordTaskCompletion(doDir, taskIndex, taskContent) {
|
|
40
|
+
const executionPath = join(doDir, 'execution.md');
|
|
41
|
+
const timestamp = new Date().toISOString();
|
|
42
|
+
let executionContent = '';
|
|
43
|
+
if (existsSync(executionPath)) {
|
|
44
|
+
executionContent = readFileSync(executionPath, 'utf-8');
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
executionContent = renderTemplate(executionTemplate, { taskId: '' });
|
|
48
|
+
}
|
|
49
|
+
// Append task completion record
|
|
50
|
+
const completionRecord = `\n## Task Completions\n\n- [${timestamp}] Task ${String(taskIndex)}: ${taskContent}\n`;
|
|
51
|
+
// Check if "Task Completions" section exists
|
|
52
|
+
if (executionContent.includes('## Task Completions')) {
|
|
53
|
+
// Append to existing section
|
|
54
|
+
executionContent = executionContent.replace(/(## Task Completions\n\n)/, `$1- [${timestamp}] Task ${String(taskIndex)}: ${taskContent}\n`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Add new section before "## Issues Encountered" or at the end
|
|
58
|
+
if (executionContent.includes('## Issues Encountered')) {
|
|
59
|
+
executionContent = executionContent.replace(/(## Issues Encountered)/, `## Task Completions\n\n- [${timestamp}] Task ${String(taskIndex)}: ${taskContent}\n\n$1`);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
executionContent += completionRecord;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
writeFileSync(executionPath, executionContent, 'utf-8');
|
|
66
|
+
}
|
|
67
|
+
function recordConcurrentExecution(doDir, results) {
|
|
68
|
+
const executionPath = join(doDir, 'execution.md');
|
|
69
|
+
const timestamp = new Date().toISOString();
|
|
70
|
+
let executionContent = '';
|
|
71
|
+
if (existsSync(executionPath)) {
|
|
72
|
+
executionContent = readFileSync(executionPath, 'utf-8');
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
executionContent = renderTemplate(executionTemplate, { taskId: '' });
|
|
76
|
+
}
|
|
77
|
+
const successCount = results.filter(r => r.success).length;
|
|
78
|
+
const failCount = results.length - successCount;
|
|
79
|
+
const summary = `\n## Concurrent Execution Results\n\n- Execution started: ${timestamp}\n- Total tasks: ${String(results.length)}\n- Successful: ${String(successCount)}\n- Failed: ${String(failCount)}\n\n### Task Results\n\n`;
|
|
80
|
+
let resultsList = '';
|
|
81
|
+
for (const result of results) {
|
|
82
|
+
const status = result.success ? 'SUCCESS' : 'FAILED';
|
|
83
|
+
const errorInfo = result.error ? ` (${result.error})` : '';
|
|
84
|
+
resultsList += `- Task ${String(result.taskIndex)}: ${status} (${String(result.duration)}ms)${errorInfo}\n`;
|
|
85
|
+
}
|
|
86
|
+
const resultsSection = summary + resultsList;
|
|
87
|
+
// Check if "Concurrent Execution Results" section exists
|
|
88
|
+
if (executionContent.includes('## Concurrent Execution Results')) {
|
|
89
|
+
// Replace existing section
|
|
90
|
+
executionContent = executionContent.replace(/## Concurrent Execution Results[\s\S]*?(?=\n## |$)/, resultsSection.trim());
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// Add new section before "## Issues Encountered" or at the end
|
|
94
|
+
if (executionContent.includes('## Issues Encountered')) {
|
|
95
|
+
executionContent = executionContent.replace(/(## Issues Encountered)/, `${resultsSection}\n$1`);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
executionContent += resultsSection;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
writeFileSync(executionPath, executionContent, 'utf-8');
|
|
102
|
+
}
|
|
103
|
+
function recordSkillExecution(doDir, skillName, success, error) {
|
|
104
|
+
const executionPath = join(doDir, 'execution.md');
|
|
105
|
+
const timestamp = new Date().toISOString();
|
|
106
|
+
let executionContent = '';
|
|
107
|
+
if (existsSync(executionPath)) {
|
|
108
|
+
executionContent = readFileSync(executionPath, 'utf-8');
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
executionContent = renderTemplate(executionTemplate, { taskId: '' });
|
|
112
|
+
}
|
|
113
|
+
const status = success ? 'SUCCESS' : 'FAILED';
|
|
114
|
+
const skillRecord = `- [${timestamp}] Skill: ${skillName} - ${status}${error ? ` (${error})` : ''}\n`;
|
|
115
|
+
// Check if "Skill Executions" section exists
|
|
116
|
+
if (executionContent.includes('## Skill Executions')) {
|
|
117
|
+
// Append to existing section
|
|
118
|
+
executionContent = executionContent.replace(/(## Skill Executions\n\n)/, `$1${skillRecord}`);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// Add new section before "## Issues Encountered" or at the end
|
|
122
|
+
const skillSection = `\n## Skill Executions\n\n${skillRecord}`;
|
|
123
|
+
if (executionContent.includes('## Issues Encountered')) {
|
|
124
|
+
executionContent = executionContent.replace(/(## Issues Encountered)/, `${skillSection}\n$1`);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
executionContent += skillSection;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
writeFileSync(executionPath, executionContent, 'utf-8');
|
|
131
|
+
}
|
|
132
|
+
export async function doCommand(taskId, options) {
|
|
133
|
+
// Validate plan exists
|
|
134
|
+
const planPath = join(getPlanDir(taskId), 'plan.md');
|
|
135
|
+
if (!existsSync(planPath)) {
|
|
136
|
+
console.error(chalk.red(`ā Plan not found for task: ${taskId}`));
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
// Extract tasks from plan
|
|
140
|
+
const tasks = extractTasks(planPath);
|
|
141
|
+
// Handle --list option: show task status without modifying
|
|
142
|
+
if (options.list) {
|
|
143
|
+
console.log(chalk.blue(`\nTask Status for: ${taskId}`));
|
|
144
|
+
displayTasks(tasks, true);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// Handle --task option: mark specific task as complete
|
|
148
|
+
if (options.task !== undefined) {
|
|
149
|
+
const taskIndex = options.task;
|
|
150
|
+
const task = tasks.find(t => t.index === taskIndex);
|
|
151
|
+
if (!task) {
|
|
152
|
+
console.error(chalk.red(`ā Task at index ${String(taskIndex)} not found`));
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
if (task.completed) {
|
|
156
|
+
console.log(chalk.yellow(` Task ${String(taskIndex)} is already completed`));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const doDir = getDoDir(taskId);
|
|
160
|
+
ensureDir(doDir);
|
|
161
|
+
// Ensure execution.md exists
|
|
162
|
+
const executionPath = join(doDir, 'execution.md');
|
|
163
|
+
if (!existsSync(executionPath)) {
|
|
164
|
+
const executionContent = renderTemplate(executionTemplate, { taskId });
|
|
165
|
+
writeFileSync(executionPath, executionContent);
|
|
166
|
+
}
|
|
167
|
+
// If --skill option is provided, execute skill first and only mark complete if successful
|
|
168
|
+
// Otherwise, mark task as complete immediately
|
|
169
|
+
const hasSkillOption = !!options.skill;
|
|
170
|
+
const skillRef = detectSkillReference(task.content);
|
|
171
|
+
const shouldExecuteSkill = hasSkillOption || skillRef;
|
|
172
|
+
if (shouldExecuteSkill) {
|
|
173
|
+
try {
|
|
174
|
+
// Execute skill from --skill option if provided
|
|
175
|
+
if (options.skill) {
|
|
176
|
+
await executeSkill(options.skill, taskId, false); // Sequential for single task execution
|
|
177
|
+
recordSkillExecution(doDir, options.skill, true);
|
|
178
|
+
}
|
|
179
|
+
// Check for skill reference and execute if present
|
|
180
|
+
if (skillRef) {
|
|
181
|
+
console.log(chalk.blue(` Detected skill reference: ${skillRef}`));
|
|
182
|
+
await executeSkill(skillRef, taskId, false); // Sequential for single task execution
|
|
183
|
+
recordSkillExecution(doDir, skillRef, true);
|
|
184
|
+
}
|
|
185
|
+
// Only mark task as complete if skill execution succeeded
|
|
186
|
+
updateTaskStatus(planPath, taskIndex, true);
|
|
187
|
+
recordTaskCompletion(doDir, taskIndex, task.content);
|
|
188
|
+
console.log(chalk.green(`ā Task ${String(taskIndex)} marked as complete`));
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
192
|
+
// Record failed skill execution
|
|
193
|
+
if (options.skill) {
|
|
194
|
+
recordSkillExecution(doDir, options.skill, false, errorMessage);
|
|
195
|
+
}
|
|
196
|
+
if (skillRef) {
|
|
197
|
+
recordSkillExecution(doDir, skillRef, false, errorMessage);
|
|
198
|
+
}
|
|
199
|
+
console.error(chalk.red(`ā Skill execution failed. Task ${String(taskIndex)} not marked as complete.`));
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
// No skill execution required, mark task as complete immediately
|
|
205
|
+
updateTaskStatus(planPath, taskIndex, true);
|
|
206
|
+
recordTaskCompletion(doDir, taskIndex, task.content);
|
|
207
|
+
console.log(chalk.green(`ā Task ${String(taskIndex)} marked as complete`));
|
|
208
|
+
}
|
|
209
|
+
// Show updated task list
|
|
210
|
+
const updatedTasks = extractTasks(planPath);
|
|
211
|
+
displayTasks(updatedTasks, true);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
// Normal execution: start do phase
|
|
215
|
+
const doDir = getDoDir(taskId);
|
|
216
|
+
ensureDir(doDir);
|
|
217
|
+
// Create execution.md if it doesn't exist
|
|
218
|
+
const executionPath = join(doDir, 'execution.md');
|
|
219
|
+
if (!existsSync(executionPath)) {
|
|
220
|
+
const executionContent = renderTemplate(executionTemplate, { taskId });
|
|
221
|
+
writeFileSync(executionPath, executionContent);
|
|
222
|
+
}
|
|
223
|
+
// Create data.md if it doesn't exist
|
|
224
|
+
const dataPath = join(doDir, 'data.md');
|
|
225
|
+
if (!existsSync(dataPath)) {
|
|
226
|
+
writeFileSync(dataPath, '# Data Collection\n\n');
|
|
227
|
+
}
|
|
228
|
+
// Update task status
|
|
229
|
+
updatePhaseStatus(taskId, 'doing');
|
|
230
|
+
console.log(chalk.green(`ā Started execution phase for task: ${taskId}`));
|
|
231
|
+
console.log(chalk.blue(` Directory: ${doDir}`));
|
|
232
|
+
// Display tasks with progress
|
|
233
|
+
displayTasks(tasks, true);
|
|
234
|
+
// Concurrent execution if enabled
|
|
235
|
+
if (options.parallel) {
|
|
236
|
+
const maxConcurrency = options.maxConcurrency ?? Infinity;
|
|
237
|
+
const incompleteTasks = tasks.filter(t => !t.completed);
|
|
238
|
+
if (incompleteTasks.length === 0) {
|
|
239
|
+
console.log(chalk.yellow(' All tasks already completed'));
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
console.log(chalk.blue(`\n Executing ${String(incompleteTasks.length)} tasks concurrently (max: ${maxConcurrency === Infinity ? 'unlimited' : String(maxConcurrency)})...`));
|
|
243
|
+
try {
|
|
244
|
+
const results = await executeConcurrently(incompleteTasks, async (task) => {
|
|
245
|
+
const startTime = Date.now();
|
|
246
|
+
try {
|
|
247
|
+
// Execute task (mark as complete)
|
|
248
|
+
updateTaskStatus(planPath, task.index, true);
|
|
249
|
+
recordTaskCompletion(doDir, task.index, task.content);
|
|
250
|
+
// Execute skill if referenced (with parallel support)
|
|
251
|
+
const skillRef = detectSkillReference(task.content);
|
|
252
|
+
if (skillRef) {
|
|
253
|
+
await executeSkill(skillRef, taskId, options.parallel, options.maxConcurrency);
|
|
254
|
+
recordSkillExecution(doDir, skillRef, true);
|
|
255
|
+
}
|
|
256
|
+
const duration = Date.now() - startTime;
|
|
257
|
+
console.log(chalk.green(` ā Task ${String(task.index)} completed (${String(duration)}ms)`));
|
|
258
|
+
return {
|
|
259
|
+
taskIndex: task.index,
|
|
260
|
+
success: true,
|
|
261
|
+
duration
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
const duration = Date.now() - startTime;
|
|
266
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
267
|
+
console.error(chalk.red(` ā Task ${String(task.index)} failed: ${errorMessage}`));
|
|
268
|
+
return {
|
|
269
|
+
taskIndex: task.index,
|
|
270
|
+
success: false,
|
|
271
|
+
error: errorMessage,
|
|
272
|
+
duration
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}, maxConcurrency);
|
|
276
|
+
// Record concurrent execution results
|
|
277
|
+
recordConcurrentExecution(doDir, results);
|
|
278
|
+
// Display summary
|
|
279
|
+
const successCount = results.filter(r => r.success).length;
|
|
280
|
+
const failCount = results.length - successCount;
|
|
281
|
+
console.log(chalk.blue(`\n Execution Summary:`));
|
|
282
|
+
console.log(chalk.green(` Successful: ${String(successCount)}`));
|
|
283
|
+
if (failCount > 0) {
|
|
284
|
+
console.log(chalk.red(` Failed: ${String(failCount)}`));
|
|
285
|
+
}
|
|
286
|
+
// Show updated task list
|
|
287
|
+
const updatedTasks = extractTasks(planPath);
|
|
288
|
+
displayTasks(updatedTasks, true);
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
292
|
+
console.error(chalk.red(`ā Concurrent execution failed: ${errorMessage}`));
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// Data collection
|
|
298
|
+
if (options.data) {
|
|
299
|
+
const [key, value] = options.data.split('=');
|
|
300
|
+
const safeKey = key ?? '';
|
|
301
|
+
const safeValue = value ?? '';
|
|
302
|
+
appendFileSync(dataPath, `- ${safeKey}: ${safeValue}\n`);
|
|
303
|
+
console.log(chalk.green(` Recorded data: ${safeKey}=${safeValue}`));
|
|
304
|
+
}
|
|
305
|
+
// Skill execution
|
|
306
|
+
if (options.skill) {
|
|
307
|
+
await executeSkill(options.skill, taskId, options.parallel, options.maxConcurrency);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
//# sourceMappingURL=do.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { readdirSync, existsSync } from 'fs';
|
|
2
|
+
import { getTmdDir } from '../utils/paths.js';
|
|
3
|
+
import { getTaskStatus } from '../utils/task-status.js';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
export function listCommand(options) {
|
|
7
|
+
const tmdDir = getTmdDir();
|
|
8
|
+
if (!existsSync(tmdDir)) {
|
|
9
|
+
console.log(chalk.yellow('No tasks found. Create one with: tmd plan "description"'));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const planDir = join(tmdDir, 'plan');
|
|
13
|
+
if (!existsSync(planDir)) {
|
|
14
|
+
console.log(chalk.yellow('No tasks found. Create one with: tmd plan "description"'));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
let tasks = readdirSync(planDir, { withFileTypes: true })
|
|
18
|
+
.filter(dirent => dirent.isDirectory())
|
|
19
|
+
.map(dirent => dirent.name);
|
|
20
|
+
if (tasks.length === 0) {
|
|
21
|
+
console.log(chalk.yellow('No tasks found. Create one with: tmd plan "description"'));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// Filter by status if provided
|
|
25
|
+
if (options.status) {
|
|
26
|
+
tasks = tasks.filter(taskId => getTaskStatus(taskId) === options.status);
|
|
27
|
+
}
|
|
28
|
+
// Filter by phase if provided
|
|
29
|
+
if (options.phase) {
|
|
30
|
+
tasks = tasks.filter(taskId => {
|
|
31
|
+
const status = getTaskStatus(taskId);
|
|
32
|
+
return status === options.phase ||
|
|
33
|
+
(options.phase === 'plan' && status === 'planning') ||
|
|
34
|
+
(options.phase === 'do' && status === 'doing') ||
|
|
35
|
+
(options.phase === 'check' && status === 'checking') ||
|
|
36
|
+
(options.phase === 'act' && status === 'acting');
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (tasks.length === 0) {
|
|
40
|
+
console.log(chalk.yellow('No tasks found matching the criteria'));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
console.log(chalk.bold('\nTasks:'));
|
|
44
|
+
console.log('ā'.repeat(60));
|
|
45
|
+
for (const taskId of tasks) {
|
|
46
|
+
const status = getTaskStatus(taskId);
|
|
47
|
+
const statusColor = status === 'completed' ? chalk.green :
|
|
48
|
+
status === 'acting' ? chalk.blue :
|
|
49
|
+
status === 'checking' ? chalk.yellow :
|
|
50
|
+
status === 'doing' ? chalk.cyan : chalk.gray;
|
|
51
|
+
console.log(` ${chalk.cyan(taskId)} - ${statusColor(status)}`);
|
|
52
|
+
}
|
|
53
|
+
console.log('ā'.repeat(60));
|
|
54
|
+
console.log(chalk.gray(`Total: ${String(tasks.length)} task(s)`));
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { ensureDir, getPlanDir } from '../utils/paths.js';
|
|
2
|
+
import { generateTaskId } from '../utils/task-id.js';
|
|
3
|
+
import { planTemplate, resourcesTemplate, renderTemplate } from '../utils/templates.js';
|
|
4
|
+
import { updateTaskStatus } from '../utils/task-status.js';
|
|
5
|
+
import { createOpenSpecChange } from '../utils/openspec.js';
|
|
6
|
+
import { createSkill } from '../utils/skills.js';
|
|
7
|
+
import { extractTasks } from '../utils/tasks.js';
|
|
8
|
+
import { validateTasks } from '../utils/task-validator.js';
|
|
9
|
+
import { writeFileSync, existsSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import chalk from 'chalk';
|
|
12
|
+
export function planCommand(description, options) {
|
|
13
|
+
const taskId = generateTaskId(description);
|
|
14
|
+
const planDir = getPlanDir(taskId);
|
|
15
|
+
ensureDir(planDir);
|
|
16
|
+
// Create plan.md
|
|
17
|
+
const planContent = renderTemplate(planTemplate, { description });
|
|
18
|
+
writeFileSync(join(planDir, 'plan.md'), planContent);
|
|
19
|
+
// Create resources.md
|
|
20
|
+
const resourcesContent = renderTemplate(resourcesTemplate, { description });
|
|
21
|
+
writeFileSync(join(planDir, 'resources.md'), resourcesContent);
|
|
22
|
+
// Initialize task status
|
|
23
|
+
updateTaskStatus(taskId, 'planning');
|
|
24
|
+
console.log(chalk.green(`ā Created plan for task: ${taskId}`));
|
|
25
|
+
console.log(chalk.blue(` Directory: ${planDir}`));
|
|
26
|
+
// OpenSpec integration
|
|
27
|
+
if (options.openspec) {
|
|
28
|
+
const changeId = createOpenSpecChange(description, taskId);
|
|
29
|
+
if (changeId) {
|
|
30
|
+
console.log(chalk.green(` Linked to OpenSpec change: ${changeId}`));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Skill creation
|
|
34
|
+
if (options.skill) {
|
|
35
|
+
const skillName = createSkill(description, taskId);
|
|
36
|
+
if (skillName) {
|
|
37
|
+
console.log(chalk.green(` Created skill: ${skillName}`));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Use existing skill
|
|
41
|
+
if (options.useSkill) {
|
|
42
|
+
console.log(chalk.blue(` Using skill: ${options.useSkill}`));
|
|
43
|
+
// TODO: Link skill to task
|
|
44
|
+
}
|
|
45
|
+
// Task validation
|
|
46
|
+
if (options.validateTasks) {
|
|
47
|
+
const planPath = join(planDir, 'plan.md');
|
|
48
|
+
if (existsSync(planPath)) {
|
|
49
|
+
const tasks = extractTasks(planPath);
|
|
50
|
+
if (tasks.length > 0) {
|
|
51
|
+
const taskDescriptions = tasks.map(t => t.content);
|
|
52
|
+
const validation = validateTasks(taskDescriptions);
|
|
53
|
+
console.log(chalk.blue('\nš Task Size Validation:'));
|
|
54
|
+
console.log(chalk.blue(` Total tasks: ${String(validation.summary.total)}`));
|
|
55
|
+
console.log(chalk.green(` Valid (pee-break sized): ${String(validation.summary.valid)}`));
|
|
56
|
+
if (validation.summary.invalid > 0) {
|
|
57
|
+
console.log(chalk.yellow(` ā ļø Tasks that may exceed 10 minutes: ${String(validation.summary.invalid)}`));
|
|
58
|
+
console.log(chalk.yellow(` Large tasks: ${String(validation.summary.large)}\n`));
|
|
59
|
+
validation.results.forEach((result, index) => {
|
|
60
|
+
if (!result.isValid) {
|
|
61
|
+
const task = tasks[index];
|
|
62
|
+
if (task) {
|
|
63
|
+
console.log(chalk.yellow(` Task ${String(task.index + 1)}: "${task.content}"`));
|
|
64
|
+
console.log(chalk.yellow(` Estimated: ${String(result.estimate.estimatedMinutes)} minutes (${result.estimate.category})`));
|
|
65
|
+
if (result.warning) {
|
|
66
|
+
console.log(chalk.yellow(` ā ļø ${result.warning}`));
|
|
67
|
+
}
|
|
68
|
+
if (result.suggestions && result.suggestions.length > 0) {
|
|
69
|
+
console.log(chalk.blue(` š” Suggestions:`));
|
|
70
|
+
result.suggestions.slice(0, 3).forEach(suggestion => {
|
|
71
|
+
console.log(chalk.blue(` - ${suggestion}`));
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
console.log('');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
console.log(chalk.green(' ā All tasks are pee-break sized (10 seconds to 10 minutes)\n'));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
console.log(chalk.blue(' No tasks found in plan.md yet'));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=plan.js.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { getPlanDir, getDoDir, getCheckDir, getActDir } from '../utils/paths.js';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
export async function showCommand(taskId) {
|
|
6
|
+
const planDir = getPlanDir(taskId);
|
|
7
|
+
const planPath = join(planDir, 'plan.md');
|
|
8
|
+
if (!existsSync(planPath)) {
|
|
9
|
+
console.error(chalk.red(`ā Task not found: ${taskId}`));
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
console.log(chalk.bold(`\nTask: ${chalk.cyan(taskId)}`));
|
|
13
|
+
console.log('ā'.repeat(60));
|
|
14
|
+
// Read plan
|
|
15
|
+
if (existsSync(planPath)) {
|
|
16
|
+
const planContent = readFileSync(planPath, 'utf-8');
|
|
17
|
+
const firstLine = planContent.split('\n')[0] ?? '';
|
|
18
|
+
console.log(chalk.bold('\nPlan:'));
|
|
19
|
+
console.log(` ${firstLine.replace('# Plan: ', '')}`);
|
|
20
|
+
}
|
|
21
|
+
// Check phase status
|
|
22
|
+
const phases = [];
|
|
23
|
+
if (existsSync(join(getPlanDir(taskId), 'plan.md'))) {
|
|
24
|
+
phases.push('Plan');
|
|
25
|
+
}
|
|
26
|
+
if (existsSync(join(getDoDir(taskId), 'execution.md'))) {
|
|
27
|
+
phases.push('Do');
|
|
28
|
+
}
|
|
29
|
+
if (existsSync(join(getCheckDir(taskId), 'evaluation.md'))) {
|
|
30
|
+
phases.push('Check');
|
|
31
|
+
}
|
|
32
|
+
if (existsSync(join(getActDir(taskId), 'improvement.md'))) {
|
|
33
|
+
phases.push('Act');
|
|
34
|
+
}
|
|
35
|
+
console.log(chalk.bold('\nCurrent Phase:'));
|
|
36
|
+
console.log(` ${phases[phases.length - 1] ?? 'Plan'}`);
|
|
37
|
+
console.log(chalk.bold('\nCompleted Phases:'));
|
|
38
|
+
phases.forEach(phase => {
|
|
39
|
+
console.log(` ${chalk.green('ā')} ${phase}`);
|
|
40
|
+
});
|
|
41
|
+
console.log(chalk.bold('\nDirectories:'));
|
|
42
|
+
console.log(` Plan: ${chalk.blue(getPlanDir(taskId))}`);
|
|
43
|
+
if (existsSync(getDoDir(taskId))) {
|
|
44
|
+
console.log(` Do: ${chalk.blue(getDoDir(taskId))}`);
|
|
45
|
+
}
|
|
46
|
+
if (existsSync(getCheckDir(taskId))) {
|
|
47
|
+
console.log(` Check: ${chalk.blue(getCheckDir(taskId))}`);
|
|
48
|
+
}
|
|
49
|
+
if (existsSync(getActDir(taskId))) {
|
|
50
|
+
console.log(` Act: ${chalk.blue(getActDir(taskId))}`);
|
|
51
|
+
}
|
|
52
|
+
// OpenSpec link
|
|
53
|
+
const { getTaskMetadata } = await import('../utils/task-status.js');
|
|
54
|
+
const metadata = getTaskMetadata(taskId);
|
|
55
|
+
console.log(chalk.bold('\nOpenSpec Change:'));
|
|
56
|
+
if (metadata.openspecChange) {
|
|
57
|
+
console.log(` ${chalk.cyan(metadata.openspecChange)}`);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
console.log(chalk.gray(' Not linked (use --openspec when creating plan)'));
|
|
61
|
+
}
|
|
62
|
+
// Task status
|
|
63
|
+
const { getTaskStatus } = await import('../utils/task-status.js');
|
|
64
|
+
const status = getTaskStatus(taskId);
|
|
65
|
+
console.log(chalk.bold('\nStatus:'));
|
|
66
|
+
console.log(` ${status}`);
|
|
67
|
+
console.log('ā'.repeat(60));
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=show.js.map
|