@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,407 @@
1
+ "use strict";
2
+ /**
3
+ * hooks/phase-gate-validator.ts
4
+ * Validate phase transition gates
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.normalizeValidationName = normalizeValidationName;
42
+ exports.getValidationAliases = getValidationAliases;
43
+ exports.isPassedStatus = isPassedStatus;
44
+ exports.parseReviewConclusion = parseReviewConclusion;
45
+ exports.parseReviewValidationMap = parseReviewValidationMap;
46
+ exports.parseReviewReport = parseReviewReport;
47
+ exports.deriveTransitionFromFilePath = deriveTransitionFromFilePath;
48
+ exports.getTaskFilePath = getTaskFilePath;
49
+ exports.readTaskJson = readTaskJson;
50
+ exports.getValidationItems = getValidationItems;
51
+ exports.getPlanningSummary = getPlanningSummary;
52
+ exports.getPlanningList = getPlanningList;
53
+ exports.getSnapshotValidationMap = getSnapshotValidationMap;
54
+ exports.hasValidationPassed = hasValidationPassed;
55
+ exports.assessTransitionGate = assessTransitionGate;
56
+ const fs = __importStar(require("fs"));
57
+ const path = __importStar(require("path"));
58
+ const config_1 = require("../lib/config");
59
+ const state_1 = require("../lib/state");
60
+ const constants_1 = require("../lib/constants");
61
+ function normalizePath(inputPath, projectRoot) {
62
+ if (!inputPath || typeof inputPath !== 'string') {
63
+ return '';
64
+ }
65
+ const absolutePath = path.isAbsolute(inputPath) ? inputPath : path.resolve(projectRoot, inputPath);
66
+ return path.relative(projectRoot, absolutePath).replace(/\\/g, '/');
67
+ }
68
+ function normalizeValidationName(name) {
69
+ return String(name || '')
70
+ .trim()
71
+ .toLowerCase()
72
+ .replace(/_/g, '-')
73
+ .replace(/\s+/g, '-');
74
+ }
75
+ function getValidationAliases(validationName) {
76
+ const normalized = normalizeValidationName(validationName);
77
+ return constants_1.VALIDATION_ALIASES[normalized] || [normalized];
78
+ }
79
+ function isPassedStatus(status) {
80
+ if (!status) {
81
+ return false;
82
+ }
83
+ const normalized = status.trim().toLowerCase();
84
+ return constants_1.VALIDATED_STATUSES.includes(normalized);
85
+ }
86
+ function parseReviewConclusion(content) {
87
+ const patterns = [
88
+ { regex: /^\s*(?:结论 |Conclusion)\s*:\s*PASS\b/m, result: constants_1.REVIEW_CONCLUSION.PASS },
89
+ { regex: /^\s*(?:结论 |Conclusion)\s*:\s*NEEDS_WORK\b/m, result: constants_1.REVIEW_CONCLUSION.NEEDS_WORK },
90
+ { regex: /^\s*(?:结论 |Conclusion)\s*:\s*FAIL\b/m, result: constants_1.REVIEW_CONCLUSION.FAIL },
91
+ { regex: /^##\s*结论\s*:\s*PASS\b/m, result: constants_1.REVIEW_CONCLUSION.PASS },
92
+ { regex: /^##\s*结论\s*:\s*NEEDS_WORK\b/m, result: constants_1.REVIEW_CONCLUSION.NEEDS_WORK },
93
+ { regex: /^##\s*结论\s*:\s*FAIL\b/m, result: constants_1.REVIEW_CONCLUSION.FAIL },
94
+ { regex: /^#\s*PASS\b/m, result: constants_1.REVIEW_CONCLUSION.PASS },
95
+ { regex: /^#\s*NEEDS_WORK\b/m, result: constants_1.REVIEW_CONCLUSION.NEEDS_WORK },
96
+ { regex: /^#\s*FAIL\b/m, result: constants_1.REVIEW_CONCLUSION.FAIL },
97
+ { regex: /\*\*结论\*\*\s*:\s*PASS\b/, result: constants_1.REVIEW_CONCLUSION.PASS },
98
+ { regex: /\*\*结论\*\*\s*:\s*NEEDS_WORK\b/, result: constants_1.REVIEW_CONCLUSION.NEEDS_WORK },
99
+ { regex: /\*\*结论\*\*\s*:\s*FAIL\b/, result: constants_1.REVIEW_CONCLUSION.FAIL },
100
+ { regex: /\*\*PASS\*\*/, result: constants_1.REVIEW_CONCLUSION.PASS },
101
+ { regex: /\*\*NEEDS_WORK\*\*/, result: constants_1.REVIEW_CONCLUSION.NEEDS_WORK },
102
+ { regex: /\*\*FAIL\*\*/, result: constants_1.REVIEW_CONCLUSION.FAIL },
103
+ // Additional pattern for "结论 : NEEDS_WORK" format (with space before colon)
104
+ { regex: /结论\s*:\s*NEEDS_WORK\b/, result: constants_1.REVIEW_CONCLUSION.NEEDS_WORK },
105
+ { regex: /结论\s*:\s*PASS\b/, result: constants_1.REVIEW_CONCLUSION.PASS },
106
+ { regex: /结论\s*:\s*FAIL\b/, result: constants_1.REVIEW_CONCLUSION.FAIL },
107
+ ];
108
+ for (const { regex, result } of patterns) {
109
+ if (regex.test(content)) {
110
+ return result;
111
+ }
112
+ }
113
+ return null;
114
+ }
115
+ function parseReviewValidationMap(content) {
116
+ const validationMap = {};
117
+ const regex = /^\s*[-*]?\s*([a-z][a-z0-9_-]*)\s*:\s*([A-Za-z0-9_-]+)\s*$/gm;
118
+ let match = regex.exec(content);
119
+ while (match) {
120
+ const key = match[1];
121
+ const value = match[2];
122
+ if (key && value) {
123
+ validationMap[normalizeValidationName(key)] = value;
124
+ }
125
+ match = regex.exec(content);
126
+ }
127
+ return validationMap;
128
+ }
129
+ function parseReviewReport(content) {
130
+ const conclusion = parseReviewConclusion(content);
131
+ const validationMap = parseReviewValidationMap(content);
132
+ if (conclusion && !validationMap['review-conclusion']) {
133
+ validationMap['review-conclusion'] = conclusion;
134
+ }
135
+ return {
136
+ conclusion,
137
+ validationMap,
138
+ };
139
+ }
140
+ function deriveTransitionFromFilePath(filePath, config) {
141
+ const relativePath = normalizePath(filePath, config.projectRoot);
142
+ // Security: Prevent path traversal by ensuring path doesn't escape expected directories
143
+ if (relativePath.includes('..') || (relativePath.startsWith('/') && !relativePath.startsWith(config.projectRoot))) {
144
+ return null;
145
+ }
146
+ // Check if path is in harness directory or matches expected patterns
147
+ const isInHarness = relativePath.includes('.harness/');
148
+ const matchesTaskPattern = new RegExp(`^${normalizePath(config.tasksDirAbs, config.projectRoot)}/.+\\.json$`).test(relativePath);
149
+ const matchesSnapshotPattern = new RegExp(`^${normalizePath(config.snapshotsDirAbs, config.projectRoot)}/.+\\.json$`).test(relativePath);
150
+ const matchesReportPattern = new RegExp(`^${normalizePath(config.reportsDirAbs, config.projectRoot)}/.+\\.md$`).test(relativePath);
151
+ if (!isInHarness && !matchesTaskPattern && !matchesSnapshotPattern && !matchesReportPattern) {
152
+ return null;
153
+ }
154
+ const tasksPattern = new RegExp(`^${normalizePath(config.tasksDirAbs, config.projectRoot)}/.+\\.json$`);
155
+ const snapshotPattern = new RegExp(`^${normalizePath(config.snapshotsDirAbs, config.projectRoot)}/.+\\.json$`);
156
+ const reportPattern = new RegExp(`^${normalizePath(config.reportsDirAbs, config.projectRoot)}/.+\\.md$`);
157
+ if (tasksPattern.test(relativePath)) {
158
+ return { phaseCompleted: 'planning', nextPhase: 'implementation', relativePath };
159
+ }
160
+ if (snapshotPattern.test(relativePath) && /-impl\.json$/.test(relativePath)) {
161
+ return { phaseCompleted: 'implementation', nextPhase: 'review', relativePath };
162
+ }
163
+ if (reportPattern.test(relativePath) && /-review\.md$/.test(relativePath)) {
164
+ return { phaseCompleted: 'review', nextPhase: 'merge', relativePath };
165
+ }
166
+ return null;
167
+ }
168
+ function getTaskFilePath(config, taskId) {
169
+ return path.resolve(config.tasksDirAbs, `${taskId}.json`);
170
+ }
171
+ function readTaskJson(config, taskId) {
172
+ const taskPath = getTaskFilePath(config, taskId);
173
+ if (!fs.existsSync(taskPath)) {
174
+ return null;
175
+ }
176
+ try {
177
+ return JSON.parse(fs.readFileSync(taskPath, 'utf8'));
178
+ }
179
+ catch {
180
+ return null;
181
+ }
182
+ }
183
+ function getValidationItems(taskJson, phaseName) {
184
+ if (!taskJson) {
185
+ return [];
186
+ }
187
+ if (phaseName === 'implementation') {
188
+ return Array.isArray(taskJson.validation?.commands) ? taskJson.validation.commands : [];
189
+ }
190
+ if (phaseName === 'review') {
191
+ return Array.isArray(taskJson.validation?.manual_checks) ? taskJson.validation.manual_checks : [];
192
+ }
193
+ return [];
194
+ }
195
+ function getPlanningSummary(taskJson) {
196
+ return taskJson?.implementation?.notes || '';
197
+ }
198
+ function getPlanningList(taskJson, key) {
199
+ if (!taskJson) {
200
+ return [];
201
+ }
202
+ if (key === 'implementationSteps') {
203
+ const steps = Array.isArray(taskJson.implementation?.steps) ? taskJson.implementation.steps : [];
204
+ return steps.map((item) => (typeof item === 'string' ? item : item.step || ''));
205
+ }
206
+ if (key === 'testStrategy') {
207
+ return Array.isArray(taskJson.validation?.commands) ? taskJson.validation.commands : [];
208
+ }
209
+ return [];
210
+ }
211
+ function getSnapshotValidationMap(snapshotPath) {
212
+ if (!snapshotPath || !fs.existsSync(snapshotPath)) {
213
+ return {};
214
+ }
215
+ try {
216
+ const snapshot = JSON.parse(fs.readFileSync(snapshotPath, 'utf8'));
217
+ const results = Array.isArray(snapshot.validationResults) ? snapshot.validationResults : [];
218
+ const map = {};
219
+ for (const result of results) {
220
+ if (!result?.name) {
221
+ continue;
222
+ }
223
+ const name = normalizeValidationName(result.name);
224
+ map[name] = isPassedStatus(result.status);
225
+ }
226
+ return map;
227
+ }
228
+ catch {
229
+ return {};
230
+ }
231
+ }
232
+ function hasValidationPassed(validationName, snapshotValidationMap) {
233
+ const candidates = getValidationAliases(validationName).map(normalizeValidationName);
234
+ return candidates.some((item) => snapshotValidationMap[item] === true || isPassedStatus(snapshotValidationMap[item]));
235
+ }
236
+ /**
237
+ * Validate planning phase
238
+ */
239
+ function validatePlanningPhase(taskJson, validations, config) {
240
+ const missingValidations = [];
241
+ const details = [];
242
+ const taskOutput = taskJson.metadata?.outputs?.task;
243
+ const taskOutputExists = taskOutput && fs.existsSync(path.resolve(config.projectRoot, taskOutput));
244
+ if (!taskOutputExists) {
245
+ missingValidations.push('planning-task-exists');
246
+ }
247
+ if (validations.includes('planning-ready')) {
248
+ const summary = getPlanningSummary(taskJson);
249
+ const implementationSteps = getPlanningList(taskJson, 'implementationSteps');
250
+ const testStrategy = getPlanningList(taskJson, 'testStrategy');
251
+ if (!summary) {
252
+ missingValidations.push('planning.summary');
253
+ }
254
+ if (implementationSteps.length === 0) {
255
+ missingValidations.push('planning.implementationSteps');
256
+ }
257
+ if (testStrategy.length === 0) {
258
+ missingValidations.push('planning.testStrategy');
259
+ }
260
+ }
261
+ return { missingValidations, details };
262
+ }
263
+ /**
264
+ * Validate implementation phase
265
+ */
266
+ function validateImplementationPhase(validations, relativePath, config) {
267
+ const missingValidations = [];
268
+ const details = [];
269
+ const snapshotPath = path.resolve(config.projectRoot, relativePath);
270
+ const snapshotValidationMap = getSnapshotValidationMap(snapshotPath);
271
+ for (const validation of validations) {
272
+ if (!hasValidationPassed(validation, snapshotValidationMap)) {
273
+ missingValidations.push(validation);
274
+ }
275
+ }
276
+ details.push(`snapshot:${relativePath}`);
277
+ return { missingValidations, details };
278
+ }
279
+ /**
280
+ * Validate review phase
281
+ */
282
+ function validateReviewPhase(validations, relativePath, config, stateReviewConclusion) {
283
+ const missingValidations = [];
284
+ const details = [];
285
+ let reviewConclusion = stateReviewConclusion;
286
+ const reportPath = path.resolve(config.projectRoot, relativePath);
287
+ if (!fs.existsSync(reportPath)) {
288
+ missingValidations.push('review-report-exists');
289
+ return { missingValidations, details, reviewConclusion };
290
+ }
291
+ const reportContent = fs.readFileSync(reportPath, 'utf8');
292
+ const parsedReport = parseReviewReport(reportContent);
293
+ if (parsedReport.conclusion) {
294
+ reviewConclusion = parsedReport.conclusion;
295
+ }
296
+ for (const validation of validations) {
297
+ const normalizedValidation = normalizeValidationName(validation);
298
+ if (normalizedValidation === 'review-conclusion') {
299
+ if (reviewConclusion !== 'PASS') {
300
+ missingValidations.push('review-conclusion=PASS');
301
+ }
302
+ continue;
303
+ }
304
+ if (!hasValidationPassed(normalizedValidation, parsedReport.validationMap)) {
305
+ missingValidations.push(validation);
306
+ }
307
+ }
308
+ details.push(`report:${relativePath}`);
309
+ return { missingValidations, details, reviewConclusion };
310
+ }
311
+ /**
312
+ * Assess transition gate
313
+ * @param options - Options
314
+ * @returns Gate assessment result
315
+ */
316
+ function assessTransitionGate(options) {
317
+ const { taskId, filePath, state, config } = options;
318
+ const transition = deriveTransitionFromFilePath(filePath, config);
319
+ if (!transition) {
320
+ return { allowed: false, skipped: true, reason: 'not-transition-artifact' };
321
+ }
322
+ const taskJson = readTaskJson(config, taskId);
323
+ if (!taskJson) {
324
+ return {
325
+ allowed: false,
326
+ skipped: false,
327
+ phaseCompleted: transition.phaseCompleted,
328
+ nextPhase: transition.nextPhase,
329
+ reason: `task-not-found:${normalizePath(getTaskFilePath(config, taskId), config.projectRoot)}`,
330
+ };
331
+ }
332
+ const validations = getValidationItems(taskJson, transition.phaseCompleted);
333
+ let missingValidations = [];
334
+ let details = [];
335
+ let reviewConclusion = state?.reviewConclusion || null;
336
+ if (transition.phaseCompleted === 'planning') {
337
+ const planningResult = validatePlanningPhase(taskJson, validations, config);
338
+ missingValidations = planningResult.missingValidations;
339
+ details = planningResult.details;
340
+ }
341
+ if (transition.phaseCompleted === 'implementation') {
342
+ const implResult = validateImplementationPhase(validations, transition.relativePath, config);
343
+ missingValidations = implResult.missingValidations;
344
+ details = implResult.details;
345
+ }
346
+ if (transition.phaseCompleted === 'review') {
347
+ const reviewResult = validateReviewPhase(validations, transition.relativePath, config, reviewConclusion);
348
+ missingValidations = reviewResult.missingValidations;
349
+ details = reviewResult.details;
350
+ reviewConclusion = reviewResult.reviewConclusion;
351
+ }
352
+ if (missingValidations.length > 0) {
353
+ return {
354
+ allowed: false,
355
+ skipped: false,
356
+ phaseCompleted: transition.phaseCompleted,
357
+ nextPhase: transition.nextPhase,
358
+ missingValidations,
359
+ details,
360
+ reviewConclusion,
361
+ reason: 'phase-validation-failed',
362
+ };
363
+ }
364
+ return {
365
+ allowed: true,
366
+ skipped: false,
367
+ phaseCompleted: transition.phaseCompleted,
368
+ nextPhase: transition.nextPhase,
369
+ validations,
370
+ details,
371
+ reviewConclusion,
372
+ reason: 'phase-validation-passed',
373
+ };
374
+ }
375
+ /**
376
+ * Main entry point for hook usage
377
+ */
378
+ function main() {
379
+ const input = JSON.parse(process.argv[2] || '{}');
380
+ const filePath = input.tool_input?.file_path || input.tool_input?.path || '';
381
+ const config = (0, config_1.loadConfig)();
382
+ const state = (0, state_1.loadState)(config);
383
+ if (!state?.currentTask) {
384
+ process.exit(0);
385
+ }
386
+ const result = assessTransitionGate({ taskId: state.currentTask, filePath, state, config });
387
+ if (result.skipped) {
388
+ process.exit(0);
389
+ }
390
+ if (!result.allowed) {
391
+ console.error(`[PHASE-GATE] blocked ${result.phaseCompleted} -> ${result.nextPhase}: ${result.reason}`);
392
+ if (Array.isArray(result.missingValidations) && result.missingValidations.length > 0) {
393
+ console.error(`[PHASE-GATE] missing: ${result.missingValidations.join(', ')}`);
394
+ }
395
+ process.exit(2);
396
+ }
397
+ process.exit(0);
398
+ }
399
+ if (require.main === module) {
400
+ main();
401
+ }
402
+ exports.default = {
403
+ assessTransitionGate,
404
+ deriveTransitionFromFilePath,
405
+ parseReviewConclusion,
406
+ parseReviewReport,
407
+ };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * hooks/save-checkpoint.ts
3
+ * Save runtime snapshot/checkpoint
4
+ */
5
+ import { Config } from '../lib/config';
6
+ import { State } from '../lib/state';
7
+ export interface HookInput {
8
+ hook_event_name?: string;
9
+ [key: string]: unknown;
10
+ }
11
+ export interface CheckpointResult {
12
+ saved: boolean;
13
+ reason?: string;
14
+ snapshotId?: string;
15
+ snapshotPath?: string;
16
+ }
17
+ export interface CheckpointOptions {
18
+ input: HookInput;
19
+ config: Config;
20
+ state: State;
21
+ }
22
+ export interface Snapshot {
23
+ snapshotId: string;
24
+ snapshotType: string;
25
+ taskId: string;
26
+ phase: string;
27
+ timestamp: string;
28
+ status: string;
29
+ source: string;
30
+ metrics: Record<string, unknown>;
31
+ files: string[];
32
+ validationResults: unknown[];
33
+ }
34
+ /**
35
+ * Save runtime snapshot
36
+ * @param options - Options
37
+ * @returns Result
38
+ */
39
+ export declare function saveCheckpoint(options: CheckpointOptions): CheckpointResult;
40
+ declare const _default: {
41
+ saveCheckpoint: typeof saveCheckpoint;
42
+ };
43
+ export default _default;
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ /**
3
+ * hooks/save-checkpoint.ts
4
+ * Save runtime snapshot/checkpoint
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.saveCheckpoint = saveCheckpoint;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const child_process_1 = require("child_process");
44
+ const config_1 = require("../lib/config");
45
+ const state_1 = require("../lib/state");
46
+ /**
47
+ * Validate config has required properties for checkpoint
48
+ */
49
+ function validateConfigForCheckpoint(config) {
50
+ if (!config.projectRoot) {
51
+ return {
52
+ valid: false,
53
+ reason: 'missing-project-root',
54
+ message: 'Config missing projectRoot',
55
+ };
56
+ }
57
+ if (!config.snapshotsDirAbs) {
58
+ return {
59
+ valid: false,
60
+ reason: 'missing-snapshots-dir',
61
+ message: 'Config missing snapshotsDirAbs',
62
+ };
63
+ }
64
+ return { valid: true };
65
+ }
66
+ /**
67
+ * Save runtime snapshot
68
+ * @param options - Options
69
+ * @returns Result
70
+ */
71
+ function saveCheckpoint(options) {
72
+ const { input, config, state } = options;
73
+ const taskId = process.env.HARNESS_TASK_ID || state.currentTask;
74
+ const currentPhase = process.env.HARNESS_PHASE || state.currentPhase || 'unknown';
75
+ if (!taskId) {
76
+ return { saved: false, reason: 'missing-task-id' };
77
+ }
78
+ // Validate config before use
79
+ const configValidation = validateConfigForCheckpoint(config);
80
+ if (!configValidation.valid) {
81
+ console.error(`[SNAPSHOT] ${configValidation.message}`);
82
+ return { saved: false, reason: configValidation.reason || 'config-validation-failed' };
83
+ }
84
+ const projectRoot = config.projectRoot || process.cwd();
85
+ const snapshotDir = config.snapshotsDirAbs || path.resolve(projectRoot, '.harness', 'snapshots');
86
+ fs.mkdirSync(snapshotDir, { recursive: true });
87
+ const timestamp = Date.now();
88
+ const snapshotId = `${taskId}-${currentPhase}-${timestamp}`;
89
+ const snapshotRelativePath = path.posix.join(path.relative(projectRoot, snapshotDir), 'runtime', `${snapshotId}.json`);
90
+ const snapshot = {
91
+ snapshotId,
92
+ snapshotType: 'runtime',
93
+ taskId,
94
+ phase: currentPhase,
95
+ timestamp: new Date().toISOString(),
96
+ status: 'in_progress',
97
+ source: input.hook_event_name || 'runtime',
98
+ metrics: {},
99
+ files: [],
100
+ validationResults: [],
101
+ };
102
+ // Try to collect changed files from git diff
103
+ try {
104
+ const diffOutput = (0, child_process_1.execSync)('git diff --name-only HEAD', { encoding: 'utf8' });
105
+ snapshot.files = diffOutput.trim().split('\n').filter((f) => f);
106
+ }
107
+ catch {
108
+ // Git not available or no changes
109
+ }
110
+ // Save snapshot
111
+ const runtimeSnapshotDir = path.resolve(snapshotDir, 'runtime');
112
+ fs.mkdirSync(runtimeSnapshotDir, { recursive: true });
113
+ const snapshotPath = path.resolve(runtimeSnapshotDir, `${snapshotId}.json`);
114
+ fs.writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2), 'utf8');
115
+ console.log(`[SNAPSHOT] Saved: ${snapshotId}`);
116
+ // Update state with current snapshot reference
117
+ (0, state_1.updateState)({
118
+ currentSnapshot: snapshotRelativePath,
119
+ }, config);
120
+ return {
121
+ saved: true,
122
+ snapshotId,
123
+ snapshotPath: snapshotRelativePath,
124
+ };
125
+ }
126
+ /**
127
+ * Main entry point for hook usage
128
+ */
129
+ function main() {
130
+ const input = JSON.parse(process.argv[2] || '{}');
131
+ const config = (0, config_1.loadConfig)();
132
+ const state = (0, state_1.loadState)(config);
133
+ const result = saveCheckpoint({ input, config, state });
134
+ if (!result.saved) {
135
+ console.log(`[SNAPSHOT] skipped: ${result.reason}`);
136
+ }
137
+ process.exit(0);
138
+ }
139
+ if (require.main === module) {
140
+ main();
141
+ }
142
+ exports.default = {
143
+ saveCheckpoint,
144
+ };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * hooks/start-mcp-servers.ts
3
+ * Hook for starting MCP servers with autoStart enabled
4
+ *
5
+ * This hook:
6
+ * 1. Gets MCP server configurations from context
7
+ * 2. Starts servers with lifecycle.autoStart = true
8
+ * 3. Returns connection information for downstream use
9
+ */
10
+ import { McpServerConfig, StartResult, ConnectionInfo } from '../lib/mcp-client';
11
+ export interface StartMcpContext {
12
+ config?: {
13
+ projectRoot: string;
14
+ };
15
+ tools?: {
16
+ mcp: McpServerConfig[];
17
+ };
18
+ [key: string]: unknown;
19
+ }
20
+ export interface StartMcpResult {
21
+ check: 'mcp-started';
22
+ success: boolean;
23
+ error?: string;
24
+ reason?: string;
25
+ message?: string;
26
+ result?: {
27
+ success: boolean;
28
+ results: StartResult[];
29
+ };
30
+ connectionInfo?: ConnectionInfo[];
31
+ started?: unknown[];
32
+ summary?: {
33
+ totalCount: number;
34
+ startedCount: number;
35
+ skippedCount: number;
36
+ failedCount: number;
37
+ };
38
+ stack?: string;
39
+ }
40
+ /**
41
+ * Start MCP servers hook
42
+ * @param context - Hook execution context
43
+ * @returns Hook execution result
44
+ */
45
+ export declare function run(context: StartMcpContext): StartMcpResult;
46
+ declare const _default: {
47
+ run: typeof run;
48
+ name: string;
49
+ };
50
+ export default _default;