@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.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +252 -0
  3. package/SKILL.md +250 -0
  4. package/assets/.harnessrc +10 -0
  5. package/assets/PHASE-task.json.example +50 -0
  6. package/assets/design.md +69 -0
  7. package/assets/hooks.json +15 -0
  8. package/assets/product-requirement.md +82 -0
  9. package/assets/schema.json +127 -0
  10. package/assets/tasks.md +26 -0
  11. package/dist/commands/analyze.d.ts +32 -0
  12. package/dist/commands/analyze.js +338 -0
  13. package/dist/commands/archive.d.ts +11 -0
  14. package/dist/commands/archive.js +53 -0
  15. package/dist/commands/design.d.ts +38 -0
  16. package/dist/commands/design.js +492 -0
  17. package/dist/commands/extract.d.ts +31 -0
  18. package/dist/commands/extract.js +477 -0
  19. package/dist/commands/init.d.ts +24 -0
  20. package/dist/commands/init.js +165 -0
  21. package/dist/commands/merge/index.d.ts +17 -0
  22. package/dist/commands/merge/index.js +322 -0
  23. package/dist/commands/merge/merger.d.ts +18 -0
  24. package/dist/commands/merge/merger.js +151 -0
  25. package/dist/commands/merge/types.d.ts +67 -0
  26. package/dist/commands/merge/types.js +6 -0
  27. package/dist/commands/merge/validators.d.ts +14 -0
  28. package/dist/commands/merge/validators.js +147 -0
  29. package/dist/commands/merge.d.ts +7 -0
  30. package/dist/commands/merge.js +15 -0
  31. package/dist/commands/start.d.ts +32 -0
  32. package/dist/commands/start.js +265 -0
  33. package/dist/commands/status.d.ts +15 -0
  34. package/dist/commands/status.js +143 -0
  35. package/dist/commands/sync.d.ts +11 -0
  36. package/dist/commands/sync.js +58 -0
  37. package/dist/commands/tasks-gen/doc-parser.d.ts +7 -0
  38. package/dist/commands/tasks-gen/doc-parser.js +259 -0
  39. package/dist/commands/tasks-gen/generators.d.ts +33 -0
  40. package/dist/commands/tasks-gen/generators.js +141 -0
  41. package/dist/commands/tasks-gen/index.d.ts +30 -0
  42. package/dist/commands/tasks-gen/index.js +345 -0
  43. package/dist/commands/tasks-gen/parsers.d.ts +29 -0
  44. package/dist/commands/tasks-gen/parsers.js +272 -0
  45. package/dist/commands/tasks-gen/templates.d.ts +8 -0
  46. package/dist/commands/tasks-gen/templates.js +37 -0
  47. package/dist/commands/tasks-gen/types.d.ts +71 -0
  48. package/dist/commands/tasks-gen/types.js +17 -0
  49. package/dist/commands/tasks-gen/validators.d.ts +14 -0
  50. package/dist/commands/tasks-gen/validators.js +54 -0
  51. package/dist/commands/tasks.d.ts +9 -0
  52. package/dist/commands/tasks.js +22 -0
  53. package/dist/commands/worktree.d.ts +28 -0
  54. package/dist/commands/worktree.js +275 -0
  55. package/dist/hooks/check-prd-exists.d.ts +20 -0
  56. package/dist/hooks/check-prd-exists.js +61 -0
  57. package/dist/hooks/check-worktree-conflict.d.ts +34 -0
  58. package/dist/hooks/check-worktree-conflict.js +107 -0
  59. package/dist/hooks/hook-runner/executor.d.ts +18 -0
  60. package/dist/hooks/hook-runner/executor.js +143 -0
  61. package/dist/hooks/hook-runner/index.d.ts +64 -0
  62. package/dist/hooks/hook-runner/index.js +220 -0
  63. package/dist/hooks/hook-runner/loader.d.ts +23 -0
  64. package/dist/hooks/hook-runner/loader.js +126 -0
  65. package/dist/hooks/hook-runner/types.d.ts +59 -0
  66. package/dist/hooks/hook-runner/types.js +6 -0
  67. package/dist/hooks/hook-runner.d.ts +9 -0
  68. package/dist/hooks/hook-runner.js +30 -0
  69. package/dist/hooks/phase-complete-detector.d.ts +35 -0
  70. package/dist/hooks/phase-complete-detector.js +203 -0
  71. package/dist/hooks/phase-gate-validator.d.ts +76 -0
  72. package/dist/hooks/phase-gate-validator.js +407 -0
  73. package/dist/hooks/save-checkpoint.d.ts +43 -0
  74. package/dist/hooks/save-checkpoint.js +144 -0
  75. package/dist/hooks/start-mcp-servers.d.ts +50 -0
  76. package/dist/hooks/start-mcp-servers.js +75 -0
  77. package/dist/hooks/stop-mcp-servers.d.ts +40 -0
  78. package/dist/hooks/stop-mcp-servers.js +58 -0
  79. package/dist/index.d.ts +29 -0
  80. package/dist/index.js +238 -0
  81. package/dist/lib/archive.d.ts +31 -0
  82. package/dist/lib/archive.js +226 -0
  83. package/dist/lib/config.d.ts +93 -0
  84. package/dist/lib/config.js +251 -0
  85. package/dist/lib/constants.d.ts +222 -0
  86. package/dist/lib/constants.js +247 -0
  87. package/dist/lib/interactive.d.ts +31 -0
  88. package/dist/lib/interactive.js +166 -0
  89. package/dist/lib/mcp-client.d.ts +156 -0
  90. package/dist/lib/mcp-client.js +370 -0
  91. package/dist/lib/state.d.ts +119 -0
  92. package/dist/lib/state.js +293 -0
  93. package/dist/slash/executor.d.ts +22 -0
  94. package/dist/slash/executor.js +259 -0
  95. package/dist/slash/index.d.ts +11 -0
  96. package/dist/slash/index.js +45 -0
  97. package/dist/slash/parser.d.ts +24 -0
  98. package/dist/slash/parser.js +101 -0
  99. package/dist/slash/registry.d.ts +22 -0
  100. package/dist/slash/registry.js +155 -0
  101. package/dist/spec/openspec-to-task/builders.d.ts +107 -0
  102. package/dist/spec/openspec-to-task/builders.js +138 -0
  103. package/dist/spec/openspec-to-task/index.d.ts +20 -0
  104. package/dist/spec/openspec-to-task/index.js +182 -0
  105. package/dist/spec/openspec-to-task/parsers.d.ts +65 -0
  106. package/dist/spec/openspec-to-task/parsers.js +232 -0
  107. package/dist/spec/openspec-to-task/types.d.ts +49 -0
  108. package/dist/spec/openspec-to-task/types.js +6 -0
  109. package/dist/spec/sync-openspec-to-task.d.ts +7 -0
  110. package/dist/spec/sync-openspec-to-task.js +21 -0
  111. package/dist/spec/sync-task-to-openspec.d.ts +27 -0
  112. package/dist/spec/sync-task-to-openspec.js +288 -0
  113. package/dist/types/ai-context.d.ts +108 -0
  114. package/dist/types/ai-context.js +9 -0
  115. package/package.json +66 -0
  116. package/references/AI-CONVERSATION-TUTORIAL.md +270 -0
  117. package/references/CLI-TUTORIAL.md +447 -0
  118. 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 };