forge-workflow 0.0.1

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 (105) hide show
  1. package/.claude/commands/dev.md +314 -0
  2. package/.claude/commands/plan.md +389 -0
  3. package/.claude/commands/premerge.md +179 -0
  4. package/.claude/commands/research.md +42 -0
  5. package/.claude/commands/review.md +442 -0
  6. package/.claude/commands/rollback.md +721 -0
  7. package/.claude/commands/ship.md +134 -0
  8. package/.claude/commands/sonarcloud.md +152 -0
  9. package/.claude/commands/status.md +77 -0
  10. package/.claude/commands/validate.md +237 -0
  11. package/.claude/commands/verify.md +221 -0
  12. package/.claude/rules/greptile-review-process.md +285 -0
  13. package/.claude/rules/workflow.md +105 -0
  14. package/.claude/scripts/greptile-resolve.sh +526 -0
  15. package/.claude/scripts/load-env.sh +32 -0
  16. package/.forge/hooks/check-tdd.js +240 -0
  17. package/.github/PLUGIN_TEMPLATE.json +32 -0
  18. package/.mcp.json.example +12 -0
  19. package/AGENTS.md +169 -0
  20. package/CLAUDE.md +99 -0
  21. package/LICENSE +21 -0
  22. package/README.md +414 -0
  23. package/bin/forge-cmd.js +313 -0
  24. package/bin/forge-validate.js +303 -0
  25. package/bin/forge.js +4228 -0
  26. package/docs/AGENT_INSTALL_PROMPT.md +342 -0
  27. package/docs/ENHANCED_ONBOARDING.md +602 -0
  28. package/docs/EXAMPLES.md +482 -0
  29. package/docs/GREPTILE_SETUP.md +400 -0
  30. package/docs/MANUAL_REVIEW_GUIDE.md +106 -0
  31. package/docs/ROADMAP.md +359 -0
  32. package/docs/SETUP.md +632 -0
  33. package/docs/TOOLCHAIN.md +849 -0
  34. package/docs/VALIDATION.md +363 -0
  35. package/docs/WORKFLOW.md +400 -0
  36. package/docs/planning/PROGRESS.md +396 -0
  37. package/docs/plans/.gitkeep +0 -0
  38. package/docs/plans/2026-02-27-forge-test-suite-v2-decisions.md +21 -0
  39. package/docs/plans/2026-02-27-forge-test-suite-v2-design.md +362 -0
  40. package/docs/plans/2026-02-27-forge-test-suite-v2-tasks.md +343 -0
  41. package/docs/plans/2026-03-02-superpowers-gaps-decisions.md +26 -0
  42. package/docs/plans/2026-03-02-superpowers-gaps-design.md +239 -0
  43. package/docs/plans/2026-03-02-superpowers-gaps-tasks.md +260 -0
  44. package/docs/plans/2026-03-04-agent-command-parity-design.md +163 -0
  45. package/docs/plans/2026-03-04-verify-worktree-cleanup-decisions.md +7 -0
  46. package/docs/plans/2026-03-04-verify-worktree-cleanup-design.md +165 -0
  47. package/docs/plans/2026-03-05-forge-uto-decisions.md +6 -0
  48. package/docs/plans/2026-03-05-forge-uto-design.md +116 -0
  49. package/docs/plans/2026-03-05-forge-uto-tasks.md +244 -0
  50. package/docs/plans/2026-03-10-command-creator-and-eval-decisions.md +52 -0
  51. package/docs/plans/2026-03-10-command-creator-and-eval-design.md +350 -0
  52. package/docs/plans/2026-03-10-command-creator-and-eval-tasks.md +426 -0
  53. package/docs/plans/2026-03-10-stale-workflow-refs-decisions.md +8 -0
  54. package/docs/plans/2026-03-10-stale-workflow-refs-design.md +80 -0
  55. package/docs/plans/2026-03-10-stale-workflow-refs-tasks.md +90 -0
  56. package/docs/plans/2026-03-14-beads-plan-context-decisions.md +9 -0
  57. package/docs/plans/2026-03-14-beads-plan-context-design.md +171 -0
  58. package/docs/plans/2026-03-14-beads-plan-context-tasks.md +160 -0
  59. package/docs/plans/2026-03-14-skill-eval-loop-decisions.md +33 -0
  60. package/docs/plans/2026-03-14-skill-eval-loop-design.md +118 -0
  61. package/docs/plans/2026-03-14-skill-eval-loop-results.md +78 -0
  62. package/docs/plans/2026-03-14-skill-eval-loop-tasks.md +160 -0
  63. package/docs/plans/2026-03-15-agent-command-parity-v2-decisions.md +11 -0
  64. package/docs/plans/2026-03-15-agent-command-parity-v2-design.md +145 -0
  65. package/docs/plans/2026-03-15-agent-command-parity-v2-tasks.md +211 -0
  66. package/docs/research/TEMPLATE.md +292 -0
  67. package/docs/research/advanced-testing.md +297 -0
  68. package/docs/research/agent-permissions.md +167 -0
  69. package/docs/research/dependency-chain.md +328 -0
  70. package/docs/research/forge-workflow-v2.md +550 -0
  71. package/docs/research/plugin-architecture.md +772 -0
  72. package/docs/research/pr4-cli-automation.md +326 -0
  73. package/docs/research/premerge-verify-restructure.md +205 -0
  74. package/docs/research/skills-restructure.md +508 -0
  75. package/docs/research/sonarcloud-perfection-plan.md +166 -0
  76. package/docs/research/sonarcloud-quality-gate.md +184 -0
  77. package/docs/research/superpowers-integration.md +403 -0
  78. package/docs/research/superpowers.md +319 -0
  79. package/docs/research/test-environment.md +519 -0
  80. package/install.sh +1062 -0
  81. package/lefthook.yml +39 -0
  82. package/lib/agents/README.md +198 -0
  83. package/lib/agents/claude.plugin.json +28 -0
  84. package/lib/agents/cline.plugin.json +22 -0
  85. package/lib/agents/codex.plugin.json +19 -0
  86. package/lib/agents/copilot.plugin.json +24 -0
  87. package/lib/agents/cursor.plugin.json +25 -0
  88. package/lib/agents/kilocode.plugin.json +22 -0
  89. package/lib/agents/opencode.plugin.json +20 -0
  90. package/lib/agents/roo.plugin.json +23 -0
  91. package/lib/agents-config.js +2112 -0
  92. package/lib/commands/dev.js +513 -0
  93. package/lib/commands/plan.js +696 -0
  94. package/lib/commands/recommend.js +119 -0
  95. package/lib/commands/ship.js +377 -0
  96. package/lib/commands/status.js +378 -0
  97. package/lib/commands/validate.js +602 -0
  98. package/lib/context-merge.js +359 -0
  99. package/lib/plugin-catalog.js +360 -0
  100. package/lib/plugin-manager.js +166 -0
  101. package/lib/plugin-recommender.js +141 -0
  102. package/lib/project-discovery.js +491 -0
  103. package/lib/setup.js +118 -0
  104. package/lib/workflow-profiles.js +203 -0
  105. package/package.json +115 -0
