@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,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* hooks/start-mcp-servers.ts
|
|
4
|
+
* Hook for starting MCP servers with autoStart enabled
|
|
5
|
+
*
|
|
6
|
+
* This hook:
|
|
7
|
+
* 1. Gets MCP server configurations from context
|
|
8
|
+
* 2. Starts servers with lifecycle.autoStart = true
|
|
9
|
+
* 3. Returns connection information for downstream use
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.run = run;
|
|
13
|
+
const mcp_client_1 = require("../lib/mcp-client");
|
|
14
|
+
/**
|
|
15
|
+
* Start MCP servers hook
|
|
16
|
+
* @param context - Hook execution context
|
|
17
|
+
* @returns Hook execution result
|
|
18
|
+
*/
|
|
19
|
+
function run(context) {
|
|
20
|
+
const { config, tools } = context;
|
|
21
|
+
if (!config || !config.projectRoot) {
|
|
22
|
+
return {
|
|
23
|
+
check: 'mcp-started',
|
|
24
|
+
success: false,
|
|
25
|
+
error: 'missing-config',
|
|
26
|
+
reason: 'Project root not found in context',
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// Get MCP servers from tools (passed by previous hook)
|
|
30
|
+
const mcpServers = tools?.mcp || [];
|
|
31
|
+
if (mcpServers.length === 0) {
|
|
32
|
+
return {
|
|
33
|
+
check: 'mcp-started',
|
|
34
|
+
success: true,
|
|
35
|
+
message: 'No MCP servers configured',
|
|
36
|
+
started: [],
|
|
37
|
+
connectionInfo: [],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
console.log('[START-MCP] Starting MCP servers...');
|
|
42
|
+
// Start MCP servers with autoStart enabled
|
|
43
|
+
const result = (0, mcp_client_1.startMcpServers)(mcpServers, config.projectRoot);
|
|
44
|
+
// Get connection information
|
|
45
|
+
const connectionInfo = (0, mcp_client_1.getMcpConnectionInfo)();
|
|
46
|
+
const startedCount = result.results.filter(r => r.status === 'started').length;
|
|
47
|
+
const skippedCount = result.results.filter(r => r.status === 'skipped').length;
|
|
48
|
+
console.log(`[START-MCP] Started ${startedCount} servers, ${skippedCount} skipped (autoStart=false)`);
|
|
49
|
+
return {
|
|
50
|
+
check: 'mcp-started',
|
|
51
|
+
success: result.success,
|
|
52
|
+
result,
|
|
53
|
+
connectionInfo,
|
|
54
|
+
summary: {
|
|
55
|
+
totalCount: mcpServers.length,
|
|
56
|
+
startedCount,
|
|
57
|
+
skippedCount,
|
|
58
|
+
failedCount: result.results.filter(r => r.status === 'error').length,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
console.error('[START-MCP] Failed to start MCP servers:', error.message);
|
|
64
|
+
return {
|
|
65
|
+
check: 'mcp-started',
|
|
66
|
+
success: false,
|
|
67
|
+
error: error.message,
|
|
68
|
+
stack: error.stack,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.default = {
|
|
73
|
+
run,
|
|
74
|
+
name: 'start-mcp-servers',
|
|
75
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hooks/stop-mcp-servers.ts
|
|
3
|
+
* Hook for stopping all MCP servers
|
|
4
|
+
*
|
|
5
|
+
* This hook:
|
|
6
|
+
* 1. Stops all running MCP servers
|
|
7
|
+
* 2. Should be called in post-merge or cleanup phase
|
|
8
|
+
*/
|
|
9
|
+
import { StopResult } from '../lib/mcp-client';
|
|
10
|
+
export interface StopMcpContext {
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
export interface StopMcpResult {
|
|
14
|
+
check: 'mcp-stopped';
|
|
15
|
+
success: boolean;
|
|
16
|
+
error?: string;
|
|
17
|
+
reason?: string;
|
|
18
|
+
message?: string;
|
|
19
|
+
result?: {
|
|
20
|
+
success: boolean;
|
|
21
|
+
results: StopResult[];
|
|
22
|
+
};
|
|
23
|
+
stopped?: StopResult[];
|
|
24
|
+
summary?: {
|
|
25
|
+
stoppedCount: number;
|
|
26
|
+
errorCount: number;
|
|
27
|
+
};
|
|
28
|
+
stack?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Stop MCP servers hook
|
|
32
|
+
* @param _context - Hook execution context
|
|
33
|
+
* @returns Hook execution result
|
|
34
|
+
*/
|
|
35
|
+
export declare function run(_context: StopMcpContext): StopMcpResult;
|
|
36
|
+
declare const _default: {
|
|
37
|
+
run: typeof run;
|
|
38
|
+
name: string;
|
|
39
|
+
};
|
|
40
|
+
export default _default;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* hooks/stop-mcp-servers.ts
|
|
4
|
+
* Hook for stopping all MCP servers
|
|
5
|
+
*
|
|
6
|
+
* This hook:
|
|
7
|
+
* 1. Stops all running MCP servers
|
|
8
|
+
* 2. Should be called in post-merge or cleanup phase
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.run = run;
|
|
12
|
+
const mcp_client_1 = require("../lib/mcp-client");
|
|
13
|
+
/**
|
|
14
|
+
* Stop MCP servers hook
|
|
15
|
+
* @param _context - Hook execution context
|
|
16
|
+
* @returns Hook execution result
|
|
17
|
+
*/
|
|
18
|
+
function run(_context) {
|
|
19
|
+
const runningCount = (0, mcp_client_1.getRunningCount)();
|
|
20
|
+
if (runningCount === 0) {
|
|
21
|
+
return {
|
|
22
|
+
check: 'mcp-stopped',
|
|
23
|
+
success: true,
|
|
24
|
+
message: 'No MCP servers running',
|
|
25
|
+
stopped: [],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
console.log(`[STOP-MCP] Stopping ${runningCount} MCP server(s)...`);
|
|
30
|
+
// Stop all MCP servers
|
|
31
|
+
const result = (0, mcp_client_1.stopAllMcpServers)();
|
|
32
|
+
const stoppedCount = result.results.filter(r => r.status !== 'error').length;
|
|
33
|
+
const errorCount = result.results.filter(r => r.status === 'error').length;
|
|
34
|
+
console.log(`[STOP-MCP] Stopped ${stoppedCount} servers, ${errorCount} errors`);
|
|
35
|
+
return {
|
|
36
|
+
check: 'mcp-stopped',
|
|
37
|
+
success: result.success,
|
|
38
|
+
result,
|
|
39
|
+
summary: {
|
|
40
|
+
stoppedCount,
|
|
41
|
+
errorCount,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error('[STOP-MCP] Failed to stop MCP servers:', error.message);
|
|
47
|
+
return {
|
|
48
|
+
check: 'mcp-stopped',
|
|
49
|
+
success: false,
|
|
50
|
+
error: error.message,
|
|
51
|
+
stack: error.stack,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.default = {
|
|
56
|
+
run,
|
|
57
|
+
name: 'stop-mcp-servers',
|
|
58
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* index.ts
|
|
4
|
+
* CLI entry point for @bicorne/task-flow
|
|
5
|
+
*
|
|
6
|
+
* Supports both traditional CLI commands and slash commands:
|
|
7
|
+
* task-flow extract --change my-feature
|
|
8
|
+
* /tf:propose my-feature (in AI chat)
|
|
9
|
+
*/
|
|
10
|
+
declare function printHelp(): void;
|
|
11
|
+
declare function printVersion(): void;
|
|
12
|
+
declare function parseArgs(argv: string[]): {
|
|
13
|
+
command: string | null;
|
|
14
|
+
options: Record<string, unknown>;
|
|
15
|
+
help?: boolean;
|
|
16
|
+
version?: boolean;
|
|
17
|
+
};
|
|
18
|
+
declare function runCommand(command: string, options: Record<string, unknown>): void;
|
|
19
|
+
declare function handleSlashCommand(input: string): Promise<void>;
|
|
20
|
+
declare function main(): Promise<void>;
|
|
21
|
+
declare const _default: {
|
|
22
|
+
main: typeof main;
|
|
23
|
+
printHelp: typeof printHelp;
|
|
24
|
+
printVersion: typeof printVersion;
|
|
25
|
+
parseArgs: typeof parseArgs;
|
|
26
|
+
runCommand: typeof runCommand;
|
|
27
|
+
handleSlashCommand: typeof handleSlashCommand;
|
|
28
|
+
};
|
|
29
|
+
export default _default;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* index.ts
|
|
5
|
+
* CLI entry point for @bicorne/task-flow
|
|
6
|
+
*
|
|
7
|
+
* Supports both traditional CLI commands and slash commands:
|
|
8
|
+
* task-flow extract --change my-feature
|
|
9
|
+
* /tf:propose my-feature (in AI chat)
|
|
10
|
+
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const slash_1 = require("./slash");
|
|
47
|
+
function printHelp() {
|
|
48
|
+
console.log(`
|
|
49
|
+
task-flow - Git worktree-based parallel task execution system
|
|
50
|
+
|
|
51
|
+
Usage:
|
|
52
|
+
task-flow <command> [options]
|
|
53
|
+
/tf:<command> [args] (in AI chat)
|
|
54
|
+
task-flow /tf:<command> [args] (via CLI)
|
|
55
|
+
|
|
56
|
+
Commands:
|
|
57
|
+
init Initialize task flow in current project
|
|
58
|
+
analyze Scan project and generate AI context JSON
|
|
59
|
+
extract Extract project requirements and generate PRD document
|
|
60
|
+
design Generate design.md from product-requirement.md
|
|
61
|
+
tasks Generate PHASE-*.json files from PRD and design docs
|
|
62
|
+
worktree Create isolated git worktree for task execution
|
|
63
|
+
start Start a task from spec change (legacy)
|
|
64
|
+
status Show current task and phase status
|
|
65
|
+
archive Archive current task state
|
|
66
|
+
merge Merge completed worktree back to main branch
|
|
67
|
+
sync Sync task execution status to spec
|
|
68
|
+
help Show this help message
|
|
69
|
+
${(0, slash_1.printSlashHelp)()}
|
|
70
|
+
Tasks Command Options:
|
|
71
|
+
--input <json> Provide tasks JSON directly (AI input)
|
|
72
|
+
--yes, -y Skip interactive confirmation
|
|
73
|
+
--type <type> Task type (feat|fix|refactor|test|docs)
|
|
74
|
+
--priority <level> Task priority (high|medium|low)
|
|
75
|
+
|
|
76
|
+
Tasks Command Input Sources (priority order):
|
|
77
|
+
1. --input parameter: Provide JSON array of tasks
|
|
78
|
+
2. stdin: Pipe JSON through standard input
|
|
79
|
+
3. Auto-extraction: Parse product-requirement.md and design.md
|
|
80
|
+
4. tasks.md: Parse from markdown file (fallback)
|
|
81
|
+
|
|
82
|
+
Options:
|
|
83
|
+
--help, -h Show help for command
|
|
84
|
+
--version, -v Show version
|
|
85
|
+
--force Force overwrite existing files
|
|
86
|
+
--yes, -y Skip interactive confirmation (non-interactive mode)
|
|
87
|
+
|
|
88
|
+
Examples:
|
|
89
|
+
/tf:propose add-dark-mode (in AI chat)
|
|
90
|
+
/tf:apply add-dark-mode (in AI chat)
|
|
91
|
+
/tf:archive --task-id add-dark-mode (in AI chat)
|
|
92
|
+
task-flow init
|
|
93
|
+
task-flow analyze --output context.json
|
|
94
|
+
task-flow extract --change my-feature --context-file context.json
|
|
95
|
+
task-flow design --change my-feature --context-file context.json
|
|
96
|
+
task-flow tasks --change my-feature --yes
|
|
97
|
+
task-flow worktree --change my-feature --yes
|
|
98
|
+
task-flow status
|
|
99
|
+
task-flow merge --dry-run
|
|
100
|
+
|
|
101
|
+
Environment Variables:
|
|
102
|
+
HARNESS_ROOT Root directory for harness state (default: .harness)
|
|
103
|
+
HARNESS_CONFIG_FILE Path to config file (default: .harnessrc)
|
|
104
|
+
|
|
105
|
+
Documentation:
|
|
106
|
+
https://github.com/didengren/harness-task-flow
|
|
107
|
+
`);
|
|
108
|
+
}
|
|
109
|
+
function printVersion() {
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
111
|
+
const pkg = require('../package.json');
|
|
112
|
+
console.log(`@bicorne/task-flow v${pkg.version}`);
|
|
113
|
+
}
|
|
114
|
+
function parseArgs(argv) {
|
|
115
|
+
const args = {
|
|
116
|
+
command: null,
|
|
117
|
+
options: {},
|
|
118
|
+
help: false,
|
|
119
|
+
version: false,
|
|
120
|
+
};
|
|
121
|
+
for (let i = 2; i < argv.length; i += 1) {
|
|
122
|
+
const token = argv[i] || '';
|
|
123
|
+
if (token.startsWith('--')) {
|
|
124
|
+
const key = token.slice(2);
|
|
125
|
+
if (key === 'help' || key === 'h') {
|
|
126
|
+
args.help = true;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (key === 'version' || key === 'v') {
|
|
130
|
+
args.version = true;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const next = argv[i + 1];
|
|
134
|
+
if (!next || next.startsWith('--')) {
|
|
135
|
+
args.options[key] = true;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
args.options[key] = next;
|
|
139
|
+
i += 1;
|
|
140
|
+
}
|
|
141
|
+
else if (token.startsWith('-') && token.length === 2) {
|
|
142
|
+
const shortOpt = token.slice(1);
|
|
143
|
+
if (shortOpt === 'h') {
|
|
144
|
+
args.help = true;
|
|
145
|
+
}
|
|
146
|
+
else if (shortOpt === 'v') {
|
|
147
|
+
args.version = true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else if (!args.command) {
|
|
151
|
+
args.command = token || null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return args;
|
|
155
|
+
}
|
|
156
|
+
function runCommand(command, options) {
|
|
157
|
+
const commandPath = path.resolve(__dirname, 'commands', `${command}.js`);
|
|
158
|
+
try {
|
|
159
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
160
|
+
const commandModule = require(commandPath);
|
|
161
|
+
if (typeof commandModule === 'function') {
|
|
162
|
+
commandModule(options);
|
|
163
|
+
}
|
|
164
|
+
else if (commandModule.main && typeof commandModule.main === 'function') {
|
|
165
|
+
commandModule.main();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
if (error.code === 'MODULE_NOT_FOUND') {
|
|
170
|
+
console.error(`[ERROR] Unknown command: ${command}`);
|
|
171
|
+
console.error('Run "task-flow help" for available commands.');
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
console.error(`[ERROR] Command failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
async function handleSlashCommand(input) {
|
|
179
|
+
const result = await (0, slash_1.runSlashCommand)(input);
|
|
180
|
+
if (result.success) {
|
|
181
|
+
console.log('');
|
|
182
|
+
console.log(`[SLASH] ✓ /tf:${result.command} completed`);
|
|
183
|
+
if (result.steps) {
|
|
184
|
+
for (const step of result.steps) {
|
|
185
|
+
const icon = step.success ? '✓' : '✗';
|
|
186
|
+
console.log(` ${icon} ${step.step}: ${step.message || (step.success ? 'OK' : 'Failed')}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
console.log('');
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
console.error('');
|
|
193
|
+
console.error(`[SLASH] ✗ /tf:${result.command} failed`);
|
|
194
|
+
if (result.error) {
|
|
195
|
+
console.error(` Error: ${result.error}`);
|
|
196
|
+
}
|
|
197
|
+
if (result.steps) {
|
|
198
|
+
for (const step of result.steps) {
|
|
199
|
+
if (!step.success) {
|
|
200
|
+
console.error(` ✗ ${step.step}: ${step.message || 'Failed'}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
console.error('');
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async function main() {
|
|
209
|
+
const rawCommand = process.argv[2] || '';
|
|
210
|
+
if ((0, slash_1.isSlashCommand)(rawCommand)) {
|
|
211
|
+
const fullInput = process.argv.slice(2).join(' ');
|
|
212
|
+
await handleSlashCommand(fullInput);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const args = parseArgs(process.argv);
|
|
216
|
+
if (args.version) {
|
|
217
|
+
printVersion();
|
|
218
|
+
process.exit(0);
|
|
219
|
+
}
|
|
220
|
+
if (args.help || !args.command) {
|
|
221
|
+
printHelp();
|
|
222
|
+
process.exit(0);
|
|
223
|
+
}
|
|
224
|
+
if (args.command === 'help') {
|
|
225
|
+
printHelp();
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
228
|
+
runCommand(args.command, args.options);
|
|
229
|
+
}
|
|
230
|
+
main();
|
|
231
|
+
exports.default = {
|
|
232
|
+
main,
|
|
233
|
+
printHelp,
|
|
234
|
+
printVersion,
|
|
235
|
+
parseArgs,
|
|
236
|
+
runCommand,
|
|
237
|
+
handleSlashCommand,
|
|
238
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* archive.ts
|
|
3
|
+
* Archive task state to persistent storage
|
|
4
|
+
*/
|
|
5
|
+
export interface ArchiveOptions {
|
|
6
|
+
cwd?: string;
|
|
7
|
+
taskId?: string;
|
|
8
|
+
eventType?: string;
|
|
9
|
+
status?: string;
|
|
10
|
+
phase?: string;
|
|
11
|
+
phaseCompleted?: string;
|
|
12
|
+
reviewConclusion?: string;
|
|
13
|
+
reason?: string;
|
|
14
|
+
completedAt?: string;
|
|
15
|
+
at?: string;
|
|
16
|
+
updateState?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface ArchiveResult {
|
|
19
|
+
skipped: boolean;
|
|
20
|
+
reason?: string;
|
|
21
|
+
taskId?: string;
|
|
22
|
+
archivePath?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Archive task state
|
|
26
|
+
*/
|
|
27
|
+
export declare function archiveTaskState(options?: ArchiveOptions): ArchiveResult;
|
|
28
|
+
declare const _default: {
|
|
29
|
+
archiveTaskState: typeof archiveTaskState;
|
|
30
|
+
};
|
|
31
|
+
export default _default;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* archive.ts
|
|
4
|
+
* Archive task state to persistent storage
|
|
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.archiveTaskState = archiveTaskState;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const config_1 = require("./config");
|
|
44
|
+
const state_1 = require("./state");
|
|
45
|
+
/**
|
|
46
|
+
* Ensure path uses POSIX separators
|
|
47
|
+
*/
|
|
48
|
+
function ensurePosix(input) {
|
|
49
|
+
return String(input || '').replace(/\\/g, '/');
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Read JSON file if exists
|
|
53
|
+
*/
|
|
54
|
+
function readJsonIfExists(filePath) {
|
|
55
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Get task file path
|
|
67
|
+
*/
|
|
68
|
+
function getTaskFilePath(config, taskId) {
|
|
69
|
+
return path.resolve(config.tasksDirAbs, `${taskId}.json`);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Read task JSON file
|
|
73
|
+
*/
|
|
74
|
+
function readTaskJson(config, taskId) {
|
|
75
|
+
const taskPath = getTaskFilePath(config, taskId);
|
|
76
|
+
return readJsonIfExists(taskPath);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get task summary from JSON
|
|
80
|
+
*/
|
|
81
|
+
function getTaskSummary(taskJson, taskId) {
|
|
82
|
+
return {
|
|
83
|
+
taskId,
|
|
84
|
+
title: taskJson?.title || '',
|
|
85
|
+
type: taskJson?.type || '',
|
|
86
|
+
priority: taskJson?.priority || 'medium',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get Spec metadata from task JSON
|
|
91
|
+
*/
|
|
92
|
+
function getMetadataSpec(taskJson) {
|
|
93
|
+
if (!taskJson?.metadata?.change) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
change: taskJson.metadata.change,
|
|
98
|
+
proposal: taskJson.docs_to_read?.[0] || null,
|
|
99
|
+
design: taskJson.docs_to_read?.[1] || null,
|
|
100
|
+
tasks: taskJson.docs_to_read?.[2] || null,
|
|
101
|
+
manifest: taskJson.metadata?.manifest || null,
|
|
102
|
+
execution: taskJson.metadata?.execution || null,
|
|
103
|
+
version: 'v1',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Get artifacts paths
|
|
108
|
+
*/
|
|
109
|
+
function getArtifacts(config, taskId, taskJson, spec) {
|
|
110
|
+
return {
|
|
111
|
+
task: ensurePosix(path.relative(config.projectRoot, path.resolve(config.tasksDirAbs, `${taskId}.json`))),
|
|
112
|
+
implementationSnapshot: taskJson?.metadata?.outputs?.snapshot || null,
|
|
113
|
+
review: taskJson?.metadata?.outputs?.report || null,
|
|
114
|
+
execution: spec?.execution || null,
|
|
115
|
+
archive: ensurePosix(path.relative(config.projectRoot, path.resolve(config.archiveDirAbs, `${taskId}.json`))),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Build event object
|
|
120
|
+
*/
|
|
121
|
+
function buildEvent(options, state) {
|
|
122
|
+
return {
|
|
123
|
+
type: options.eventType || 'state_synced',
|
|
124
|
+
at: options.at || new Date().toISOString(),
|
|
125
|
+
status: options.status || state?.status || null,
|
|
126
|
+
phase: options.phase || state?.currentPhase || null,
|
|
127
|
+
phaseCompleted: options.phaseCompleted || state?.phaseCompleted || null,
|
|
128
|
+
reviewConclusion: options.reviewConclusion || state?.reviewConclusion || null,
|
|
129
|
+
reason: options.reason || state?.lastGateBlockedReason || null,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Check if two events are the same
|
|
134
|
+
*/
|
|
135
|
+
function isSameEvent(left, right) {
|
|
136
|
+
if (!left || !right) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
return left.type === right.type
|
|
140
|
+
&& left.status === right.status
|
|
141
|
+
&& left.phase === right.phase
|
|
142
|
+
&& left.phaseCompleted === right.phaseCompleted
|
|
143
|
+
&& left.reviewConclusion === right.reviewConclusion
|
|
144
|
+
&& left.reason === right.reason;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Archive task state
|
|
148
|
+
*/
|
|
149
|
+
function archiveTaskState(options = {}) {
|
|
150
|
+
const config = (0, config_1.loadConfig)({ cwd: options.cwd });
|
|
151
|
+
const state = (0, state_1.loadState)(config);
|
|
152
|
+
const taskId = options.taskId || state.currentTask;
|
|
153
|
+
if (!taskId) {
|
|
154
|
+
return { skipped: true, reason: 'missing-task-id' };
|
|
155
|
+
}
|
|
156
|
+
const taskJson = readTaskJson(config, taskId);
|
|
157
|
+
if (!taskJson) {
|
|
158
|
+
return { skipped: true, reason: 'missing-task-file' };
|
|
159
|
+
}
|
|
160
|
+
const archiveDir = config.archiveDirAbs;
|
|
161
|
+
const archivePath = path.resolve(archiveDir, `${taskId}.json`);
|
|
162
|
+
const archiveRelativePath = ensurePosix(path.relative(config.projectRoot, archivePath));
|
|
163
|
+
const previous = readJsonIfExists(archivePath) || {};
|
|
164
|
+
const taskSummary = getTaskSummary(taskJson, taskId);
|
|
165
|
+
const spec = getMetadataSpec(taskJson);
|
|
166
|
+
const now = options.at || new Date().toISOString();
|
|
167
|
+
const lifecycleStatus = options.status || state.status || previous.lifecycle?.status || 'initialized';
|
|
168
|
+
const lifecycle = {
|
|
169
|
+
createdAt: previous.lifecycle?.createdAt || state.startedAt || now,
|
|
170
|
+
startedAt: previous.lifecycle?.startedAt || state.startedAt || now,
|
|
171
|
+
completedAt: lifecycleStatus === 'completed' ? (options.completedAt || now) : (previous.lifecycle?.completedAt || null),
|
|
172
|
+
lastUpdatedAt: now,
|
|
173
|
+
status: lifecycleStatus,
|
|
174
|
+
currentPhase: options.phase || state.currentPhase || null,
|
|
175
|
+
phaseCompleted: options.phaseCompleted || state.phaseCompleted || null,
|
|
176
|
+
reviewConclusion: options.reviewConclusion || state.reviewConclusion || null,
|
|
177
|
+
};
|
|
178
|
+
const archiveDoc = {
|
|
179
|
+
taskId,
|
|
180
|
+
title: taskSummary.title,
|
|
181
|
+
type: taskSummary.type,
|
|
182
|
+
priority: taskSummary.priority,
|
|
183
|
+
spec,
|
|
184
|
+
lifecycle,
|
|
185
|
+
artifacts: getArtifacts(config, taskId, taskJson, spec),
|
|
186
|
+
mergeStrategy: taskJson?.metadata?.mergeStrategy || { type: 'squash', deleteBranch: true, deleteWorktree: true },
|
|
187
|
+
worktrees: Array.isArray(state.worktrees) ? state.worktrees.filter((item) => item?.task === taskId) : [],
|
|
188
|
+
lastGate: {
|
|
189
|
+
blockedAt: state.lastGateBlockedAt || null,
|
|
190
|
+
reason: options.reason || state.lastGateBlockedReason || null,
|
|
191
|
+
phase: state.lastGateBlockedPhase || null,
|
|
192
|
+
missingValidations: Array.isArray(state.lastMissingValidations) ? state.lastMissingValidations : [],
|
|
193
|
+
},
|
|
194
|
+
taskPhases: {
|
|
195
|
+
planning: taskJson?.status || 'pending',
|
|
196
|
+
implementation: taskJson?.status || 'pending',
|
|
197
|
+
review: taskJson?.status || 'pending',
|
|
198
|
+
},
|
|
199
|
+
events: Array.isArray(previous.events) ? previous.events : [],
|
|
200
|
+
};
|
|
201
|
+
const nextEvent = buildEvent(options, state);
|
|
202
|
+
if (!isSameEvent(archiveDoc.events[archiveDoc.events.length - 1], nextEvent)) {
|
|
203
|
+
archiveDoc.events.push(nextEvent);
|
|
204
|
+
}
|
|
205
|
+
fs.mkdirSync(archiveDir, { recursive: true });
|
|
206
|
+
fs.writeFileSync(archivePath, JSON.stringify(archiveDoc, null, 2), 'utf8');
|
|
207
|
+
if (options.updateState !== false) {
|
|
208
|
+
(0, state_1.addTaskToHistory)({
|
|
209
|
+
taskId,
|
|
210
|
+
title: taskSummary.title,
|
|
211
|
+
status: lifecycleStatus,
|
|
212
|
+
currentPhase: options.phase || state.currentPhase,
|
|
213
|
+
phaseCompleted: options.phaseCompleted || state.phaseCompleted,
|
|
214
|
+
reviewConclusion: options.reviewConclusion || state.reviewConclusion,
|
|
215
|
+
archivePath: archiveRelativePath,
|
|
216
|
+
}, config);
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
skipped: false,
|
|
220
|
+
taskId,
|
|
221
|
+
archivePath: archiveRelativePath,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
exports.default = {
|
|
225
|
+
archiveTaskState,
|
|
226
|
+
};
|