@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,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build dependency graph from tasks
|
|
3
|
+
* Returns a map of task index to array of dependency indices
|
|
4
|
+
*/
|
|
5
|
+
export function buildDependencyGraph(tasks) {
|
|
6
|
+
const graph = new Map();
|
|
7
|
+
for (const task of tasks) {
|
|
8
|
+
const deps = task.dependencies ?? [];
|
|
9
|
+
graph.set(task.index, deps);
|
|
10
|
+
}
|
|
11
|
+
return graph;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Detect cycles in dependency graph using DFS
|
|
15
|
+
*/
|
|
16
|
+
export function detectCycles(graph) {
|
|
17
|
+
const visited = new Set();
|
|
18
|
+
const recStack = new Set();
|
|
19
|
+
const cycle = [];
|
|
20
|
+
function dfs(node) {
|
|
21
|
+
if (recStack.has(node)) {
|
|
22
|
+
// Found cycle
|
|
23
|
+
cycle.push(node);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
if (visited.has(node)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
visited.add(node);
|
|
30
|
+
recStack.add(node);
|
|
31
|
+
const deps = graph.get(node) ?? [];
|
|
32
|
+
for (const dep of deps) {
|
|
33
|
+
if (dfs(dep)) {
|
|
34
|
+
if (cycle.length === 0 || cycle[0] !== node) {
|
|
35
|
+
cycle.push(node);
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
recStack.delete(node);
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
for (const node of graph.keys()) {
|
|
44
|
+
if (!visited.has(node)) {
|
|
45
|
+
if (dfs(node)) {
|
|
46
|
+
return cycle.reverse();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Topological sort to determine execution order
|
|
54
|
+
* Returns array of arrays, where each inner array contains tasks that can run concurrently
|
|
55
|
+
*/
|
|
56
|
+
export function topologicalSort(tasks, graph) {
|
|
57
|
+
const inDegree = new Map();
|
|
58
|
+
const levels = [];
|
|
59
|
+
// Initialize in-degree for all tasks
|
|
60
|
+
for (const task of tasks) {
|
|
61
|
+
inDegree.set(task.index, 0);
|
|
62
|
+
}
|
|
63
|
+
// Calculate in-degrees
|
|
64
|
+
for (const task of tasks) {
|
|
65
|
+
const deps = graph.get(task.index) ?? [];
|
|
66
|
+
for (const dep of deps) {
|
|
67
|
+
const current = inDegree.get(dep) ?? 0;
|
|
68
|
+
inDegree.set(dep, current + 1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Build execution levels
|
|
72
|
+
let currentLevel = [];
|
|
73
|
+
const remaining = new Set(tasks.map(t => t.index));
|
|
74
|
+
while (remaining.size > 0) {
|
|
75
|
+
// Find tasks with no dependencies (in-degree = 0)
|
|
76
|
+
for (const taskIndex of remaining) {
|
|
77
|
+
const deps = graph.get(taskIndex) ?? [];
|
|
78
|
+
const hasUnresolvedDeps = deps.some(dep => remaining.has(dep));
|
|
79
|
+
if (!hasUnresolvedDeps) {
|
|
80
|
+
currentLevel.push(taskIndex);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (currentLevel.length === 0) {
|
|
84
|
+
// Circular dependency detected (should be caught earlier, but handle gracefully)
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
levels.push([...currentLevel]);
|
|
88
|
+
// Remove completed tasks and update in-degrees
|
|
89
|
+
for (const taskIndex of currentLevel) {
|
|
90
|
+
remaining.delete(taskIndex);
|
|
91
|
+
const deps = graph.get(taskIndex) ?? [];
|
|
92
|
+
for (const dep of deps) {
|
|
93
|
+
const current = inDegree.get(dep) ?? 0;
|
|
94
|
+
inDegree.set(dep, Math.max(0, current - 1));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
currentLevel = [];
|
|
98
|
+
}
|
|
99
|
+
return levels;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Execute tasks concurrently with dependency management
|
|
103
|
+
* @param tasks Array of tasks to execute
|
|
104
|
+
* @param executeTask Function to execute a single task
|
|
105
|
+
* @param maxConcurrency Maximum number of concurrent executions
|
|
106
|
+
* @returns Array of execution results
|
|
107
|
+
*/
|
|
108
|
+
export async function executeConcurrently(tasks, executeTask, maxConcurrency = Infinity) {
|
|
109
|
+
const graph = buildDependencyGraph(tasks);
|
|
110
|
+
// Check for cycles
|
|
111
|
+
const cycle = detectCycles(graph);
|
|
112
|
+
if (cycle) {
|
|
113
|
+
throw new Error(`Circular dependency detected: ${cycle.map(String).join(' -> ')}`);
|
|
114
|
+
}
|
|
115
|
+
// Get execution order
|
|
116
|
+
const executionLevels = topologicalSort(tasks, graph);
|
|
117
|
+
const results = [];
|
|
118
|
+
const taskMap = new Map(tasks.map(t => [t.index, t]));
|
|
119
|
+
// Execute level by level
|
|
120
|
+
for (const level of executionLevels) {
|
|
121
|
+
const levelTasks = level.map(idx => taskMap.get(idx)).filter((task) => task !== undefined);
|
|
122
|
+
// Execute tasks in batches to respect concurrency limit
|
|
123
|
+
for (let i = 0; i < levelTasks.length; i += maxConcurrency) {
|
|
124
|
+
const batch = levelTasks.slice(i, i + maxConcurrency);
|
|
125
|
+
const batchPromises = batch.map(task => executeTask(task));
|
|
126
|
+
// Wait for all tasks in this batch to complete
|
|
127
|
+
const batchResults = await Promise.allSettled(batchPromises);
|
|
128
|
+
for (const result of batchResults) {
|
|
129
|
+
if (result.status === 'fulfilled') {
|
|
130
|
+
results.push(result.value);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// Handle rejected promises
|
|
134
|
+
results.push({
|
|
135
|
+
taskIndex: -1,
|
|
136
|
+
success: false,
|
|
137
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason ?? 'Unknown error'),
|
|
138
|
+
duration: 0
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return results;
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=task-chain.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate task ID in format: YYYYMMDD-description
|
|
3
|
+
*/
|
|
4
|
+
export function generateTaskId(description) {
|
|
5
|
+
const date = new Date();
|
|
6
|
+
const datePart = date.toISOString().split('T')[0] ?? '';
|
|
7
|
+
const dateStr = datePart.replace(/-/g, '');
|
|
8
|
+
const desc = description
|
|
9
|
+
.toLowerCase()
|
|
10
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
11
|
+
.replace(/^-+|-+$/g, '')
|
|
12
|
+
.substring(0, 50);
|
|
13
|
+
return `${dateStr}-${desc}`;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=task-id.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TaskMetadata } from '../types.js';
|
|
2
|
+
export type TaskStatus = 'planning' | 'doing' | 'checking' | 'acting' | 'completed';
|
|
3
|
+
export declare function getTaskStatus(taskId: string): TaskStatus;
|
|
4
|
+
export declare function updateTaskStatus(taskId: string, status: TaskStatus): void;
|
|
5
|
+
export declare function getTaskMetadata(taskId: string): TaskMetadata;
|
|
6
|
+
//# sourceMappingURL=task-status.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { getPlanDir, getDoDir, getCheckDir, getActDir, ensureDir } from './paths.js';
|
|
4
|
+
export function getTaskStatus(taskId) {
|
|
5
|
+
// Check phase files to determine status
|
|
6
|
+
if (existsSync(join(getActDir(taskId), 'improvement.md'))) {
|
|
7
|
+
// Check if completed
|
|
8
|
+
const metadataPath = join(getPlanDir(taskId), 'metadata.json');
|
|
9
|
+
if (existsSync(metadataPath)) {
|
|
10
|
+
try {
|
|
11
|
+
const metadata = JSON.parse(readFileSync(metadataPath, 'utf-8'));
|
|
12
|
+
if (metadata.completed) {
|
|
13
|
+
return 'completed';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// Ignore parse errors
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return 'acting';
|
|
21
|
+
}
|
|
22
|
+
if (existsSync(join(getCheckDir(taskId), 'evaluation.md'))) {
|
|
23
|
+
return 'checking';
|
|
24
|
+
}
|
|
25
|
+
if (existsSync(join(getDoDir(taskId), 'execution.md'))) {
|
|
26
|
+
return 'doing';
|
|
27
|
+
}
|
|
28
|
+
if (existsSync(join(getPlanDir(taskId), 'plan.md'))) {
|
|
29
|
+
return 'planning';
|
|
30
|
+
}
|
|
31
|
+
return 'planning';
|
|
32
|
+
}
|
|
33
|
+
export function updateTaskStatus(taskId, status) {
|
|
34
|
+
const planDir = getPlanDir(taskId);
|
|
35
|
+
const metadataPath = join(planDir, 'metadata.json');
|
|
36
|
+
// Ensure plan directory exists before writing metadata
|
|
37
|
+
ensureDir(planDir);
|
|
38
|
+
const metadata = existsSync(metadataPath)
|
|
39
|
+
? JSON.parse(readFileSync(metadataPath, 'utf-8'))
|
|
40
|
+
: {};
|
|
41
|
+
metadata.status = status;
|
|
42
|
+
metadata.lastUpdated = new Date().toISOString();
|
|
43
|
+
if (status === 'completed') {
|
|
44
|
+
metadata.completed = true;
|
|
45
|
+
metadata.completedAt = new Date().toISOString();
|
|
46
|
+
}
|
|
47
|
+
writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
|
|
48
|
+
}
|
|
49
|
+
export function getTaskMetadata(taskId) {
|
|
50
|
+
const metadataPath = join(getPlanDir(taskId), 'metadata.json');
|
|
51
|
+
if (existsSync(metadataPath)) {
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(readFileSync(metadataPath, 'utf-8'));
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return {};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=task-status.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task size validation utilities
|
|
3
|
+
* Validates that tasks are atomic and completable in 10 seconds to 10 minutes (pee-break sized)
|
|
4
|
+
*/
|
|
5
|
+
export type TaskSizeCategory = 'atomic' | 'medium' | 'large';
|
|
6
|
+
export interface TaskSizeEstimate {
|
|
7
|
+
category: TaskSizeCategory;
|
|
8
|
+
estimatedMinutes: number;
|
|
9
|
+
confidence: 'high' | 'medium' | 'low';
|
|
10
|
+
reasons: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface ValidationResult {
|
|
13
|
+
isValid: boolean;
|
|
14
|
+
estimate: TaskSizeEstimate;
|
|
15
|
+
suggestions?: string[];
|
|
16
|
+
warning?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Estimates task size based on description
|
|
20
|
+
*/
|
|
21
|
+
export declare function estimateTaskSize(taskDescription: string): TaskSizeEstimate;
|
|
22
|
+
/**
|
|
23
|
+
* Validates if task is atomic (10 seconds to 10 minutes)
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateTaskSize(taskDescription: string): ValidationResult;
|
|
26
|
+
/**
|
|
27
|
+
* Suggests how to break down a large task
|
|
28
|
+
*/
|
|
29
|
+
export declare function suggestTaskBreakdown(taskDescription: string, _estimate?: TaskSizeEstimate): string[];
|
|
30
|
+
/**
|
|
31
|
+
* Validates multiple tasks and returns summary
|
|
32
|
+
*/
|
|
33
|
+
export declare function validateTasks(taskDescriptions: string[]): {
|
|
34
|
+
results: ValidationResult[];
|
|
35
|
+
summary: {
|
|
36
|
+
total: number;
|
|
37
|
+
valid: number;
|
|
38
|
+
invalid: number;
|
|
39
|
+
large: number;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
//# sourceMappingURL=task-validator.d.ts.map
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task size validation utilities
|
|
3
|
+
* Validates that tasks are atomic and completable in 10 seconds to 10 minutes (pee-break sized)
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Keywords that suggest large tasks
|
|
7
|
+
*/
|
|
8
|
+
const LARGE_TASK_KEYWORDS = [
|
|
9
|
+
'implement',
|
|
10
|
+
'build',
|
|
11
|
+
'create system',
|
|
12
|
+
'develop',
|
|
13
|
+
'design',
|
|
14
|
+
'architecture',
|
|
15
|
+
'refactor entire',
|
|
16
|
+
'migrate',
|
|
17
|
+
'rewrite',
|
|
18
|
+
];
|
|
19
|
+
/**
|
|
20
|
+
* Keywords that suggest medium tasks
|
|
21
|
+
*/
|
|
22
|
+
const MEDIUM_TASK_KEYWORDS = [
|
|
23
|
+
'create',
|
|
24
|
+
'add',
|
|
25
|
+
'update',
|
|
26
|
+
'modify',
|
|
27
|
+
'integrate',
|
|
28
|
+
'configure',
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* Keywords that suggest atomic tasks
|
|
32
|
+
*/
|
|
33
|
+
const ATOMIC_TASK_KEYWORDS = [
|
|
34
|
+
'fix',
|
|
35
|
+
'update',
|
|
36
|
+
'add import',
|
|
37
|
+
'change',
|
|
38
|
+
'rename',
|
|
39
|
+
'remove',
|
|
40
|
+
'delete',
|
|
41
|
+
];
|
|
42
|
+
/**
|
|
43
|
+
* Conjunctions that suggest compound tasks
|
|
44
|
+
*/
|
|
45
|
+
const COMPOUND_INDICATORS = [' and ', ' then ', ' after ', ' followed by ', ' plus ', ' along with '];
|
|
46
|
+
/**
|
|
47
|
+
* Estimates task size based on description
|
|
48
|
+
*/
|
|
49
|
+
export function estimateTaskSize(taskDescription) {
|
|
50
|
+
const description = taskDescription.toLowerCase().trim();
|
|
51
|
+
const reasons = [];
|
|
52
|
+
let estimatedMinutes = 2; // Default to small task
|
|
53
|
+
let category = 'atomic';
|
|
54
|
+
let confidence = 'medium';
|
|
55
|
+
// Check for compound tasks (multiple actions)
|
|
56
|
+
const hasCompound = COMPOUND_INDICATORS.some(indicator => description.includes(indicator));
|
|
57
|
+
if (hasCompound) {
|
|
58
|
+
reasons.push('Contains conjunctions suggesting multiple actions');
|
|
59
|
+
estimatedMinutes = 15;
|
|
60
|
+
category = 'large';
|
|
61
|
+
confidence = 'high';
|
|
62
|
+
}
|
|
63
|
+
// Check for large task keywords
|
|
64
|
+
const hasLargeKeyword = LARGE_TASK_KEYWORDS.some(keyword => description.includes(keyword));
|
|
65
|
+
if (hasLargeKeyword && !hasCompound) {
|
|
66
|
+
const foundKeyword = LARGE_TASK_KEYWORDS.find(k => description.includes(k));
|
|
67
|
+
reasons.push(`Contains large task keyword: "${foundKeyword ?? 'unknown'}"`);
|
|
68
|
+
estimatedMinutes = 10;
|
|
69
|
+
category = 'large';
|
|
70
|
+
confidence = 'high';
|
|
71
|
+
}
|
|
72
|
+
// Check for medium task keywords
|
|
73
|
+
const hasMediumKeyword = MEDIUM_TASK_KEYWORDS.some(keyword => description.includes(keyword));
|
|
74
|
+
if (hasMediumKeyword && !hasLargeKeyword && !hasCompound) {
|
|
75
|
+
const foundKeyword = MEDIUM_TASK_KEYWORDS.find(k => description.includes(k));
|
|
76
|
+
reasons.push(`Contains medium task keyword: "${foundKeyword ?? 'unknown'}"`);
|
|
77
|
+
estimatedMinutes = 5;
|
|
78
|
+
category = 'medium';
|
|
79
|
+
confidence = 'medium';
|
|
80
|
+
}
|
|
81
|
+
// Check description length
|
|
82
|
+
const wordCount = description.split(/\s+/).length;
|
|
83
|
+
if (wordCount > 15 && !hasCompound && !hasLargeKeyword) {
|
|
84
|
+
reasons.push(`Long description (${String(wordCount)} words) suggests complexity`);
|
|
85
|
+
estimatedMinutes = Math.min(estimatedMinutes + 3, 12);
|
|
86
|
+
if (category === 'atomic') {
|
|
87
|
+
category = 'medium';
|
|
88
|
+
}
|
|
89
|
+
confidence = 'low';
|
|
90
|
+
}
|
|
91
|
+
// Check for atomic task keywords (overrides if found)
|
|
92
|
+
const hasAtomicKeyword = ATOMIC_TASK_KEYWORDS.some(keyword => description.includes(keyword));
|
|
93
|
+
if (hasAtomicKeyword && !hasCompound && !hasLargeKeyword && wordCount <= 10) {
|
|
94
|
+
const foundKeyword = ATOMIC_TASK_KEYWORDS.find(k => description.includes(k));
|
|
95
|
+
reasons.push(`Contains atomic task keyword: "${foundKeyword ?? 'unknown'}"`);
|
|
96
|
+
estimatedMinutes = 1;
|
|
97
|
+
category = 'atomic';
|
|
98
|
+
confidence = 'high';
|
|
99
|
+
}
|
|
100
|
+
// If no specific indicators, assume small atomic task
|
|
101
|
+
if (reasons.length === 0) {
|
|
102
|
+
reasons.push('No complexity indicators found');
|
|
103
|
+
estimatedMinutes = 2;
|
|
104
|
+
category = 'atomic';
|
|
105
|
+
confidence = 'low';
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
category,
|
|
109
|
+
estimatedMinutes,
|
|
110
|
+
confidence,
|
|
111
|
+
reasons,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Validates if task is atomic (10 seconds to 10 minutes)
|
|
116
|
+
*/
|
|
117
|
+
export function validateTaskSize(taskDescription) {
|
|
118
|
+
const estimate = estimateTaskSize(taskDescription);
|
|
119
|
+
const isValid = estimate.category === 'atomic' ||
|
|
120
|
+
(estimate.category === 'medium' && estimate.estimatedMinutes <= 10);
|
|
121
|
+
const result = {
|
|
122
|
+
isValid,
|
|
123
|
+
estimate,
|
|
124
|
+
};
|
|
125
|
+
if (!isValid) {
|
|
126
|
+
result.warning = `Task appears to be ${estimate.category} sized (estimated ${String(estimate.estimatedMinutes)} minutes), exceeding the pee-break (10 seconds to 10 minutes) guideline`;
|
|
127
|
+
result.suggestions = suggestTaskBreakdown(taskDescription, estimate);
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Suggests how to break down a large task
|
|
133
|
+
*/
|
|
134
|
+
export function suggestTaskBreakdown(taskDescription, _estimate) {
|
|
135
|
+
const suggestions = [];
|
|
136
|
+
const description = taskDescription.toLowerCase();
|
|
137
|
+
// Check for compound tasks
|
|
138
|
+
if (description.includes(' and ')) {
|
|
139
|
+
suggestions.push('Break down by separating actions: split "X and Y" into two separate tasks');
|
|
140
|
+
}
|
|
141
|
+
if (description.includes(' then ') || description.includes(' after ')) {
|
|
142
|
+
suggestions.push('Break down by sequential steps: create separate tasks for each step');
|
|
143
|
+
}
|
|
144
|
+
// Check for implementation tasks
|
|
145
|
+
if (description.includes('implement') || description.includes('build')) {
|
|
146
|
+
suggestions.push('Break down by components: identify sub-components and create tasks for each');
|
|
147
|
+
suggestions.push('Break down by phases: separate design, implementation, and testing into different tasks');
|
|
148
|
+
}
|
|
149
|
+
// Check for system-level tasks
|
|
150
|
+
if (description.includes('system') || description.includes('architecture')) {
|
|
151
|
+
suggestions.push('Break down by modules: create tasks for each module or component');
|
|
152
|
+
suggestions.push('Break down by layers: separate frontend, backend, and integration tasks');
|
|
153
|
+
}
|
|
154
|
+
// Generic suggestions
|
|
155
|
+
if (suggestions.length === 0) {
|
|
156
|
+
suggestions.push('Break down into sequential steps');
|
|
157
|
+
suggestions.push('Identify sub-components and create separate tasks');
|
|
158
|
+
suggestions.push('Consider splitting by functionality or features');
|
|
159
|
+
}
|
|
160
|
+
// Add examples
|
|
161
|
+
suggestions.push('Examples of good atomic tasks: "Add import statement", "Update function signature", "Create test file"');
|
|
162
|
+
suggestions.push('Examples to avoid: "Implement authentication and authorization", "Create API and update frontend"');
|
|
163
|
+
return suggestions;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Validates multiple tasks and returns summary
|
|
167
|
+
*/
|
|
168
|
+
export function validateTasks(taskDescriptions) {
|
|
169
|
+
const results = taskDescriptions.map(validateTaskSize);
|
|
170
|
+
const summary = {
|
|
171
|
+
total: results.length,
|
|
172
|
+
valid: results.filter(r => r.isValid).length,
|
|
173
|
+
invalid: results.filter(r => !r.isValid).length,
|
|
174
|
+
large: results.filter(r => r.estimate.category === 'large').length,
|
|
175
|
+
};
|
|
176
|
+
return { results, summary };
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=task-validator.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface Task {
|
|
2
|
+
content: string;
|
|
3
|
+
completed: boolean;
|
|
4
|
+
index: number;
|
|
5
|
+
dependencies?: number[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Extract tasks from plan.md's Tasks section
|
|
9
|
+
* Parses `- [ ]` and `- [x]` items from the ## Tasks section
|
|
10
|
+
*/
|
|
11
|
+
export declare function extractTasks(planPath: string): Task[];
|
|
12
|
+
/**
|
|
13
|
+
* Update task status in plan.md
|
|
14
|
+
* Updates the task at given index from `- [ ]` to `- [x]` or vice versa
|
|
15
|
+
*/
|
|
16
|
+
export declare function updateTaskStatus(planPath: string, taskIndex: number, completed: boolean): void;
|
|
17
|
+
/**
|
|
18
|
+
* Detect skill reference in task text
|
|
19
|
+
* Looks for patterns like [skill:name] or [skill:name:args]
|
|
20
|
+
*/
|
|
21
|
+
export declare function detectSkillReference(taskContent: string): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Parse dependencies from task content
|
|
24
|
+
* Looks for patterns like [depends:task-0,task-2] or [depends:0,2]
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseDependencies(taskContent: string): number[];
|
|
27
|
+
//# sourceMappingURL=tasks.d.ts.map
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
/**
|
|
3
|
+
* Extract tasks from plan.md's Tasks section
|
|
4
|
+
* Parses `- [ ]` and `- [x]` items from the ## Tasks section
|
|
5
|
+
*/
|
|
6
|
+
export function extractTasks(planPath) {
|
|
7
|
+
const content = readFileSync(planPath, 'utf-8');
|
|
8
|
+
const lines = content.split('\n');
|
|
9
|
+
const tasks = [];
|
|
10
|
+
let inTasksSection = false;
|
|
11
|
+
let taskIndex = 0;
|
|
12
|
+
for (let i = 0; i < lines.length; i++) {
|
|
13
|
+
const line = lines[i];
|
|
14
|
+
if (!line)
|
|
15
|
+
continue;
|
|
16
|
+
const trimmed = line.trim();
|
|
17
|
+
// Detect Tasks section header
|
|
18
|
+
if (trimmed.startsWith('##') && trimmed.toLowerCase().includes('tasks')) {
|
|
19
|
+
inTasksSection = true;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
// Stop at next section (## header)
|
|
23
|
+
if (inTasksSection && trimmed.startsWith('##') && !trimmed.toLowerCase().includes('tasks')) {
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
// Parse task items
|
|
27
|
+
if (inTasksSection) {
|
|
28
|
+
const taskMatch = trimmed.match(/^-\s+\[([ x])\]\s+(.+)$/);
|
|
29
|
+
if (taskMatch?.[1] && taskMatch[2]) {
|
|
30
|
+
const completed = taskMatch[1] === 'x';
|
|
31
|
+
const taskContent = taskMatch[2];
|
|
32
|
+
const dependencies = parseDependencies(taskContent);
|
|
33
|
+
const task = {
|
|
34
|
+
content: taskContent,
|
|
35
|
+
completed,
|
|
36
|
+
index: taskIndex
|
|
37
|
+
};
|
|
38
|
+
if (dependencies.length > 0) {
|
|
39
|
+
task.dependencies = dependencies;
|
|
40
|
+
}
|
|
41
|
+
tasks.push(task);
|
|
42
|
+
taskIndex++;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return tasks;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Update task status in plan.md
|
|
50
|
+
* Updates the task at given index from `- [ ]` to `- [x]` or vice versa
|
|
51
|
+
*/
|
|
52
|
+
export function updateTaskStatus(planPath, taskIndex, completed) {
|
|
53
|
+
const content = readFileSync(planPath, 'utf-8');
|
|
54
|
+
const lines = content.split('\n');
|
|
55
|
+
let inTasksSection = false;
|
|
56
|
+
let currentTaskIndex = 0;
|
|
57
|
+
let modified = false;
|
|
58
|
+
for (let i = 0; i < lines.length; i++) {
|
|
59
|
+
const line = lines[i];
|
|
60
|
+
if (!line)
|
|
61
|
+
continue;
|
|
62
|
+
const trimmed = line.trim();
|
|
63
|
+
// Detect Tasks section header
|
|
64
|
+
if (trimmed.startsWith('##') && trimmed.toLowerCase().includes('tasks')) {
|
|
65
|
+
inTasksSection = true;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
// Stop at next section (## header)
|
|
69
|
+
if (inTasksSection && trimmed.startsWith('##') && !trimmed.toLowerCase().includes('tasks')) {
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
// Update task item
|
|
73
|
+
if (inTasksSection) {
|
|
74
|
+
const taskMatch = trimmed.match(/^-\s+\[([ x])\]\s+(.+)$/);
|
|
75
|
+
if (taskMatch?.[1] && taskMatch[2]) {
|
|
76
|
+
if (currentTaskIndex === taskIndex) {
|
|
77
|
+
const newCheckbox = completed ? '[x]' : '[ ]';
|
|
78
|
+
const taskContent = taskMatch[2];
|
|
79
|
+
// Preserve original indentation
|
|
80
|
+
const indent = line.match(/^(\s*)/)?.[1] ?? '';
|
|
81
|
+
lines[i] = `${indent}- ${newCheckbox} ${taskContent}`;
|
|
82
|
+
modified = true;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
currentTaskIndex++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (modified) {
|
|
90
|
+
writeFileSync(planPath, lines.join('\n'), 'utf-8');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Detect skill reference in task text
|
|
95
|
+
* Looks for patterns like [skill:name] or [skill:name:args]
|
|
96
|
+
*/
|
|
97
|
+
export function detectSkillReference(taskContent) {
|
|
98
|
+
const skillMatch = taskContent.match(/\[skill:([^\]]+)\]/);
|
|
99
|
+
return skillMatch?.[1] ?? null;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Parse dependencies from task content
|
|
103
|
+
* Looks for patterns like [depends:task-0,task-2] or [depends:0,2]
|
|
104
|
+
*/
|
|
105
|
+
export function parseDependencies(taskContent) {
|
|
106
|
+
const dependsMatch = taskContent.match(/\[depends:([^\]]+)\]/);
|
|
107
|
+
if (!dependsMatch?.[1]) {
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
const deps = [];
|
|
111
|
+
const depList = dependsMatch[1].split(',');
|
|
112
|
+
for (const dep of depList) {
|
|
113
|
+
const trimmed = dep.trim();
|
|
114
|
+
// Support both "task-0" and "0" formats
|
|
115
|
+
const indexMatch = trimmed.match(/(?:task-)?(\d+)/);
|
|
116
|
+
if (indexMatch?.[1]) {
|
|
117
|
+
const index = parseInt(indexMatch[1], 10);
|
|
118
|
+
if (!isNaN(index)) {
|
|
119
|
+
deps.push(index);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return deps;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=tasks.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template generators for PDCA phase documents
|
|
3
|
+
* All templates are in English as per specification
|
|
4
|
+
*/
|
|
5
|
+
export declare const planTemplate = "# Plan: {{description}}\n\n## Why\n<!-- Define the problem or improvement opportunity -->\n<!-- What issue are we solving? What opportunity are we pursuing? -->\n\n## Goals\n<!-- SMART Goals: Specific, Measurable, Achievable, Relevant, Time-bound -->\n<!-- Example: \"Reduce image load time by 50% within 2 weeks\" -->\n- [ ] Goal 1:\n- [ ] Goal 2:\n\n## What Changes\n<!-- Specific changes to be made -->\n<!-- List concrete deliverables and modifications -->\n-\n\n## Impact\n<!-- Affected systems, files, processes, or stakeholders -->\n- Affected areas:\n- Dependencies:\n- Risks:\n\n## Tasks\n<!-- Implementation steps as checklist -->\n<!-- \n Task Size Guidelines: Ten seconds to Ten minutes Task Markdown Driven Development!\n Tasks should be atomic and completable in 10 seconds to 10 minutes (pee-break sized).\n - Each task should be a single, focused action\n - Avoid compound tasks (tasks with \"and\" or multiple actions)\n - Break down large tasks into smaller atomic tasks\n \n Examples of good atomic tasks:\n - \"Add import statement\"\n - \"Update function signature\"\n - \"Create test file\"\n - \"Fix typo in documentation\"\n \n Examples of tasks to break down:\n - \"Implement authentication and authorization\" \u2192 Break into: \"Add auth middleware\", \"Create login endpoint\", \"Add password validation\"\n - \"Create API and update frontend\" \u2192 Break into: \"Create API endpoint\", \"Update frontend component\", \"Add API integration\"\n-->\n- [ ] 1.\n- [ ] 2.\n- [ ] 3.\n\n## OpenSpec Change\n<!-- Link to OpenSpec change proposal if applicable -->\n";
|
|
6
|
+
export declare const executionTemplate = "# Execution: {{taskId}}\n\n## Actions Taken\n<!-- Document actions performed -->\n\n## Data Collected\n<!-- Record data collected during execution -->\n\n## Observations\n<!-- Note any observations or insights -->\n\n## Issues Encountered\n<!-- Document any problems or blockers -->\n\n## Time Tracking\n<!-- Track time spent if applicable -->\n";
|
|
7
|
+
export declare const evaluationTemplate = "# Evaluation: {{taskId}}\n\n## Results Summary\n<!-- Summarize execution results -->\n\n## Comparison with Plan\n<!-- Compare actual results with planned objectives -->\n\n| Objective | Planned | Actual | Status |\n|-----------|---------|--------|--------|\n| | | | |\n\n## Deviations Identified\n<!-- List any deviations from the plan -->\n\n## Root Cause Analysis\n<!-- Analyze causes of deviations -->\n\n## Lessons Learned\n<!-- Document insights and learnings -->\n";
|
|
8
|
+
export declare const improvementTemplate = "# Improvement Actions: {{taskId}}\n\n## Corrective Actions\n<!-- Actions to address identified issues -->\n\n## Preventive Measures\n<!-- Measures to prevent similar issues in future -->\n\n## Standardization Opportunities\n<!-- Practices that can be standardized -->\n\n## Next Cycle Planning\n<!-- Plan for next PDCA iteration -->\n";
|
|
9
|
+
export declare const resourcesTemplate = "# Resources: {{description}}\n\n## People / Roles\n<!-- Who is needed for this task? -->\n-\n\n## Tools & Systems\n<!-- What tools, systems, or infrastructure is required? -->\n-\n\n## Data Requirements\n<!-- What data or information is needed? -->\n-\n\n## Time Estimate\n<!-- Estimated effort and timeline -->\n- Effort:\n- Timeline:\n";
|
|
10
|
+
export declare const standardizationTemplate = "# Standardization: {{taskId}}\n\n## Successful Practices\n<!-- Document practices that worked well -->\n\n## Reusable Patterns\n<!-- Patterns that can be reused -->\n\n## Standard Operating Procedures\n<!-- Document SOPs if applicable -->\n";
|
|
11
|
+
export declare function renderTemplate(template: string, vars: Record<string, string>): string;
|
|
12
|
+
//# sourceMappingURL=templates.d.ts.map
|