@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,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* hooks/check-worktree-conflict.ts
|
|
4
|
+
* Check for worktree conflicts to prevent multi-agent file conflicts
|
|
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.normalizePath = normalizePath;
|
|
41
|
+
exports.checkWorktreeConflict = checkWorktreeConflict;
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const config_1 = require("../lib/config");
|
|
44
|
+
const state_1 = require("../lib/state");
|
|
45
|
+
function normalizePath(inputPath, projectRoot) {
|
|
46
|
+
if (!inputPath || typeof inputPath !== 'string') {
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
const absolutePath = path.isAbsolute(inputPath) ? inputPath : path.resolve(projectRoot, inputPath);
|
|
50
|
+
return path.relative(projectRoot, absolutePath).replace(/\\/g, '/');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check for worktree conflicts
|
|
54
|
+
* @param options - Options
|
|
55
|
+
* @returns Result
|
|
56
|
+
*/
|
|
57
|
+
function checkWorktreeConflict(options) {
|
|
58
|
+
const { input, config } = options;
|
|
59
|
+
const currentWorktree = process.env.WORKTREE_NAME;
|
|
60
|
+
const activeWorktrees = (0, state_1.getActiveWorktrees)(config);
|
|
61
|
+
if (activeWorktrees.length <= 1) {
|
|
62
|
+
return { conflict: false, reason: 'single-worktree' };
|
|
63
|
+
}
|
|
64
|
+
// Check if current operation involves file path
|
|
65
|
+
const targetFile = normalizePath(input.tool_input?.file_path || input.tool_input?.path, config.projectRoot);
|
|
66
|
+
if (!targetFile) {
|
|
67
|
+
return { conflict: false, reason: 'no-file-path' };
|
|
68
|
+
}
|
|
69
|
+
// Check if other active worktrees are modifying the same file
|
|
70
|
+
for (const wt of activeWorktrees) {
|
|
71
|
+
if (wt.name !== currentWorktree) {
|
|
72
|
+
const wtChanges = (wt.pendingChanges || [])
|
|
73
|
+
.map((p) => normalizePath(p, config.projectRoot))
|
|
74
|
+
.filter(Boolean);
|
|
75
|
+
if (wtChanges.includes(targetFile)) {
|
|
76
|
+
return {
|
|
77
|
+
conflict: true,
|
|
78
|
+
file: targetFile,
|
|
79
|
+
conflictingWorktree: wt.name,
|
|
80
|
+
message: `File ${targetFile} is being modified by ${wt.name}`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { conflict: false, reason: 'no-conflict' };
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Main entry point for hook usage
|
|
89
|
+
*/
|
|
90
|
+
function main() {
|
|
91
|
+
const input = JSON.parse(process.argv[2] || '{}');
|
|
92
|
+
const config = (0, config_1.loadConfig)();
|
|
93
|
+
(0, state_1.loadState)(config);
|
|
94
|
+
const result = checkWorktreeConflict({ input, config });
|
|
95
|
+
if (result.conflict) {
|
|
96
|
+
console.error(`[CONFLICT] ${result.message}`);
|
|
97
|
+
console.error(`[CONFLICT] Suggestion: Wait for ${result.conflictingWorktree} to complete or use a different file`);
|
|
98
|
+
process.exit(2); // Non-zero exit to block operation
|
|
99
|
+
}
|
|
100
|
+
process.exit(0);
|
|
101
|
+
}
|
|
102
|
+
if (require.main === module) {
|
|
103
|
+
main();
|
|
104
|
+
}
|
|
105
|
+
exports.default = {
|
|
106
|
+
checkWorktreeConflict,
|
|
107
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hooks/hook-runner/executor.ts
|
|
3
|
+
* Hook execution utilities
|
|
4
|
+
*/
|
|
5
|
+
import { Config } from '../../lib/config';
|
|
6
|
+
import { HookConfig, HookContext, HookResult } from './types';
|
|
7
|
+
/**
|
|
8
|
+
* Execute a hook module
|
|
9
|
+
*/
|
|
10
|
+
export declare function executeHookFunction(hookModule: Record<string, unknown> | null, context: HookContext): Promise<HookResult>;
|
|
11
|
+
/**
|
|
12
|
+
* Execute a single hook with timeout control
|
|
13
|
+
*/
|
|
14
|
+
export declare function executeSingleHookAsync(hookConfig: HookConfig, context: HookContext, config: Config): Promise<HookResult>;
|
|
15
|
+
/**
|
|
16
|
+
* Execute a single hook synchronously
|
|
17
|
+
*/
|
|
18
|
+
export declare function executeSingleHookSync(hookConfig: HookConfig, context: HookContext, config: Config): HookResult;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* hooks/hook-runner/executor.ts
|
|
4
|
+
* Hook execution utilities
|
|
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.executeHookFunction = executeHookFunction;
|
|
41
|
+
exports.executeSingleHookAsync = executeSingleHookAsync;
|
|
42
|
+
exports.executeSingleHookSync = executeSingleHookSync;
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const loader_1 = require("./loader");
|
|
46
|
+
/**
|
|
47
|
+
* Execute a hook module
|
|
48
|
+
*/
|
|
49
|
+
async function executeHookFunction(hookModule, context) {
|
|
50
|
+
if (!hookModule) {
|
|
51
|
+
return { success: false, error: 'hook-module-not-found' };
|
|
52
|
+
}
|
|
53
|
+
const hookFn = hookModule?.run
|
|
54
|
+
|| hookModule?.execute
|
|
55
|
+
|| hookModule?.default
|
|
56
|
+
|| hookModule;
|
|
57
|
+
if (typeof hookFn !== 'function') {
|
|
58
|
+
return { success: false, error: 'hook-function-not-found' };
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const result = hookFn(context);
|
|
62
|
+
if (result && typeof result.then === 'function') {
|
|
63
|
+
const asyncResult = await result;
|
|
64
|
+
return { success: true, result: asyncResult };
|
|
65
|
+
}
|
|
66
|
+
return { success: true, result };
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.error('[HOOK-RUNNER] Hook execution failed:', error instanceof Error ? error.message : 'Unknown error');
|
|
70
|
+
return { success: false, error: error instanceof Error ? error.message : 'Unknown error', stack: error instanceof Error ? error.stack : undefined };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Execute a single hook with timeout control
|
|
75
|
+
*/
|
|
76
|
+
async function executeSingleHookAsync(hookConfig, context, config) {
|
|
77
|
+
const { name, script, timeout = 30000 } = hookConfig;
|
|
78
|
+
console.log(`[HOOK-RUNNER] Executing hook: ${name} (${hookConfig.hookName || 'unknown'})`);
|
|
79
|
+
// Use dynamic import for async execution
|
|
80
|
+
const cfg = config || (await Promise.resolve().then(() => __importStar(require('../../lib/config')))).loadConfig();
|
|
81
|
+
const absolutePath = path.isAbsolute(script)
|
|
82
|
+
? script
|
|
83
|
+
: path.resolve(cfg.projectRoot, script);
|
|
84
|
+
if (!fs.existsSync(absolutePath)) {
|
|
85
|
+
return { name, success: false, error: 'script-not-found', script };
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const fileUrl = `file://${absolutePath}?t=${Date.now()}`;
|
|
89
|
+
const hookModule = await Promise.resolve(`${fileUrl}`).then(s => __importStar(require(s)));
|
|
90
|
+
const abortController = new AbortController();
|
|
91
|
+
const timeoutId = setTimeout(() => {
|
|
92
|
+
abortController.abort(new Error(`hook-timeout:${timeout}ms`));
|
|
93
|
+
}, timeout);
|
|
94
|
+
try {
|
|
95
|
+
const executePromise = executeHookFunction(hookModule.default || hookModule, context);
|
|
96
|
+
const result = await Promise.race([
|
|
97
|
+
executePromise,
|
|
98
|
+
new Promise((_, reject) => {
|
|
99
|
+
abortController.signal.addEventListener('abort', () => {
|
|
100
|
+
reject(abortController.signal.reason);
|
|
101
|
+
});
|
|
102
|
+
}),
|
|
103
|
+
]);
|
|
104
|
+
return { name, ...result, script };
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
clearTimeout(timeoutId);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
return { name, success: false, error: error instanceof Error ? error.message : 'Unknown error', script };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Execute a single hook synchronously
|
|
116
|
+
*/
|
|
117
|
+
function executeSingleHookSync(hookConfig, context, config) {
|
|
118
|
+
const { name, script } = hookConfig;
|
|
119
|
+
console.log(`[HOOK-RUNNER] Executing hook (sync): ${name} (${hookConfig.hookName || 'unknown'})`);
|
|
120
|
+
const hookModule = (0, loader_1.loadHookScript)(script, config);
|
|
121
|
+
if (!hookModule) {
|
|
122
|
+
return { name, success: false, error: 'script-not-found', script };
|
|
123
|
+
}
|
|
124
|
+
const hookFn = hookModule?.run
|
|
125
|
+
|| hookModule?.execute
|
|
126
|
+
|| hookModule?.default
|
|
127
|
+
|| hookModule;
|
|
128
|
+
if (typeof hookFn !== 'function') {
|
|
129
|
+
return { name, success: false, error: 'hook-function-not-found', script };
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const result = hookFn(context);
|
|
133
|
+
if (result && typeof result.then === 'function') {
|
|
134
|
+
console.warn('[HOOK-RUNNER] Async hook detected in sync execution:', name);
|
|
135
|
+
return { name, success: false, error: 'async-hook-in-sync-mode', script };
|
|
136
|
+
}
|
|
137
|
+
return { name, success: true, result, script };
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
console.error('[HOOK-RUNNER] Hook execution failed:', error instanceof Error ? error.message : 'Unknown error');
|
|
141
|
+
return { name, success: false, error: error instanceof Error ? error.message : 'Unknown error', script };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hooks/hook-runner/index.ts
|
|
3
|
+
* Unified hook execution engine for task-flow
|
|
4
|
+
*/
|
|
5
|
+
import { Config } from '../../lib/config';
|
|
6
|
+
import { HookLifecycle, HookConfig, HookContext, HookExecutionResult } from './types';
|
|
7
|
+
import { loadHooksConfig, loadHookScript, loadHookScriptAsync } from './loader';
|
|
8
|
+
import { executeHookFunction } from './executor';
|
|
9
|
+
/**
|
|
10
|
+
* Hook lifecycle definitions
|
|
11
|
+
*/
|
|
12
|
+
export declare const HOOK_LIFECYCLE: Record<string, HookLifecycle>;
|
|
13
|
+
/**
|
|
14
|
+
* Run a single hook by name
|
|
15
|
+
*/
|
|
16
|
+
export declare function runHook(hookName: string, context: HookContext): Promise<HookExecutionResult>;
|
|
17
|
+
/**
|
|
18
|
+
* Run a single hook by name (sync)
|
|
19
|
+
*/
|
|
20
|
+
export declare function runHookSync(hookName: string, context: HookContext): HookExecutionResult;
|
|
21
|
+
/**
|
|
22
|
+
* Run a chain of hooks
|
|
23
|
+
*/
|
|
24
|
+
export declare function runHookChain(hookNames: string[], context: HookContext): Promise<HookExecutionResult>;
|
|
25
|
+
/**
|
|
26
|
+
* Get hook lifecycle info
|
|
27
|
+
*/
|
|
28
|
+
export declare function getHookLifecycle(hookName: string): HookLifecycle | null;
|
|
29
|
+
/**
|
|
30
|
+
* Get hooks for a phase and timing
|
|
31
|
+
*/
|
|
32
|
+
export declare function getHooksForPhase(phase: string, timing: string, config?: Config): (HookConfig & {
|
|
33
|
+
hookName: string;
|
|
34
|
+
lifecycle: HookLifecycle;
|
|
35
|
+
})[];
|
|
36
|
+
/**
|
|
37
|
+
* Validate hook config
|
|
38
|
+
*/
|
|
39
|
+
export declare function validateHookConfig(hookConfig: HookConfig): {
|
|
40
|
+
valid: boolean;
|
|
41
|
+
errors: string[];
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Create hook config
|
|
45
|
+
*/
|
|
46
|
+
export declare function createHookConfig(name: string, script: string, options?: {
|
|
47
|
+
enabled?: boolean;
|
|
48
|
+
timeout?: number;
|
|
49
|
+
}): HookConfig;
|
|
50
|
+
declare const _default: {
|
|
51
|
+
runHook: typeof runHook;
|
|
52
|
+
runHookSync: typeof runHookSync;
|
|
53
|
+
runHookChain: typeof runHookChain;
|
|
54
|
+
loadHooksConfig: typeof loadHooksConfig;
|
|
55
|
+
loadHookScript: typeof loadHookScript;
|
|
56
|
+
loadHookScriptAsync: typeof loadHookScriptAsync;
|
|
57
|
+
executeHookFunction: typeof executeHookFunction;
|
|
58
|
+
getHookLifecycle: typeof getHookLifecycle;
|
|
59
|
+
getHooksForPhase: typeof getHooksForPhase;
|
|
60
|
+
validateHookConfig: typeof validateHookConfig;
|
|
61
|
+
createHookConfig: typeof createHookConfig;
|
|
62
|
+
HOOK_LIFECYCLE: Record<string, HookLifecycle>;
|
|
63
|
+
};
|
|
64
|
+
export default _default;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* hooks/hook-runner/index.ts
|
|
4
|
+
* Unified hook execution engine for task-flow
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.HOOK_LIFECYCLE = void 0;
|
|
8
|
+
exports.runHook = runHook;
|
|
9
|
+
exports.runHookSync = runHookSync;
|
|
10
|
+
exports.runHookChain = runHookChain;
|
|
11
|
+
exports.getHookLifecycle = getHookLifecycle;
|
|
12
|
+
exports.getHooksForPhase = getHooksForPhase;
|
|
13
|
+
exports.validateHookConfig = validateHookConfig;
|
|
14
|
+
exports.createHookConfig = createHookConfig;
|
|
15
|
+
const config_1 = require("../../lib/config");
|
|
16
|
+
const loader_1 = require("./loader");
|
|
17
|
+
const executor_1 = require("./executor");
|
|
18
|
+
/**
|
|
19
|
+
* Hook lifecycle definitions
|
|
20
|
+
*/
|
|
21
|
+
exports.HOOK_LIFECYCLE = {
|
|
22
|
+
'pre-design': { phase: 'design', timing: 'pre' },
|
|
23
|
+
'post-design': { phase: 'design', timing: 'post' },
|
|
24
|
+
'pre-planning': { phase: 'planning', timing: 'pre' },
|
|
25
|
+
'post-planning': { phase: 'planning', timing: 'post' },
|
|
26
|
+
'pre-implementation': { phase: 'implementation', timing: 'pre' },
|
|
27
|
+
'post-implementation': { phase: 'implementation', timing: 'post' },
|
|
28
|
+
'pre-review': { phase: 'review', timing: 'pre' },
|
|
29
|
+
'post-review': { phase: 'review', timing: 'post' },
|
|
30
|
+
'pre-merge': { phase: 'merge', timing: 'pre' },
|
|
31
|
+
'post-merge': { phase: 'merge', timing: 'post' },
|
|
32
|
+
'pre-command': { phase: 'command', timing: 'pre' },
|
|
33
|
+
'post-command': { phase: 'command', timing: 'post' },
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Run enabled hooks async
|
|
37
|
+
*/
|
|
38
|
+
async function runEnabledHooks(enabledHooks, context, config, hookName) {
|
|
39
|
+
const results = [];
|
|
40
|
+
let allSuccess = true;
|
|
41
|
+
for (const hookConfig of enabledHooks) {
|
|
42
|
+
const result = await (0, executor_1.executeSingleHookAsync)(hookConfig, context, config);
|
|
43
|
+
results.push(result);
|
|
44
|
+
if (!result.success) {
|
|
45
|
+
allSuccess = false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
success: allSuccess,
|
|
50
|
+
executed: results.length,
|
|
51
|
+
results,
|
|
52
|
+
hookName,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Run enabled hooks sync
|
|
57
|
+
*/
|
|
58
|
+
function runEnabledHooksSync(enabledHooks, context, config, hookName) {
|
|
59
|
+
const results = [];
|
|
60
|
+
let allSuccess = true;
|
|
61
|
+
for (const hookConfig of enabledHooks) {
|
|
62
|
+
const result = (0, executor_1.executeSingleHookSync)(hookConfig, context, config);
|
|
63
|
+
results.push(result);
|
|
64
|
+
if (!result.success) {
|
|
65
|
+
allSuccess = false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
success: allSuccess,
|
|
70
|
+
executed: results.length,
|
|
71
|
+
results,
|
|
72
|
+
hookName,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Run a single hook by name
|
|
77
|
+
*/
|
|
78
|
+
async function runHook(hookName, context) {
|
|
79
|
+
const config = context?.config || (0, config_1.loadConfig)();
|
|
80
|
+
const hooksConfig = (0, loader_1.loadHooksConfig)(config);
|
|
81
|
+
if (!hooksConfig.hooks || !hooksConfig.hooks[hookName]) {
|
|
82
|
+
return {
|
|
83
|
+
success: true,
|
|
84
|
+
executed: 0,
|
|
85
|
+
results: [],
|
|
86
|
+
message: `no-hooks-configured:${hookName}`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const hooks = hooksConfig.hooks[hookName];
|
|
90
|
+
const enabledHooks = hooks.filter((h) => h.enabled !== false);
|
|
91
|
+
if (enabledHooks.length === 0) {
|
|
92
|
+
return {
|
|
93
|
+
success: true,
|
|
94
|
+
executed: 0,
|
|
95
|
+
results: [],
|
|
96
|
+
message: `no-enabled-hooks:${hookName}`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return runEnabledHooks(enabledHooks, context, config, hookName);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Run a single hook by name (sync)
|
|
103
|
+
*/
|
|
104
|
+
function runHookSync(hookName, context) {
|
|
105
|
+
const config = context?.config || (0, config_1.loadConfig)();
|
|
106
|
+
const hooksConfig = (0, loader_1.loadHooksConfig)(config);
|
|
107
|
+
if (!hooksConfig.hooks || !hooksConfig.hooks[hookName]) {
|
|
108
|
+
return {
|
|
109
|
+
success: true,
|
|
110
|
+
executed: 0,
|
|
111
|
+
results: [],
|
|
112
|
+
message: `no-hooks-configured:${hookName}`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const hooks = hooksConfig.hooks[hookName];
|
|
116
|
+
const enabledHooks = hooks.filter((h) => h.enabled !== false);
|
|
117
|
+
if (enabledHooks.length === 0) {
|
|
118
|
+
return {
|
|
119
|
+
success: true,
|
|
120
|
+
executed: 0,
|
|
121
|
+
results: [],
|
|
122
|
+
message: `no-enabled-hooks:${hookName}`,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
return runEnabledHooksSync(enabledHooks, context, config, hookName);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Run a chain of hooks
|
|
129
|
+
*/
|
|
130
|
+
async function runHookChain(hookNames, context) {
|
|
131
|
+
const results = [];
|
|
132
|
+
let allSuccess = true;
|
|
133
|
+
for (const hookName of hookNames) {
|
|
134
|
+
const result = await runHook(hookName, context);
|
|
135
|
+
results.push(result);
|
|
136
|
+
if (!result.success) {
|
|
137
|
+
allSuccess = false;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
success: allSuccess,
|
|
143
|
+
executed: results.length,
|
|
144
|
+
results: results.flatMap((r) => r.results),
|
|
145
|
+
hookNames,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get hook lifecycle info
|
|
150
|
+
*/
|
|
151
|
+
function getHookLifecycle(hookName) {
|
|
152
|
+
return exports.HOOK_LIFECYCLE[hookName] || null;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get hooks for a phase and timing
|
|
156
|
+
*/
|
|
157
|
+
function getHooksForPhase(phase, timing, config) {
|
|
158
|
+
const cfg = config || (0, config_1.loadConfig)();
|
|
159
|
+
const hooksConfig = (0, loader_1.loadHooksConfig)(cfg);
|
|
160
|
+
if (!hooksConfig.hooks) {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
const matchingHooks = [];
|
|
164
|
+
for (const [hookName, lifecycle] of Object.entries(exports.HOOK_LIFECYCLE)) {
|
|
165
|
+
if (lifecycle.phase === phase && lifecycle.timing === timing) {
|
|
166
|
+
const hooks = hooksConfig.hooks[hookName] || [];
|
|
167
|
+
matchingHooks.push(...hooks.map((h) => ({
|
|
168
|
+
...h,
|
|
169
|
+
hookName,
|
|
170
|
+
lifecycle,
|
|
171
|
+
})));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return matchingHooks;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Validate hook config
|
|
178
|
+
*/
|
|
179
|
+
function validateHookConfig(hookConfig) {
|
|
180
|
+
const errors = [];
|
|
181
|
+
if (!hookConfig.name || typeof hookConfig.name !== 'string') {
|
|
182
|
+
errors.push('hook-must-have-name');
|
|
183
|
+
}
|
|
184
|
+
if (!hookConfig.script || typeof hookConfig.script !== 'string') {
|
|
185
|
+
errors.push('hook-must-have-script');
|
|
186
|
+
}
|
|
187
|
+
if (hookConfig.timeout !== undefined && typeof hookConfig.timeout !== 'number') {
|
|
188
|
+
errors.push('hook-timeout-must-be-number');
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
valid: errors.length === 0,
|
|
192
|
+
errors,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Create hook config
|
|
197
|
+
*/
|
|
198
|
+
function createHookConfig(name, script, options = {}) {
|
|
199
|
+
return {
|
|
200
|
+
name,
|
|
201
|
+
script,
|
|
202
|
+
enabled: options.enabled !== false,
|
|
203
|
+
timeout: options.timeout || 30000,
|
|
204
|
+
...options,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
exports.default = {
|
|
208
|
+
runHook,
|
|
209
|
+
runHookSync,
|
|
210
|
+
runHookChain,
|
|
211
|
+
loadHooksConfig: loader_1.loadHooksConfig,
|
|
212
|
+
loadHookScript: loader_1.loadHookScript,
|
|
213
|
+
loadHookScriptAsync: loader_1.loadHookScriptAsync,
|
|
214
|
+
executeHookFunction: executor_1.executeHookFunction,
|
|
215
|
+
getHookLifecycle,
|
|
216
|
+
getHooksForPhase,
|
|
217
|
+
validateHookConfig,
|
|
218
|
+
createHookConfig,
|
|
219
|
+
HOOK_LIFECYCLE: exports.HOOK_LIFECYCLE,
|
|
220
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hooks/hook-runner/loader.ts
|
|
3
|
+
* Hook loading utilities
|
|
4
|
+
*/
|
|
5
|
+
import { Config } from '../../lib/config';
|
|
6
|
+
import { LoadedHooksConfig } from './types';
|
|
7
|
+
/**
|
|
8
|
+
* Get default hooks path
|
|
9
|
+
*/
|
|
10
|
+
export declare function getDefaultHooksPath(config: Config): string;
|
|
11
|
+
/**
|
|
12
|
+
* Load hooks config from file
|
|
13
|
+
*/
|
|
14
|
+
export declare function loadHooksConfig(config?: Config): LoadedHooksConfig;
|
|
15
|
+
/**
|
|
16
|
+
* Load hook script with dynamic import to avoid require.cache issues
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadHookScriptAsync(scriptPath: string, config?: Config): Promise<Record<string, unknown> | null>;
|
|
19
|
+
/**
|
|
20
|
+
* Load hook script (legacy sync version)
|
|
21
|
+
* @deprecated Use loadHookScriptAsync instead
|
|
22
|
+
*/
|
|
23
|
+
export declare function loadHookScript(scriptPath: string, config?: Config): Record<string, unknown> | null;
|