@bicorne/task-flow 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 +21 -0
- package/README.md +252 -0
- package/SKILL.md +250 -0
- package/assets/.harnessrc +10 -0
- package/assets/PHASE-task.json.example +50 -0
- package/assets/design.md +69 -0
- package/assets/hooks.json +15 -0
- package/assets/product-requirement.md +82 -0
- package/assets/schema.json +127 -0
- package/assets/tasks.md +26 -0
- package/dist/commands/analyze.d.ts +32 -0
- package/dist/commands/analyze.js +338 -0
- package/dist/commands/archive.d.ts +11 -0
- package/dist/commands/archive.js +53 -0
- package/dist/commands/design.d.ts +38 -0
- package/dist/commands/design.js +492 -0
- package/dist/commands/extract.d.ts +31 -0
- package/dist/commands/extract.js +477 -0
- package/dist/commands/init.d.ts +24 -0
- package/dist/commands/init.js +165 -0
- package/dist/commands/merge/index.d.ts +17 -0
- package/dist/commands/merge/index.js +322 -0
- package/dist/commands/merge/merger.d.ts +18 -0
- package/dist/commands/merge/merger.js +151 -0
- package/dist/commands/merge/types.d.ts +67 -0
- package/dist/commands/merge/types.js +6 -0
- package/dist/commands/merge/validators.d.ts +14 -0
- package/dist/commands/merge/validators.js +147 -0
- package/dist/commands/merge.d.ts +7 -0
- package/dist/commands/merge.js +15 -0
- package/dist/commands/start.d.ts +32 -0
- package/dist/commands/start.js +265 -0
- package/dist/commands/status.d.ts +15 -0
- package/dist/commands/status.js +143 -0
- package/dist/commands/sync.d.ts +11 -0
- package/dist/commands/sync.js +58 -0
- package/dist/commands/tasks-gen/doc-parser.d.ts +7 -0
- package/dist/commands/tasks-gen/doc-parser.js +259 -0
- package/dist/commands/tasks-gen/generators.d.ts +33 -0
- package/dist/commands/tasks-gen/generators.js +141 -0
- package/dist/commands/tasks-gen/index.d.ts +30 -0
- package/dist/commands/tasks-gen/index.js +345 -0
- package/dist/commands/tasks-gen/parsers.d.ts +29 -0
- package/dist/commands/tasks-gen/parsers.js +272 -0
- package/dist/commands/tasks-gen/templates.d.ts +8 -0
- package/dist/commands/tasks-gen/templates.js +37 -0
- package/dist/commands/tasks-gen/types.d.ts +71 -0
- package/dist/commands/tasks-gen/types.js +17 -0
- package/dist/commands/tasks-gen/validators.d.ts +14 -0
- package/dist/commands/tasks-gen/validators.js +54 -0
- package/dist/commands/tasks.d.ts +9 -0
- package/dist/commands/tasks.js +22 -0
- package/dist/commands/worktree.d.ts +28 -0
- package/dist/commands/worktree.js +275 -0
- package/dist/hooks/check-prd-exists.d.ts +20 -0
- package/dist/hooks/check-prd-exists.js +61 -0
- package/dist/hooks/check-worktree-conflict.d.ts +34 -0
- package/dist/hooks/check-worktree-conflict.js +107 -0
- package/dist/hooks/hook-runner/executor.d.ts +18 -0
- package/dist/hooks/hook-runner/executor.js +143 -0
- package/dist/hooks/hook-runner/index.d.ts +64 -0
- package/dist/hooks/hook-runner/index.js +220 -0
- package/dist/hooks/hook-runner/loader.d.ts +23 -0
- package/dist/hooks/hook-runner/loader.js +126 -0
- package/dist/hooks/hook-runner/types.d.ts +59 -0
- package/dist/hooks/hook-runner/types.js +6 -0
- package/dist/hooks/hook-runner.d.ts +9 -0
- package/dist/hooks/hook-runner.js +30 -0
- package/dist/hooks/phase-complete-detector.d.ts +35 -0
- package/dist/hooks/phase-complete-detector.js +203 -0
- package/dist/hooks/phase-gate-validator.d.ts +76 -0
- package/dist/hooks/phase-gate-validator.js +407 -0
- package/dist/hooks/save-checkpoint.d.ts +43 -0
- package/dist/hooks/save-checkpoint.js +144 -0
- package/dist/hooks/start-mcp-servers.d.ts +50 -0
- package/dist/hooks/start-mcp-servers.js +75 -0
- package/dist/hooks/stop-mcp-servers.d.ts +40 -0
- package/dist/hooks/stop-mcp-servers.js +58 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +238 -0
- package/dist/lib/archive.d.ts +31 -0
- package/dist/lib/archive.js +226 -0
- package/dist/lib/config.d.ts +93 -0
- package/dist/lib/config.js +251 -0
- package/dist/lib/constants.d.ts +222 -0
- package/dist/lib/constants.js +247 -0
- package/dist/lib/interactive.d.ts +31 -0
- package/dist/lib/interactive.js +166 -0
- package/dist/lib/mcp-client.d.ts +156 -0
- package/dist/lib/mcp-client.js +370 -0
- package/dist/lib/state.d.ts +119 -0
- package/dist/lib/state.js +293 -0
- package/dist/slash/executor.d.ts +22 -0
- package/dist/slash/executor.js +259 -0
- package/dist/slash/index.d.ts +11 -0
- package/dist/slash/index.js +45 -0
- package/dist/slash/parser.d.ts +24 -0
- package/dist/slash/parser.js +101 -0
- package/dist/slash/registry.d.ts +22 -0
- package/dist/slash/registry.js +155 -0
- package/dist/spec/openspec-to-task/builders.d.ts +107 -0
- package/dist/spec/openspec-to-task/builders.js +138 -0
- package/dist/spec/openspec-to-task/index.d.ts +20 -0
- package/dist/spec/openspec-to-task/index.js +182 -0
- package/dist/spec/openspec-to-task/parsers.d.ts +65 -0
- package/dist/spec/openspec-to-task/parsers.js +232 -0
- package/dist/spec/openspec-to-task/types.d.ts +49 -0
- package/dist/spec/openspec-to-task/types.js +6 -0
- package/dist/spec/sync-openspec-to-task.d.ts +7 -0
- package/dist/spec/sync-openspec-to-task.js +21 -0
- package/dist/spec/sync-task-to-openspec.d.ts +27 -0
- package/dist/spec/sync-task-to-openspec.js +288 -0
- package/dist/types/ai-context.d.ts +108 -0
- package/dist/types/ai-context.js +9 -0
- package/package.json +66 -0
- package/references/AI-CONVERSATION-TUTORIAL.md +270 -0
- package/references/CLI-TUTORIAL.md +447 -0
- package/references/GIT-WORKTREE-SOP.md +109 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* state.ts
|
|
4
|
+
* State management for task-flow
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.createInitialState = createInitialState;
|
|
41
|
+
exports.loadState = loadState;
|
|
42
|
+
exports.saveState = saveState;
|
|
43
|
+
exports.updateState = updateState;
|
|
44
|
+
exports.upsertWorktree = upsertWorktree;
|
|
45
|
+
exports.removeWorktree = removeWorktree;
|
|
46
|
+
exports.addTaskToHistory = addTaskToHistory;
|
|
47
|
+
exports.getActiveWorktrees = getActiveWorktrees;
|
|
48
|
+
exports.getWorktreeByTask = getWorktreeByTask;
|
|
49
|
+
exports.clearCurrentTask = clearCurrentTask;
|
|
50
|
+
const fs = __importStar(require("fs"));
|
|
51
|
+
const path = __importStar(require("path"));
|
|
52
|
+
const config_1 = require("./config");
|
|
53
|
+
/**
|
|
54
|
+
* Create initial state object
|
|
55
|
+
* @returns Initial state
|
|
56
|
+
*/
|
|
57
|
+
function createInitialState() {
|
|
58
|
+
const now = new Date().toISOString();
|
|
59
|
+
return {
|
|
60
|
+
version: '1.0',
|
|
61
|
+
status: 'initialized',
|
|
62
|
+
currentPhase: null,
|
|
63
|
+
currentTask: null,
|
|
64
|
+
currentSnapshot: null,
|
|
65
|
+
reviewConclusion: null,
|
|
66
|
+
phaseCompleted: null,
|
|
67
|
+
taskHistory: [],
|
|
68
|
+
worktrees: [],
|
|
69
|
+
lastGateBlockedAt: null,
|
|
70
|
+
lastGateBlockedReason: null,
|
|
71
|
+
lastGateBlockedPhase: null,
|
|
72
|
+
lastMissingValidations: [],
|
|
73
|
+
startedAt: now,
|
|
74
|
+
updatedAt: now,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Load state from file
|
|
79
|
+
* @param config - Config object
|
|
80
|
+
* @returns State object
|
|
81
|
+
*/
|
|
82
|
+
function loadState(config) {
|
|
83
|
+
const cfg = config || (0, config_1.loadConfig)();
|
|
84
|
+
const statePath = path.resolve(cfg.harnessRootAbs || '.harness', 'state.json');
|
|
85
|
+
if (!fs.existsSync(statePath)) {
|
|
86
|
+
return createInitialState();
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
return JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
93
|
+
console.warn('[STATE] Failed to load state file, attempting to restore from backup:', errorMessage);
|
|
94
|
+
// Try to restore from latest backup
|
|
95
|
+
try {
|
|
96
|
+
const harnessDir = cfg.harnessRootAbs;
|
|
97
|
+
if (!harnessDir) {
|
|
98
|
+
throw new Error('harnessRootAbs is undefined');
|
|
99
|
+
}
|
|
100
|
+
const backupPattern = /state\.json\.bak\.(\d+)$/;
|
|
101
|
+
const files = fs.readdirSync(harnessDir);
|
|
102
|
+
const backups = files
|
|
103
|
+
.filter(f => backupPattern.test(f))
|
|
104
|
+
.sort((a, b) => {
|
|
105
|
+
const matchA = a.match(backupPattern);
|
|
106
|
+
const matchB = b.match(backupPattern);
|
|
107
|
+
return (matchB?.[1] || '0').localeCompare(matchA?.[1] || '0');
|
|
108
|
+
});
|
|
109
|
+
if (backups.length > 0) {
|
|
110
|
+
const latestBackupName = backups[0];
|
|
111
|
+
if (!latestBackupName) {
|
|
112
|
+
throw new Error('No backup file found');
|
|
113
|
+
}
|
|
114
|
+
const latestBackup = path.resolve(harnessDir, latestBackupName);
|
|
115
|
+
console.log(`[STATE] Restoring from backup: ${latestBackup}`);
|
|
116
|
+
return JSON.parse(fs.readFileSync(latestBackup, 'utf8'));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (restoreError) {
|
|
120
|
+
console.warn('[STATE] Failed to restore from backup:', restoreError instanceof Error ? restoreError.message : 'Unknown error');
|
|
121
|
+
}
|
|
122
|
+
console.warn('[STATE] Creating new state');
|
|
123
|
+
return createInitialState();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Save state to file
|
|
128
|
+
* @param state - State object
|
|
129
|
+
* @param config - Config object
|
|
130
|
+
* @returns Success
|
|
131
|
+
*/
|
|
132
|
+
function saveState(state, config) {
|
|
133
|
+
const cfg = config || (0, config_1.loadConfig)();
|
|
134
|
+
const harnessRoot = cfg.harnessRootAbs;
|
|
135
|
+
if (!harnessRoot) {
|
|
136
|
+
console.error('[STATE] harnessRootAbs is undefined, cannot save state');
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
const statePath = path.resolve(harnessRoot, 'state.json');
|
|
140
|
+
try {
|
|
141
|
+
state.updatedAt = new Date().toISOString();
|
|
142
|
+
fs.mkdirSync(harnessRoot, { recursive: true });
|
|
143
|
+
// Backup existing state file before overwriting (prevent data corruption)
|
|
144
|
+
if (fs.existsSync(statePath)) {
|
|
145
|
+
const backupPath = `${statePath}.bak.${Date.now()}`;
|
|
146
|
+
try {
|
|
147
|
+
fs.copyFileSync(statePath, backupPath);
|
|
148
|
+
console.log(`[STATE] Created backup: ${backupPath}`);
|
|
149
|
+
}
|
|
150
|
+
catch (backupError) {
|
|
151
|
+
console.warn('[STATE] Failed to create backup:', backupError instanceof Error ? backupError.message : 'Unknown error');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
159
|
+
console.error('[STATE] Failed to save state:', errorMessage);
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Update state with partial changes
|
|
165
|
+
* @param updates - Updates to apply
|
|
166
|
+
* @param config - Config object
|
|
167
|
+
* @returns Updated state
|
|
168
|
+
*/
|
|
169
|
+
function updateState(updates, config) {
|
|
170
|
+
const state = loadState(config);
|
|
171
|
+
const updated = {
|
|
172
|
+
...state,
|
|
173
|
+
...updates,
|
|
174
|
+
updatedAt: new Date().toISOString(),
|
|
175
|
+
};
|
|
176
|
+
saveState(updated, config);
|
|
177
|
+
return updated;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Add or update worktree in state
|
|
181
|
+
* @param worktree - Worktree info
|
|
182
|
+
* @param config - Config object
|
|
183
|
+
* @returns Updated state
|
|
184
|
+
*/
|
|
185
|
+
function upsertWorktree(worktree, config) {
|
|
186
|
+
const state = loadState(config);
|
|
187
|
+
const worktrees = Array.isArray(state.worktrees) ? [...state.worktrees] : [];
|
|
188
|
+
const existingIndex = worktrees.findIndex((wt) => wt?.name === worktree.name);
|
|
189
|
+
const now = new Date().toISOString();
|
|
190
|
+
const nextWorktree = {
|
|
191
|
+
name: worktree.name || '',
|
|
192
|
+
path: worktree.path,
|
|
193
|
+
branch: worktree.branch,
|
|
194
|
+
status: worktree.status || 'active',
|
|
195
|
+
agent: worktree.agent || 'implementer',
|
|
196
|
+
task: worktree.task,
|
|
197
|
+
startedAt: (existingIndex >= 0 && worktrees[existingIndex]?.startedAt) ? worktrees[existingIndex].startedAt : now,
|
|
198
|
+
lastActivity: now,
|
|
199
|
+
pendingChanges: worktree.pendingChanges || [],
|
|
200
|
+
};
|
|
201
|
+
if (existingIndex >= 0) {
|
|
202
|
+
worktrees[existingIndex] = nextWorktree;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
worktrees.push(nextWorktree);
|
|
206
|
+
}
|
|
207
|
+
state.worktrees = worktrees;
|
|
208
|
+
saveState(state, config);
|
|
209
|
+
return state;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Remove worktree from state
|
|
213
|
+
* @param worktreeName - Worktree name
|
|
214
|
+
* @param config - Config object
|
|
215
|
+
* @returns Updated state
|
|
216
|
+
*/
|
|
217
|
+
function removeWorktree(worktreeName, config) {
|
|
218
|
+
const state = loadState(config);
|
|
219
|
+
state.worktrees = (state.worktrees || []).filter((wt) => wt?.name !== worktreeName);
|
|
220
|
+
saveState(state, config);
|
|
221
|
+
return state;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Add task to history
|
|
225
|
+
* @param taskInfo - Task info
|
|
226
|
+
* @param config - Config object
|
|
227
|
+
* @returns Updated state
|
|
228
|
+
*/
|
|
229
|
+
function addTaskToHistory(taskInfo, config) {
|
|
230
|
+
const state = loadState(config);
|
|
231
|
+
const history = Array.isArray(state.taskHistory) ? [...state.taskHistory] : [];
|
|
232
|
+
const existingIndex = history.findIndex((item) => item?.taskId === taskInfo.taskId);
|
|
233
|
+
const now = new Date().toISOString();
|
|
234
|
+
const summary = {
|
|
235
|
+
taskId: taskInfo.taskId || '',
|
|
236
|
+
title: taskInfo.title,
|
|
237
|
+
status: taskInfo.status || 'running',
|
|
238
|
+
currentPhase: taskInfo.currentPhase ?? null,
|
|
239
|
+
phaseCompleted: taskInfo.phaseCompleted ?? null,
|
|
240
|
+
reviewConclusion: taskInfo.reviewConclusion ?? null,
|
|
241
|
+
startedAt: taskInfo.startedAt || now,
|
|
242
|
+
completedAt: taskInfo.completedAt ?? null,
|
|
243
|
+
lastUpdatedAt: now,
|
|
244
|
+
archivePath: taskInfo.archivePath,
|
|
245
|
+
};
|
|
246
|
+
if (existingIndex >= 0) {
|
|
247
|
+
history[existingIndex] = summary;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
history.push(summary);
|
|
251
|
+
}
|
|
252
|
+
state.taskHistory = history.sort((a, b) => String(b.lastUpdatedAt || '').localeCompare(String(a.lastUpdatedAt || '')));
|
|
253
|
+
saveState(state, config);
|
|
254
|
+
return state;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Get active worktrees
|
|
258
|
+
* @param config - Config object
|
|
259
|
+
* @returns Active worktrees
|
|
260
|
+
*/
|
|
261
|
+
function getActiveWorktrees(config) {
|
|
262
|
+
const state = loadState(config);
|
|
263
|
+
return (state.worktrees || []).filter((wt) => wt?.status === 'active');
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Get worktree by task ID
|
|
267
|
+
* @param taskId - Task ID
|
|
268
|
+
* @param config - Config object
|
|
269
|
+
* @returns Worktree or null
|
|
270
|
+
*/
|
|
271
|
+
function getWorktreeByTask(taskId, config) {
|
|
272
|
+
const state = loadState(config);
|
|
273
|
+
return (state.worktrees || []).find((wt) => wt?.task === taskId) || null;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Clear current task state (after merge)
|
|
277
|
+
* @param config - Config object
|
|
278
|
+
* @returns Updated state
|
|
279
|
+
*/
|
|
280
|
+
function clearCurrentTask(config) {
|
|
281
|
+
const state = loadState(config);
|
|
282
|
+
const now = new Date().toISOString();
|
|
283
|
+
state.status = 'completed';
|
|
284
|
+
state.currentPhase = null;
|
|
285
|
+
state.currentTask = null;
|
|
286
|
+
state.currentSnapshot = null;
|
|
287
|
+
state.reviewConclusion = null;
|
|
288
|
+
state.phaseCompleted = null;
|
|
289
|
+
state.worktrees = [];
|
|
290
|
+
state.updatedAt = now;
|
|
291
|
+
saveState(state, config);
|
|
292
|
+
return state;
|
|
293
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* slash/executor.ts
|
|
3
|
+
* Execute slash commands by dispatching to the appropriate command modules.
|
|
4
|
+
* Supports both single commands and composite (multi-step) commands.
|
|
5
|
+
*/
|
|
6
|
+
import { ParsedSlashCommand } from './parser';
|
|
7
|
+
export interface SlashExecResult {
|
|
8
|
+
success: boolean;
|
|
9
|
+
command: string;
|
|
10
|
+
changeName?: string;
|
|
11
|
+
taskId?: string;
|
|
12
|
+
steps?: StepResult[];
|
|
13
|
+
message?: string;
|
|
14
|
+
error?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface StepResult {
|
|
17
|
+
step: string;
|
|
18
|
+
success: boolean;
|
|
19
|
+
message?: string;
|
|
20
|
+
detail?: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
export declare function executeSlashCommand(parsed: ParsedSlashCommand): Promise<SlashExecResult>;
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* slash/executor.ts
|
|
4
|
+
* Execute slash commands by dispatching to the appropriate command modules.
|
|
5
|
+
* Supports both single commands and composite (multi-step) commands.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.executeSlashCommand = executeSlashCommand;
|
|
9
|
+
const registry_1 = require("./registry");
|
|
10
|
+
const extract_1 = require("../commands/extract");
|
|
11
|
+
const design_1 = require("../commands/design");
|
|
12
|
+
const tasks_gen_1 = require("../commands/tasks-gen");
|
|
13
|
+
const worktree_1 = require("../commands/worktree");
|
|
14
|
+
const init_1 = require("../commands/init");
|
|
15
|
+
const status_1 = require("../commands/status");
|
|
16
|
+
const archive_1 = require("../lib/archive");
|
|
17
|
+
const merge_1 = require("../commands/merge");
|
|
18
|
+
const sync_task_to_openspec_1 = require("../spec/sync-task-to-openspec");
|
|
19
|
+
function extractChangeName(parsed) {
|
|
20
|
+
const firstArg = parsed.args[0];
|
|
21
|
+
if (firstArg && !firstArg.startsWith('--')) {
|
|
22
|
+
return firstArg;
|
|
23
|
+
}
|
|
24
|
+
for (let i = 0; i < parsed.args.length - 1; i += 1) {
|
|
25
|
+
if (parsed.args[i] === '--change' || parsed.args[i] === '--task-id') {
|
|
26
|
+
return parsed.args[i + 1];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
function extractTaskId(parsed) {
|
|
32
|
+
for (let i = 0; i < parsed.args.length - 1; i += 1) {
|
|
33
|
+
if (parsed.args[i] === '--task-id') {
|
|
34
|
+
return parsed.args[i + 1];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const firstArg = parsed.args[0];
|
|
38
|
+
if (firstArg && !firstArg.startsWith('--')) {
|
|
39
|
+
return firstArg;
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
function extractFlagValue(parsed, flag) {
|
|
44
|
+
const idx = parsed.args.indexOf(`--${flag}`);
|
|
45
|
+
if (idx === -1 || idx + 1 >= parsed.args.length) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
return parsed.args[idx + 1];
|
|
49
|
+
}
|
|
50
|
+
function hasFlag(parsed, flag) {
|
|
51
|
+
return parsed.args.includes(`--${flag}`) || parsed.args.includes(`-${flag}`);
|
|
52
|
+
}
|
|
53
|
+
async function executeSingleCommand(commandName, changeName, taskId, parsed) {
|
|
54
|
+
switch (commandName) {
|
|
55
|
+
case 'extract': {
|
|
56
|
+
if (!changeName) {
|
|
57
|
+
return { step: 'extract', success: false, message: 'Missing change name. Usage: /tf:extract <change-name>' };
|
|
58
|
+
}
|
|
59
|
+
const result = await (0, extract_1.extract)({
|
|
60
|
+
change: changeName,
|
|
61
|
+
force: hasFlag(parsed, 'force'),
|
|
62
|
+
contextFile: extractFlagValue(parsed, 'context-file'),
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
step: 'extract',
|
|
66
|
+
success: result.success,
|
|
67
|
+
message: result.success ? `PRD created: ${result.prdPath}` : result.message,
|
|
68
|
+
detail: result.success ? { prdPath: result.prdPath, changeDir: result.changeDir } : undefined,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
case 'design': {
|
|
72
|
+
if (!changeName) {
|
|
73
|
+
return { step: 'design', success: false, message: 'Missing change name. Usage: /tf:design <change-name>' };
|
|
74
|
+
}
|
|
75
|
+
const result = await (0, design_1.generateDesign)({
|
|
76
|
+
change: changeName,
|
|
77
|
+
force: hasFlag(parsed, 'force'),
|
|
78
|
+
skipPrompt: true,
|
|
79
|
+
contextFile: extractFlagValue(parsed, 'context-file'),
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
step: 'design',
|
|
83
|
+
success: result.success,
|
|
84
|
+
message: result.success ? `Design created: ${result.designPath}` : result.message,
|
|
85
|
+
detail: result.success ? { designPath: result.designPath } : undefined,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
case 'tasks': {
|
|
89
|
+
if (!changeName) {
|
|
90
|
+
return { step: 'tasks', success: false, message: 'Missing change name. Usage: /tf:tasks <change-name>' };
|
|
91
|
+
}
|
|
92
|
+
const result = await (0, tasks_gen_1.generateTasks)({
|
|
93
|
+
change: changeName,
|
|
94
|
+
yes: true,
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
step: 'tasks',
|
|
98
|
+
success: result.success,
|
|
99
|
+
message: result.success ? `Tasks generated: ${result.generatedFiles?.length || 0} files` : result.message,
|
|
100
|
+
detail: result.success ? { tasksDir: result.tasksDir, manifestPath: result.manifestPath } : undefined,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
case 'worktree': {
|
|
104
|
+
if (!changeName) {
|
|
105
|
+
return { step: 'worktree', success: false, message: 'Missing change name. Usage: /tf:worktree <change-name>' };
|
|
106
|
+
}
|
|
107
|
+
const result = await (0, worktree_1.createWorktree)({
|
|
108
|
+
change: changeName,
|
|
109
|
+
yes: true,
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
step: 'worktree',
|
|
113
|
+
success: result.success,
|
|
114
|
+
message: result.success ? `Worktree created: ${result.worktreePath}` : result.message,
|
|
115
|
+
detail: result.success ? { worktreePath: result.worktreePath, branchName: result.branchName } : undefined,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
case 'init': {
|
|
119
|
+
const result = (0, init_1.initHarness)({
|
|
120
|
+
force: hasFlag(parsed, 'force'),
|
|
121
|
+
});
|
|
122
|
+
return {
|
|
123
|
+
step: 'init',
|
|
124
|
+
success: result.success,
|
|
125
|
+
message: result.success ? 'Task flow initialized' : result.message,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
case 'status': {
|
|
129
|
+
(0, status_1.showStatus)({});
|
|
130
|
+
return { step: 'status', success: true, message: 'Status displayed' };
|
|
131
|
+
}
|
|
132
|
+
case 'archive': {
|
|
133
|
+
const tid = taskId || changeName;
|
|
134
|
+
if (!tid) {
|
|
135
|
+
return { step: 'archive', success: false, message: 'Missing task ID. Usage: /tf:archive --task-id <id>' };
|
|
136
|
+
}
|
|
137
|
+
const result = (0, archive_1.archiveTaskState)({
|
|
138
|
+
taskId: tid,
|
|
139
|
+
eventType: 'task_completed',
|
|
140
|
+
status: 'completed',
|
|
141
|
+
});
|
|
142
|
+
if ('skipped' in result && result.skipped) {
|
|
143
|
+
return { step: 'archive', success: true, message: `Skipped: ${result.reason}` };
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
step: 'archive',
|
|
147
|
+
success: true,
|
|
148
|
+
message: `Archived: ${result.archivePath}`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
case 'merge': {
|
|
152
|
+
const tid = taskId || changeName;
|
|
153
|
+
if (!tid) {
|
|
154
|
+
return { step: 'merge', success: false, message: 'Missing task ID. Usage: /tf:merge <task-id>' };
|
|
155
|
+
}
|
|
156
|
+
const result = (0, merge_1.mergeTask)({
|
|
157
|
+
taskId: tid,
|
|
158
|
+
skipReviewCheck: hasFlag(parsed, 'skip-review-check'),
|
|
159
|
+
dryRun: hasFlag(parsed, 'dry-run'),
|
|
160
|
+
});
|
|
161
|
+
return {
|
|
162
|
+
step: 'merge',
|
|
163
|
+
success: result.success,
|
|
164
|
+
message: result.success ? `Merged: ${result.taskId}` : result.message,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
case 'sync': {
|
|
168
|
+
const tid = taskId || changeName;
|
|
169
|
+
if (!tid) {
|
|
170
|
+
return { step: 'sync', success: false, message: 'Missing task ID. Usage: /tf:sync --task-id <id>' };
|
|
171
|
+
}
|
|
172
|
+
const result = (0, sync_task_to_openspec_1.syncTaskToSpec)({ taskId: tid });
|
|
173
|
+
if (result.skipped) {
|
|
174
|
+
return { step: 'sync', success: true, message: `Skipped: ${result.reason}` };
|
|
175
|
+
}
|
|
176
|
+
return { step: 'sync', success: true, message: 'Synced' };
|
|
177
|
+
}
|
|
178
|
+
default:
|
|
179
|
+
return { step: commandName, success: false, message: `Unknown command: ${commandName}` };
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async function executeCompositeCommand(cmdDef, changeName, taskId, parsed) {
|
|
183
|
+
const steps = cmdDef.steps || [];
|
|
184
|
+
const stepResults = [];
|
|
185
|
+
console.log(`[SLASH] Executing composite command: /tf:${cmdDef.name}`);
|
|
186
|
+
console.log(`[SLASH] Steps: ${steps.join(' → ')}`);
|
|
187
|
+
console.log('');
|
|
188
|
+
for (const step of steps) {
|
|
189
|
+
console.log(`[SLASH] → Step: ${step}`);
|
|
190
|
+
const result = await executeSingleCommand(step, changeName, taskId, parsed);
|
|
191
|
+
stepResults.push(result);
|
|
192
|
+
if (result.success) {
|
|
193
|
+
console.log(`[SLASH] ✓ ${step}: ${result.message || 'OK'}`);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
console.log(`[SLASH] ✗ ${step}: ${result.message || 'Failed'}`);
|
|
197
|
+
return {
|
|
198
|
+
success: false,
|
|
199
|
+
command: cmdDef.name,
|
|
200
|
+
changeName,
|
|
201
|
+
taskId,
|
|
202
|
+
steps: stepResults,
|
|
203
|
+
message: `Composite command "${cmdDef.name}" failed at step "${step}": ${result.message}`,
|
|
204
|
+
error: result.message,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
console.log('');
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
success: true,
|
|
211
|
+
command: cmdDef.name,
|
|
212
|
+
changeName,
|
|
213
|
+
taskId,
|
|
214
|
+
steps: stepResults,
|
|
215
|
+
message: `Composite command "/tf:${cmdDef.name}" completed successfully`,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
async function executeSlashCommand(parsed) {
|
|
219
|
+
const cmdDef = (0, registry_1.getCommand)(parsed.command);
|
|
220
|
+
if (!cmdDef) {
|
|
221
|
+
return {
|
|
222
|
+
success: false,
|
|
223
|
+
command: parsed.command,
|
|
224
|
+
message: `Unknown slash command: /tf:${parsed.command}`,
|
|
225
|
+
error: `Command "${parsed.command}" not found. Run /tf:help for available commands.`,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const changeName = extractChangeName(parsed);
|
|
229
|
+
const taskId = extractTaskId(parsed);
|
|
230
|
+
if (cmdDef.requiresChange && !changeName) {
|
|
231
|
+
return {
|
|
232
|
+
success: false,
|
|
233
|
+
command: parsed.command,
|
|
234
|
+
message: `Command "/tf:${parsed.command}" requires a change name. Usage: ${cmdDef.usage}`,
|
|
235
|
+
error: 'Missing change name',
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
if (cmdDef.requiresTaskId && !taskId && !changeName) {
|
|
239
|
+
return {
|
|
240
|
+
success: false,
|
|
241
|
+
command: parsed.command,
|
|
242
|
+
message: `Command "/tf:${parsed.command}" requires a task ID. Usage: ${cmdDef.usage}`,
|
|
243
|
+
error: 'Missing task ID',
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
if (cmdDef.category === 'composite') {
|
|
247
|
+
return executeCompositeCommand(cmdDef, changeName, taskId, parsed);
|
|
248
|
+
}
|
|
249
|
+
const stepResult = await executeSingleCommand(parsed.command, changeName, taskId, parsed);
|
|
250
|
+
return {
|
|
251
|
+
success: stepResult.success,
|
|
252
|
+
command: parsed.command,
|
|
253
|
+
changeName,
|
|
254
|
+
taskId,
|
|
255
|
+
steps: [stepResult],
|
|
256
|
+
message: stepResult.message,
|
|
257
|
+
error: stepResult.success ? undefined : stepResult.message,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* slash/index.ts
|
|
3
|
+
* Facade module for the slash command system
|
|
4
|
+
*
|
|
5
|
+
* Provides a single entry point for parsing and executing slash commands.
|
|
6
|
+
*/
|
|
7
|
+
export { parseSlashCommand, isSlashCommand, formatSlashCommand, ParsedSlashCommand } from './parser';
|
|
8
|
+
export { getCommand, getAllCommands, getCompositeCommands, getSingleCommands, hasCommand, printSlashHelp, SlashCommandDef } from './registry';
|
|
9
|
+
export { executeSlashCommand, SlashExecResult, StepResult } from './executor';
|
|
10
|
+
import { SlashExecResult } from './executor';
|
|
11
|
+
export declare function runSlashCommand(input: string): Promise<SlashExecResult>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* slash/index.ts
|
|
4
|
+
* Facade module for the slash command system
|
|
5
|
+
*
|
|
6
|
+
* Provides a single entry point for parsing and executing slash commands.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.executeSlashCommand = exports.printSlashHelp = exports.hasCommand = exports.getSingleCommands = exports.getCompositeCommands = exports.getAllCommands = exports.getCommand = exports.formatSlashCommand = exports.isSlashCommand = exports.parseSlashCommand = void 0;
|
|
10
|
+
exports.runSlashCommand = runSlashCommand;
|
|
11
|
+
var parser_1 = require("./parser");
|
|
12
|
+
Object.defineProperty(exports, "parseSlashCommand", { enumerable: true, get: function () { return parser_1.parseSlashCommand; } });
|
|
13
|
+
Object.defineProperty(exports, "isSlashCommand", { enumerable: true, get: function () { return parser_1.isSlashCommand; } });
|
|
14
|
+
Object.defineProperty(exports, "formatSlashCommand", { enumerable: true, get: function () { return parser_1.formatSlashCommand; } });
|
|
15
|
+
var registry_1 = require("./registry");
|
|
16
|
+
Object.defineProperty(exports, "getCommand", { enumerable: true, get: function () { return registry_1.getCommand; } });
|
|
17
|
+
Object.defineProperty(exports, "getAllCommands", { enumerable: true, get: function () { return registry_1.getAllCommands; } });
|
|
18
|
+
Object.defineProperty(exports, "getCompositeCommands", { enumerable: true, get: function () { return registry_1.getCompositeCommands; } });
|
|
19
|
+
Object.defineProperty(exports, "getSingleCommands", { enumerable: true, get: function () { return registry_1.getSingleCommands; } });
|
|
20
|
+
Object.defineProperty(exports, "hasCommand", { enumerable: true, get: function () { return registry_1.hasCommand; } });
|
|
21
|
+
Object.defineProperty(exports, "printSlashHelp", { enumerable: true, get: function () { return registry_1.printSlashHelp; } });
|
|
22
|
+
var executor_1 = require("./executor");
|
|
23
|
+
Object.defineProperty(exports, "executeSlashCommand", { enumerable: true, get: function () { return executor_1.executeSlashCommand; } });
|
|
24
|
+
const parser_2 = require("./parser");
|
|
25
|
+
const executor_2 = require("./executor");
|
|
26
|
+
async function runSlashCommand(input) {
|
|
27
|
+
if (!(0, parser_2.isSlashCommand)(input)) {
|
|
28
|
+
return {
|
|
29
|
+
success: false,
|
|
30
|
+
command: '',
|
|
31
|
+
message: `Not a valid slash command: "${input}". Format: /tf:<command> [args]`,
|
|
32
|
+
error: 'Invalid slash command format',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const parsed = (0, parser_2.parseSlashCommand)(input);
|
|
36
|
+
if (!parsed.valid) {
|
|
37
|
+
return {
|
|
38
|
+
success: false,
|
|
39
|
+
command: '',
|
|
40
|
+
message: parsed.error,
|
|
41
|
+
error: parsed.error,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
return (0, executor_2.executeSlashCommand)(parsed);
|
|
45
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* slash/parser.ts
|
|
3
|
+
* Parse slash command strings like /tf:propose or /taskflow:apply
|
|
4
|
+
*
|
|
5
|
+
* Format: /<namespace>:<command> [args...]
|
|
6
|
+
* Examples:
|
|
7
|
+
* /tf:propose add-dark-mode
|
|
8
|
+
* /taskflow:apply
|
|
9
|
+
* /tf:archive --task-id my-feature
|
|
10
|
+
*/
|
|
11
|
+
export interface ParsedSlashCommand {
|
|
12
|
+
valid: boolean;
|
|
13
|
+
namespace: string;
|
|
14
|
+
command: string;
|
|
15
|
+
args: string[];
|
|
16
|
+
raw: string;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
declare const DEFAULT_NAMESPACE = "tf";
|
|
20
|
+
declare const VALID_NAMESPACES: readonly ["tf", "taskflow"];
|
|
21
|
+
export declare function parseSlashCommand(input: string): ParsedSlashCommand;
|
|
22
|
+
export declare function isSlashCommand(input: string): boolean;
|
|
23
|
+
export declare function formatSlashCommand(namespace: string, command: string): string;
|
|
24
|
+
export { DEFAULT_NAMESPACE, VALID_NAMESPACES };
|