@@ -0,0 +1,513 @@
1
+ /**
2
+ * Dev Command - TDD Cycle Management
3
+ * Guides developers through RED-GREEN-REFACTOR cycles
4
+ *
5
+ * Security: Uses execFileSync for test execution to prevent command injection
6
+ * TDD Discipline: Enforces test-first development and validates phase transitions
7
+ *
8
+ * @module commands/dev
9
+ */
10
+
11
+ const { execFileSync } = require('node:child_process');
12
+ const fs = require('node:fs');
13
+ const path = require('node:path');
14
+
15
+ function getExecOptions() {
16
+ return { encoding: 'utf8', cwd: process.cwd(), timeout: 120000 };
17
+ }
18
+
19
+ /**
20
+ * Detect current TDD phase based on project context
21
+ *
22
+ * Detection logic:
23
+ * - RED: Tests exist, no implementation OR implementation exists but tests failing
24
+ * - GREEN: Implementation exists, tests failing
25
+ * - REFACTOR: Implementation exists, tests passing
26
+ *
27
+ * @param {{sourceFiles: string[], testFiles: string[], testsPassing?: boolean}} context - Project context
28
+ * @returns {'RED'|'GREEN'|'REFACTOR'} Current TDD phase
29
+ * @example
30
+ * const phase = detectTDDPhase({ sourceFiles: ['lib/feature.js'], testFiles: ['test/feature.test.js'], testsPassing: true });
31
+ * console.log(phase); // 'REFACTOR'
32
+ */
33
+ function detectTDDPhase(context) {
34
+ const { sourceFiles = [], testFiles = [], testsPassing } = context;
35
+
36
+ const hasTests = testFiles.length > 0;
37
+ const hasImplementation = sourceFiles.length > 0;
38
+
39
+ // RED: Tests exist but no implementation, or tests are failing
40
+ if (hasTests && !hasImplementation) {
41
+ return 'RED';
42
+ }
43
+
44
+ // GREEN: Tests failing with implementation
45
+ if (hasTests && hasImplementation && testsPassing === false) {
46
+ return 'GREEN';
47
+ }
48
+
49
+ // REFACTOR: Tests passing
50
+ if (hasTests && hasImplementation && testsPassing === true) {
51
+ return 'REFACTOR';
52
+ }
53
+
54
+ // Default to RED (write tests first)
55
+ return 'RED';
56
+ }
57
+
58
+ /**
59
+ * Identify source and test file pairs
60
+ * Maps source files to their corresponding test files
61
+ *
62
+ * Conventions:
63
+ * - lib/commands/feature.js → test/commands/feature.test.js
64
+ * - src/utils/helper.js → test/utils/helper.test.js
65
+ *
66
+ * @param {string[]} files - List of file paths
67
+ * @returns {{length: number, pairs?: Array<{source: string, test: string}>, orphanedTests?: string[], orphanedSources?: string[]}} File pair analysis
68
+ * @example
69
+ * const result = identifyFilePairs(['lib/feature.js', 'test/feature.test.js']);
70
+ * console.log(result.pairs); // [{ source: 'lib/feature.js', test: 'test/feature.test.js' }]
71
+ */
72
+ function identifyFilePairs(files) {
73
+ const testFiles = files.filter(f => f.includes('test') && f.endsWith('.test.js'));
74
+ const sourceFiles = files.filter(f => !f.includes('test') && f.endsWith('.js'));
75
+
76
+ const pairs = [];
77
+ const orphanedTests = [];
78
+ const orphanedSources = [];
79
+
80
+ // Match test files to source files
81
+ testFiles.forEach(testFile => {
82
+ // Convert test/commands/feature.test.js → lib/commands/feature.js
83
+ const sourceFile = testFile
84
+ .replace(/^test\//, 'lib/')
85
+ .replace(/\.test\.js$/, '.js');
86
+
87
+ if (sourceFiles.includes(sourceFile)) {
88
+ pairs.push({ source: sourceFile, test: testFile });
89
+ } else {
90
+ orphanedTests.push(testFile);
91
+ }
92
+ });
93
+
94
+ // Find source files without tests
95
+ sourceFiles.forEach(sourceFile => {
96
+ const testFile = sourceFile
97
+ .replace(/^(lib|src)\//, 'test/')
98
+ .replace(/\.js$/, '.test.js');
99
+
100
+ if (!testFiles.includes(testFile)) {
101
+ orphanedSources.push(sourceFile);
102
+ }
103
+ });
104
+
105
+ return {
106
+ length: pairs.length,
107
+ pairs: pairs.length > 0 ? pairs : undefined,
108
+ orphanedTests: orphanedTests.length > 0 ? orphanedTests : undefined,
109
+ orphanedSources: orphanedSources.length > 0 ? orphanedSources : undefined,
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Run tests using bun test
115
+ * Executes specified test file or all tests
116
+ *
117
+ * @param {string} [testFile] - Optional specific test file to run
118
+ * @returns {Promise<{success: boolean, passed?: number, failed?: number, duration?: number, totalTests?: number, error?: string}>} Test execution result
119
+ * @example
120
+ * const result = await runTests('test/commands/feature.test.js');
121
+ * console.log(`${result.passed}/${result.passed + result.failed} tests passed`);
122
+ */
123
+ async function runTests(testFile) {
124
+ try {
125
+ const args = ['test'];
126
+ if (testFile) {
127
+ // Check if test file exists
128
+ if (!fs.existsSync(testFile)) {
129
+ return {
130
+ success: false,
131
+ error: `Test file not found: ${testFile}\n\nEnsure the file exists and path is correct.`,
132
+ };
133
+ }
134
+ args.push(testFile);
135
+ }
136
+
137
+ const startTime = Date.now();
138
+ const result = execFileSync('bun', args, getExecOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
139
+ const duration = Date.now() - startTime;
140
+
141
+ // Parse bun test output
142
+ // Format: "X pass\nY fail\nRan Z tests"
143
+ const passMatch = /(\d+) pass/.exec(result); // NOSONAR S5852 - bounded \d+ pattern, no backtracking
144
+ const failMatch = /(\d+) fail/.exec(result); // NOSONAR S5852 - bounded \d+ pattern, no backtracking
145
+ const totalMatch = /Ran (\d+) tests/.exec(result);
146
+
147
+ const passed = passMatch ? parseInt(passMatch[1], 10) : 0;
148
+ const failed = failMatch ? parseInt(failMatch[1], 10) : 0;
149
+ const totalTests = totalMatch ? parseInt(totalMatch[1], 10) : passed + failed;
150
+
151
+ return {
152
+ success: failed === 0,
153
+ passed,
154
+ failed,
155
+ totalTests,
156
+ duration,
157
+ };
158
+ } catch (error) {
159
+ // Check for timeout
160
+ if (error.killed && error.signal === 'SIGTERM') {
161
+ return {
162
+ success: false,
163
+ error: 'Test execution timed out after 2 minutes',
164
+ };
165
+ }
166
+
167
+ // bun test exits non-zero when tests fail but stdout still contains results
168
+ // Parse stdout to extract pass/fail counts before falling back to generic error
169
+ if (error.stdout) {
170
+ const output = error.stdout;
171
+ const passMatch = /(\d+) pass/.exec(output); // NOSONAR S5852 - bounded \d+ pattern, no backtracking
172
+ const failMatch = /(\d+) fail/.exec(output); // NOSONAR S5852 - bounded \d+ pattern, no backtracking
173
+ const totalMatch = /Ran (\d+) tests/.exec(output);
174
+
175
+ if (passMatch || failMatch) {
176
+ const passed = passMatch ? Number.parseInt(passMatch[1], 10) : 0;
177
+ const failed = failMatch ? Number.parseInt(failMatch[1], 10) : 0;
178
+ const totalTests = totalMatch ? Number.parseInt(totalMatch[1], 10) : passed + failed;
179
+ return {
180
+ success: false,
181
+ passed,
182
+ failed,
183
+ totalTests,
184
+ error: `${failed} test${failed === 1 ? '' : 's'} failed`,
185
+ };
186
+ }
187
+ }
188
+
189
+ // Test execution failed (command not found or other error)
190
+ const bunNotFound = error.message.includes('ENOENT') || error.message.includes('not found');
191
+ const errorMsg = bunNotFound
192
+ ? 'bun command not found. Ensure bun is installed and in PATH'
193
+ : `Test execution failed: ${error.message}`;
194
+
195
+ return {
196
+ success: false,
197
+ error: errorMsg,
198
+ };
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Get phase-specific TDD guidance
204
+ * Provides actionable guidance for each TDD phase
205
+ *
206
+ * @param {'RED'|'GREEN'|'REFACTOR'} phase - Current TDD phase
207
+ * @returns {string} Phase-specific guidance
208
+ * @example
209
+ * const guidance = getTDDGuidance('RED');
210
+ * console.log(guidance); // "RED Phase: Write a failing test..."
211
+ */
212
+ function getTDDGuidance(phase) {
213
+ const guidance = {
214
+ RED: `RED Phase: Write a failing test
215
+
216
+ 1. Write test BEFORE implementation
217
+ 2. Test should fail (red)
218
+ 3. Verify test fails for the right reason
219
+ 4. Commit with "test: ..." message
220
+
221
+ Next: GREEN phase (implement to make test pass)`,
222
+
223
+ GREEN: `GREEN Phase: Make the test pass
224
+
225
+ 1. Write MINIMAL code to pass the test
226
+ 2. Don't worry about perfection
227
+ 3. Focus on making tests green
228
+ 4. Commit with "feat: ..." or "implement: ..." message
229
+
230
+ Next: REFACTOR phase (improve code quality)`,
231
+
232
+ REFACTOR: `REFACTOR Phase: Improve code quality
233
+
234
+ 1. Maintain passing tests throughout refactoring
235
+ 2. Extract duplicates, improve names, add docs
236
+ 3. Run tests frequently while refactoring
237
+ 4. Commit with "refactor: ..." message
238
+
239
+ Next: RED phase (next feature) or done`,
240
+ };
241
+
242
+ return guidance[phase] || 'Unknown phase';
243
+ }
244
+
245
+ /**
246
+ * Generate commit message for TDD phase
247
+ * Creates standardized commit messages for each phase
248
+ *
249
+ * @param {{phase: 'RED'|'GREEN'|'REFACTOR', files: string[], testCount?: number, feature?: string, improvements?: string[]}} context - Commit context
250
+ * @returns {string} Generated commit message
251
+ * @example
252
+ * const message = generateCommitMessage({ phase: 'RED', files: ['test/feature.test.js'], testCount: 15 });
253
+ * console.log(message); // "test: add feature tests (RED)\n\n15 tests written"
254
+ */
255
+ function generateCommitMessage(context) {
256
+ const { phase, files, testCount, feature, improvements } = context;
257
+
258
+ if (phase === 'RED') {
259
+ const fileNames = files.map(f => path.basename(f)).join(', ');
260
+ return `test: add ${feature || 'feature'} tests (RED)
261
+
262
+ ${testCount || files.length} tests written
263
+ Files: ${fileNames}
264
+
265
+ Tests are failing as expected (RED phase)`;
266
+ }
267
+
268
+ if (phase === 'GREEN') {
269
+ const fileNames = files.map(f => path.basename(f)).join(', ');
270
+ return `feat: implement ${feature || 'feature'} (GREEN)
271
+
272
+ Implementation complete, tests passing
273
+ Files: ${fileNames}
274
+
275
+ GREEN phase complete`;
276
+ }
277
+
278
+ if (phase === 'REFACTOR') {
279
+ const improvementList = improvements && improvements.length > 0
280
+ ? '\n\n' + improvements.map(i => `- ${i}`).join('\n')
281
+ : '';
282
+ return `refactor: improve ${feature || 'code'} (REFACTOR)${improvementList}
283
+
284
+ Code quality improvements while maintaining test coverage
285
+ Tests remain green throughout refactoring`;
286
+ }
287
+
288
+ return `${phase}: ${feature || 'changes'}`;
289
+ }
290
+
291
+ /**
292
+ * Identify independent features that can be worked on in parallel
293
+ * Analyzes dependencies to find parallelizable work
294
+ *
295
+ * @param {Array<{name: string, files: string[], dependencies: string[]}>} features - Feature list with dependencies
296
+ * @returns {{length: number, includes?: (name: string) => boolean, error?: string} | string[]} Parallel-safe features or error
297
+ * @example
298
+ * const parallel = identifyParallelWork([
299
+ * { name: 'feature-a', files: ['lib/a.js'], dependencies: [] },
300
+ * { name: 'feature-b', files: ['lib/b.js'], dependencies: [] }
301
+ * ]);
302
+ * console.log(parallel); // ['feature-a', 'feature-b']
303
+ */
304
+ function identifyParallelWork(features) {
305
+ // Check for circular dependencies
306
+ const visited = new Set();
307
+ const recStack = new Set();
308
+
309
+ function hasCycle(feature) {
310
+ if (!visited.has(feature.name)) {
311
+ visited.add(feature.name);
312
+ recStack.add(feature.name);
313
+
314
+ for (const dep of feature.dependencies) {
315
+ const depFeature = features.find(f => f.name === dep);
316
+ if (depFeature) {
317
+ if (!visited.has(dep) && hasCycle(depFeature)) {
318
+ return true;
319
+ } else if (recStack.has(dep)) {
320
+ return true;
321
+ }
322
+ }
323
+ }
324
+ }
325
+ recStack.delete(feature.name);
326
+ return false;
327
+ }
328
+
329
+ // Check for circular dependencies
330
+ for (const feature of features) {
331
+ if (hasCycle(feature)) {
332
+ return {
333
+ error: 'Circular dependency detected in features',
334
+ };
335
+ }
336
+ }
337
+
338
+ // Find features with no dependencies
339
+ const parallelFeatures = features
340
+ .filter(f => f.dependencies.length === 0)
341
+ .map(f => f.name);
342
+
343
+ return parallelFeatures;
344
+ }
345
+
346
+ /**
347
+ * Execute dev command workflow
348
+ * Main orchestrator for TDD development
349
+ *
350
+ * @param {string} featureName - Feature name
351
+ * @param {{phase?: 'RED'|'GREEN'|'REFACTOR', testsPassing?: boolean}} [options] - Execution options
352
+ * @returns {Promise<{success: boolean, phase?: string, detectedPhase?: string, guidance?: string, testResults?: object, summary?: string, nextPhase?: string, error?: string}>} Execution result
353
+ * @example
354
+ * const result = await executeDev('payment-integration', { phase: 'RED' });
355
+ * console.log(result.guidance);
356
+ */
357
+ async function executeDev(featureName, options = {}) {
358
+ if (!featureName || typeof featureName !== 'string') {
359
+ return {
360
+ success: false,
361
+ error: 'Feature name is required and must be a string',
362
+ };
363
+ }
364
+
365
+ const { phase, testsPassing } = options;
366
+
367
+ try {
368
+ // If no phase specified, auto-detect
369
+ let currentPhase = phase;
370
+ if (!currentPhase) {
371
+ // Scan actual project directories to detect phase
372
+ const readDirSafe = (dir) => {
373
+ try { return fs.readdirSync(dir).filter(f => f.endsWith('.js')); }
374
+ catch (_e) { return []; }
375
+ };
376
+ const sourceFiles = readDirSafe('lib/commands');
377
+ const testFiles = readDirSafe('test/commands');
378
+ // Note: testsPassing is undefined here, so detectTDDPhase defaults to RED.
379
+ // RED is the safe default when pass/fail state is unknown (write tests first).
380
+ // Use --phase flag to explicitly specify GREEN or REFACTOR.
381
+ currentPhase = detectTDDPhase({ sourceFiles, testFiles, testsPassing });
382
+
383
+ return {
384
+ success: true,
385
+ detectedPhase: currentPhase,
386
+ autoDetected: true,
387
+ guidance: getTDDGuidance(currentPhase),
388
+ summary: `Default ${currentPhase} phase (use --phase flag for explicit phase control)`,
389
+ };
390
+ }
391
+
392
+ // Validate REFACTOR requires passing tests
393
+ if (phase === 'REFACTOR' && testsPassing === false) {
394
+ return {
395
+ success: false,
396
+ error: 'Cannot proceed to REFACTOR phase: tests are failing. Complete GREEN phase first.',
397
+ };
398
+ }
399
+
400
+ // Execute based on phase
401
+ const result = {
402
+ success: true,
403
+ phase: currentPhase,
404
+ guidance: getTDDGuidance(currentPhase),
405
+ };
406
+
407
+ if (currentPhase === 'RED') {
408
+ result.nextPhase = 'GREEN';
409
+ result.summary = 'Write failing tests for the feature';
410
+ } else if (currentPhase === 'GREEN') {
411
+ // Run tests to check status
412
+ const testResults = await runTests();
413
+ result.testResults = testResults;
414
+ result.success = testResults.success; // Reflect actual test outcome
415
+ if (!testResults.success) {
416
+ result.error = testResults.error || 'Tests failed';
417
+ }
418
+ result.nextPhase = 'REFACTOR';
419
+ result.summary = 'Implement feature to make tests pass';
420
+ } else if (currentPhase === 'REFACTOR') {
421
+ result.nextPhase = null; // Cycle complete
422
+ result.summary = 'Improve code quality while keeping tests green';
423
+ }
424
+
425
+ return result;
426
+ } catch (error) {
427
+ return {
428
+ success: false,
429
+ error: `Failed to execute dev command: ${error.message}`,
430
+ };
431
+ }
432
+ }
433
+
434
+ /**
435
+ * Decision gate route constants
436
+ *
437
+ * Routes used by the /dev workflow decision gate:
438
+ * - PROCEED: Score 0-3, implementer makes the decision and documents it
439
+ * - SPEC-REVIEWER: Score 4-7, route to spec reviewer subagent
440
+ * - BLOCKED: Score 8+ or mandatory override, surface to developer
441
+ */
442
+ const DECISION_ROUTES = {
443
+ PROCEED: 'PROCEED',
444
+ SPEC_REVIEWER: 'SPEC-REVIEWER',
445
+ BLOCKED: 'BLOCKED',
446
+ };
447
+
448
+ /**
449
+ * Calculate the decision gate route based on a 7-dimension scoring rubric
450
+ *
451
+ * Each dimension is scored 0 (No), 1 (Possibly), or 2 (Yes):
452
+ * 1. Files affected beyond the current task?
453
+ * 2. Changes a function signature or public export?
454
+ * 3. Changes a shared module used by other tasks?
455
+ * 4. Changes or touches persistent data or schema?
456
+ * 5. Changes user-visible behavior not discussed in design doc?
457
+ * 6. Affects auth, permissions, or data exposure? (security — mandatory override if scored 2)
458
+ * 7. Hard to reverse without cascading changes to other files?
459
+ *
460
+ * Score routing:
461
+ * - 0-3: PROCEED
462
+ * - 4-7: SPEC-REVIEWER
463
+ * - 8+: BLOCKED
464
+ *
465
+ * Mandatory override: dimension 6 (index 5) scored 2 → always BLOCKED
466
+ *
467
+ * @param {number} score - Total score (0-14)
468
+ * @param {number[]} dimensions - Array of 7 scores (each 0, 1, or 2)
469
+ * @returns {{route: string, score: number, mandatoryOverride?: boolean}} Routing result
470
+ * @example
471
+ * const result = calculateDecisionRoute(2, [1, 1, 0, 0, 0, 0, 0]);
472
+ * console.log(result.route); // 'PROCEED'
473
+ *
474
+ * const securityBlocked = calculateDecisionRoute(2, [0, 0, 0, 0, 0, 2, 0]);
475
+ * console.log(securityBlocked.route); // 'BLOCKED'
476
+ * console.log(securityBlocked.mandatoryOverride); // true
477
+ */
478
+ function calculateDecisionRoute(score, dimensions) {
479
+ // Mandatory override: security dimension (dimension 6, zero-indexed as index 5) scored 2
480
+ const securityScore = Array.isArray(dimensions) ? dimensions[5] : undefined;
481
+ if (securityScore === 2) {
482
+ return { route: DECISION_ROUTES.BLOCKED, score, mandatoryOverride: true };
483
+ }
484
+
485
+ // Derive authoritative total from dimensions when available to prevent score/dimensions drift
486
+ const effectiveScore =
487
+ Array.isArray(dimensions) && dimensions.length === 7
488
+ ? dimensions.reduce((a, b) => a + b, 0)
489
+ : score;
490
+
491
+ // Score-based routing
492
+ if (effectiveScore <= 3) {
493
+ return { route: DECISION_ROUTES.PROCEED, score: effectiveScore };
494
+ }
495
+
496
+ if (effectiveScore <= 7) {
497
+ return { route: DECISION_ROUTES.SPEC_REVIEWER, score: effectiveScore };
498
+ }
499
+
500
+ return { route: DECISION_ROUTES.BLOCKED, score: effectiveScore };
501
+ }
502
+
503
+ module.exports = {
504
+ detectTDDPhase,
505
+ identifyFilePairs,
506
+ runTests,
507
+ getTDDGuidance,
508
+ generateCommitMessage,
509
+ identifyParallelWork,
510
+ executeDev,
511
+ calculateDecisionRoute,
512
+ DECISION_ROUTES,
513
+ };