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,602 @@
1
+ /**
2
+ * Validate Command - Validation Orchestration
3
+ * Runs all validation checks (type/lint/security/tests) in sequence
4
+ *
5
+ * Security: Uses execFileSync for command execution to prevent injection
6
+ * Validation: Orchestrates multiple check types with configurable options
7
+ *
8
+ * @module commands/validate
9
+ */
10
+
11
+ const { execFileSync } = require('node:child_process');
12
+ const fs = require('node:fs');
13
+ const path = require('node:path');
14
+
15
+ // Constants
16
+ const CHECK_TYPES = {
17
+ TYPE_CHECK: 'typeCheck',
18
+ LINT: 'lint',
19
+ SECURITY: 'security',
20
+ TESTS: 'tests',
21
+ };
22
+
23
+ function getExecOptions() {
24
+ return { encoding: 'utf8', cwd: process.cwd(), timeout: 120000 };
25
+ }
26
+
27
+ const ERROR_PATTERNS = {
28
+ COMMAND_NOT_FOUND: ['ENOENT', 'not found'],
29
+ NO_LOCK_FILE: ['requires an existing', 'package-lock'],
30
+ };
31
+
32
+ /**
33
+ * Check if error indicates command not found
34
+ * @private
35
+ */
36
+ function isCommandNotFound(error) {
37
+ return ERROR_PATTERNS.COMMAND_NOT_FOUND.some(pattern =>
38
+ error.message.includes(pattern),
39
+ );
40
+ }
41
+
42
+ /**
43
+ * Parse number from regex match
44
+ * @private
45
+ */
46
+ function parseNumber(match, index = 1, defaultValue = 0) {
47
+ return match ? Number.parseInt(match[index], 10) : defaultValue;
48
+ }
49
+
50
+ /**
51
+ * Get status label for check result
52
+ * @private
53
+ */
54
+ function getCheckStatus(check) {
55
+ if (!check) return null;
56
+ if (check.skipped) return 'SKIPPED';
57
+ return check.success ? 'PASS' : 'FAIL';
58
+ }
59
+
60
+ /**
61
+ * Parse vulnerability counts from audit output
62
+ * @private
63
+ */
64
+ function parseVulnerabilities(output) {
65
+ return {
66
+ critical: parseNumber(/(\d+) critical/i.exec(output)), // NOSONAR S5852 - bounded \d+ pattern, no backtracking
67
+ high: parseNumber(/(\d+) high/i.exec(output)), // NOSONAR S5852 - bounded \d+ pattern, no backtracking
68
+ moderate: parseNumber(/(\d+) moderate/i.exec(output)), // NOSONAR S5852 - bounded \d+ pattern, no backtracking
69
+ low: parseNumber(/(\d+) low/i.exec(output)), // NOSONAR S5852 - bounded \d+ pattern, no backtracking
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Run TypeScript type checking
75
+ * Executes tsc --noEmit if TypeScript is configured
76
+ *
77
+ * @returns {Promise<{success: boolean, duration: number, errors?: number, skipped?: boolean, message?: string}>} Type check result
78
+ * @example
79
+ * const result = await runTypeCheck();
80
+ * if (!result.success) console.log(`Type errors: ${result.errors}`);
81
+ */
82
+ async function runTypeCheck() {
83
+ const startTime = Date.now();
84
+
85
+ // Check if TypeScript is configured
86
+ const tsconfigPath = path.join(process.cwd(), 'tsconfig.json');
87
+ if (!fs.existsSync(tsconfigPath)) {
88
+ return {
89
+ success: true,
90
+ skipped: true,
91
+ duration: Date.now() - startTime,
92
+ message: 'TypeScript not configured (no tsconfig.json)',
93
+ };
94
+ }
95
+
96
+ try {
97
+ // Run tsc --noEmit for type checking only
98
+ execFileSync('tsc', ['--noEmit'], getExecOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
99
+
100
+ return {
101
+ success: true,
102
+ duration: Date.now() - startTime,
103
+ errors: 0,
104
+ message: 'Type checking passed',
105
+ };
106
+ } catch (error) {
107
+ // Check for timeout
108
+ if (error.killed && error.signal === 'SIGTERM') {
109
+ return {
110
+ success: false,
111
+ duration: Date.now() - startTime,
112
+ message: 'Type check timed out after 2 minutes',
113
+ };
114
+ }
115
+
116
+ // tsc not found or type errors
117
+ if (isCommandNotFound(error)) {
118
+ return {
119
+ success: true,
120
+ skipped: true,
121
+ duration: Date.now() - startTime,
122
+ message: 'TypeScript compiler not found (skipping type check)',
123
+ };
124
+ }
125
+
126
+ // Parse error output for error count
127
+ const errorMatch = /Found (\d+) error/.exec(error.stdout);
128
+ const errors = parseNumber(errorMatch, 1, 1);
129
+
130
+ return {
131
+ success: false,
132
+ duration: Date.now() - startTime,
133
+ errors,
134
+ message: `Type checking failed: ${errors} error(s) found`,
135
+ output: error.stdout || error.message,
136
+ };
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Run ESLint
142
+ * Executes eslint . to check code quality
143
+ *
144
+ * @returns {Promise<{success: boolean, duration: number, warnings?: number, errors?: number, message?: string}>} Lint result
145
+ * @example
146
+ * const result = await runLint();
147
+ * console.log(`Warnings: ${result.warnings}, Errors: ${result.errors}`);
148
+ */
149
+ async function runLint() {
150
+ const startTime = Date.now();
151
+
152
+ try {
153
+ // Run eslint with no output (exit code determines success)
154
+ execFileSync('eslint', ['.'], getExecOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
155
+
156
+ return {
157
+ success: true,
158
+ duration: Date.now() - startTime,
159
+ warnings: 0,
160
+ errors: 0,
161
+ message: 'Linting passed (no errors)',
162
+ };
163
+ } catch (error) {
164
+ // Check for timeout
165
+ if (error.killed && error.signal === 'SIGTERM') {
166
+ return {
167
+ success: false,
168
+ duration: Date.now() - startTime,
169
+ message: 'Lint check timed out after 2 minutes',
170
+ };
171
+ }
172
+
173
+ // eslint not found - skip gracefully (consistent with tsc behavior)
174
+ if (isCommandNotFound(error)) {
175
+ return {
176
+ success: true,
177
+ skipped: true,
178
+ duration: Date.now() - startTime,
179
+ message: 'ESLint not found (skipping lint check). Install with: bun add -D eslint',
180
+ };
181
+ }
182
+
183
+ // Parse eslint output for warnings/errors
184
+ const output = error.stdout || error.message;
185
+ const problemsMatch = /(\d+) problems? \((\d+) errors?, (\d+) warnings?\)/.exec(output); // NOSONAR S5852 - bounded quantifiers, no backtracking
186
+
187
+ const errors = parseNumber(problemsMatch, 2, 0);
188
+ const warnings = parseNumber(problemsMatch, 3, 0);
189
+
190
+ // Only fail if there are errors (warnings are acceptable)
191
+ const success = errors === 0;
192
+
193
+ return {
194
+ success,
195
+ duration: Date.now() - startTime,
196
+ warnings,
197
+ errors,
198
+ message: success
199
+ ? `Linting passed with ${warnings} warning(s)`
200
+ : `Linting failed: ${errors} error(s), ${warnings} warning(s)`,
201
+ output,
202
+ };
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Run security audit
208
+ * Executes bun audit or npm audit to check for vulnerabilities
209
+ *
210
+ * @returns {Promise<{success: boolean, duration: number, vulnerabilities?: {critical: number, high: number, moderate: number, low: number}, message?: string}>} Security scan result
211
+ * @example
212
+ * const result = await runSecurityScan();
213
+ * if (result.vulnerabilities.critical > 0) console.log('Critical vulnerabilities found!');
214
+ */
215
+ async function runSecurityScan() { // NOSONAR S3776
216
+ const startTime = Date.now();
217
+
218
+ try {
219
+ // Try bun audit first (faster and works without package-lock.json)
220
+ const result = execFileSync('bun', ['audit'], getExecOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
221
+
222
+ // Parse bun audit output
223
+ const vulnerabilities = parseVulnerabilities(result);
224
+ const totalVulns = Object.values(vulnerabilities).reduce((sum, count) => sum + count, 0);
225
+ const hasCritical = vulnerabilities.critical > 0 || vulnerabilities.high > 0;
226
+
227
+ return {
228
+ success: !hasCritical, // Fail only on critical/high
229
+ duration: Date.now() - startTime,
230
+ vulnerabilities,
231
+ message: totalVulns === 0
232
+ ? 'No vulnerabilities found'
233
+ : `Found ${totalVulns} ${totalVulns === 1 ? 'vulnerability' : 'vulnerabilities'} (${vulnerabilities.critical} critical, ${vulnerabilities.high} high)`, // NOSONAR S3358 - simple format string
234
+ };
235
+ } catch (error) {
236
+ // Check if bun audit timed out - don't retry with npm audit
237
+ if (error.killed && error.signal === 'SIGTERM') {
238
+ return {
239
+ success: false,
240
+ duration: Date.now() - startTime,
241
+ message: 'Security audit timed out after 2 minutes',
242
+ };
243
+ }
244
+ // bun audit exits non-zero when vulnerabilities are found; stdout still contains the data
245
+ if (error.stdout) {
246
+ const vulnerabilities = parseVulnerabilities(error.stdout);
247
+ const totalVulns = Object.values(vulnerabilities).reduce((sum, count) => sum + count, 0);
248
+ const hasCritical = vulnerabilities.critical > 0 || vulnerabilities.high > 0;
249
+ return {
250
+ success: !hasCritical,
251
+ duration: Date.now() - startTime,
252
+ vulnerabilities,
253
+ message: totalVulns === 0
254
+ ? 'No vulnerabilities found'
255
+ : `Found ${totalVulns} ${totalVulns === 1 ? 'vulnerability' : 'vulnerabilities'} (${vulnerabilities.critical} critical, ${vulnerabilities.high} high)`, // NOSONAR S3358 - simple format string
256
+ };
257
+ }
258
+ // bun audit failed without output, try npm audit
259
+ // Note: npm audit exits non-zero for ANY vulnerability (including low/moderate).
260
+ // Capture stdout from the error object to parse JSON output even on non-zero exit.
261
+ try {
262
+ let npmRawOutput = null;
263
+ try {
264
+ npmRawOutput = execFileSync('npm', ['audit', '--json', '--production'], getExecOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
265
+ } catch (npmExitError) {
266
+ if (npmExitError.killed && npmExitError.signal === 'SIGTERM') {
267
+ return {
268
+ success: false,
269
+ duration: Date.now() - startTime,
270
+ message: 'Security audit timed out after 2 minutes',
271
+ };
272
+ }
273
+ // npm audit exits non-zero when it finds any vulnerability - stdout still has JSON
274
+ if (npmExitError.stdout) {
275
+ npmRawOutput = npmExitError.stdout;
276
+ } else {
277
+ throw npmExitError; // Genuine failure (not found, no lock file, etc.)
278
+ }
279
+ }
280
+
281
+ if (npmRawOutput !== null) {
282
+ let vulnerabilities;
283
+ try {
284
+ const auditData = JSON.parse(npmRawOutput);
285
+ const meta = auditData.metadata?.vulnerabilities || auditData.vulnerabilities || {};
286
+ vulnerabilities = {
287
+ critical: meta.critical || 0,
288
+ high: meta.high || 0,
289
+ moderate: meta.moderate || 0,
290
+ low: meta.low || 0,
291
+ };
292
+ } catch { // JSON parse failed — npm audit returned plain text
293
+ return {
294
+ success: true,
295
+ skipped: true,
296
+ duration: Date.now() - startTime,
297
+ message: 'Security audit skipped (npm audit returned non-JSON output)',
298
+ };
299
+ }
300
+ const totalVulns = Object.values(vulnerabilities).reduce((sum, n) => sum + n, 0);
301
+ const hasCritical = vulnerabilities.critical > 0 || vulnerabilities.high > 0;
302
+ const vulnSuffix = totalVulns === 1 ? 'y' : 'ies';
303
+ return {
304
+ success: !hasCritical,
305
+ duration: Date.now() - startTime,
306
+ vulnerabilities,
307
+ message: totalVulns === 0
308
+ ? 'No vulnerabilities found (npm audit)'
309
+ : `Found ${totalVulns} vulnerabilit${vulnSuffix} (${vulnerabilities.critical} critical, ${vulnerabilities.high} high)`,
310
+ };
311
+ }
312
+ // Defensive fallback: npmRawOutput was null (unreachable in practice)
313
+ return {
314
+ success: true,
315
+ skipped: true,
316
+ duration: Date.now() - startTime,
317
+ message: 'Security audit skipped (no audit output available)',
318
+ };
319
+ } catch (npmError) {
320
+ // Check for timeout first
321
+ if (npmError.killed && npmError.signal === 'SIGTERM') {
322
+ return {
323
+ success: false,
324
+ duration: Date.now() - startTime,
325
+ message: 'Security audit timed out after 2 minutes',
326
+ };
327
+ }
328
+
329
+ // Both failed - check if it's because tools aren't available
330
+ const bunNotFound = isCommandNotFound(error);
331
+ const noLockFile = ERROR_PATTERNS.NO_LOCK_FILE.some(pattern =>
332
+ npmError.message?.includes(pattern),
333
+ );
334
+
335
+ if (bunNotFound || noLockFile) {
336
+ return {
337
+ success: true,
338
+ skipped: true,
339
+ duration: Date.now() - startTime,
340
+ message: 'Security audit skipped (no package manager audit available)',
341
+ };
342
+ }
343
+
344
+ // Audit found issues
345
+ return {
346
+ success: false,
347
+ duration: Date.now() - startTime,
348
+ message: 'Security audit failed. Run: npm audit or bun audit to see details',
349
+ output: npmError.message || error.message,
350
+ };
351
+ }
352
+ }
353
+ }
354
+
355
+ /**
356
+ * Run all tests
357
+ * Executes bun test to run the test suite
358
+ *
359
+ * @returns {Promise<{success: boolean, duration: number, passed: number, failed: number, total: number, message?: string}>} Test execution result
360
+ * @example
361
+ * const result = await runAllTests();
362
+ * console.log(`${result.passed}/${result.total} tests passed`);
363
+ */
364
+ async function runAllTests() {
365
+ const startTime = Date.now();
366
+
367
+ try {
368
+ const result = execFileSync('bun', ['test'], getExecOptions()); // NOSONAR S4036 - hardcoded CLI command, no user input, developer tool context
369
+
370
+ // Parse bun test output
371
+ const passed = parseNumber(/(\d+) pass/.exec(result)); // NOSONAR S5852 - bounded \d+ pattern
372
+ const failed = parseNumber(/(\d+) fail/.exec(result)); // NOSONAR S5852 - bounded \d+ pattern
373
+ const skipped = parseNumber(/(\d+) skip/.exec(result)); // NOSONAR S5852 - bounded \d+ pattern
374
+ const total = parseNumber(/Ran (\d+) tests/.exec(result), 1, passed + failed + skipped);
375
+
376
+ return {
377
+ success: failed === 0,
378
+ duration: Date.now() - startTime,
379
+ passed,
380
+ failed,
381
+ total,
382
+ message: failed === 0
383
+ ? `All ${total} tests passed`
384
+ : `${failed}/${total} tests failed`,
385
+ };
386
+ } catch (error) {
387
+ // Check for timeout
388
+ if (error.killed && error.signal === 'SIGTERM') {
389
+ return {
390
+ success: false,
391
+ duration: Date.now() - startTime,
392
+ passed: 0,
393
+ failed: 0,
394
+ total: 0,
395
+ message: 'Test execution timed out after 2 minutes',
396
+ };
397
+ }
398
+
399
+ // Test execution failed
400
+ if (isCommandNotFound(error)) {
401
+ return {
402
+ success: true,
403
+ skipped: true,
404
+ duration: Date.now() - startTime,
405
+ passed: 0,
406
+ failed: 0,
407
+ total: 0,
408
+ message: 'Tests skipped: bun not found.',
409
+ };
410
+ }
411
+
412
+ // Parse test failures from output
413
+ const output = error.stdout || error.message;
414
+ const passed = parseNumber(/(\d+) pass/.exec(output)); // NOSONAR S5852 - bounded \d+ pattern
415
+ const failed = parseNumber(/(\d+) fail/.exec(output), 1, 1); // NOSONAR S5852 - bounded \d+ pattern
416
+ const skipped = parseNumber(/(\d+) skip/.exec(output)); // NOSONAR S5852 - bounded \d+ pattern
417
+ const total = passed + failed + skipped;
418
+
419
+ return {
420
+ success: false,
421
+ duration: Date.now() - startTime,
422
+ passed,
423
+ failed,
424
+ total,
425
+ message: `${failed}/${total} tests failed`,
426
+ output,
427
+ };
428
+ }
429
+ }
430
+
431
+ /**
432
+ * Execute all checks
433
+ * Orchestrates type checking, linting, security scanning, and tests
434
+ *
435
+ * @param {{skip?: string[], continueOnError?: boolean}} [options] - Execution options
436
+ * @returns {Promise<{
437
+ * success: boolean,
438
+ * checks: {
439
+ * typeCheck?: object,
440
+ * lint?: object,
441
+ * security?: object,
442
+ * tests?: object
443
+ * },
444
+ * summary: string,
445
+ * failedChecks?: string[],
446
+ * errors?: string[]
447
+ * }>} Execution result
448
+ * @example
449
+ * const result = await executeValidate({ skip: ['typeCheck'] });
450
+ * console.log(result.summary);
451
+ */
452
+ async function executeValidate(options = {}) { // NOSONAR S3776
453
+ const { skip = [], continueOnError = true } = options || {};
454
+
455
+ const checks = {};
456
+ const failedChecks = [];
457
+ const errors = [];
458
+ const startTime = Date.now();
459
+
460
+ // 1. Type checking
461
+ if (!skip.includes(CHECK_TYPES.TYPE_CHECK)) {
462
+ try {
463
+ checks.typeCheck = await runTypeCheck();
464
+ if (!checks.typeCheck.success && !checks.typeCheck.skipped) {
465
+ failedChecks.push(CHECK_TYPES.TYPE_CHECK);
466
+ if (!continueOnError) {
467
+ return buildResult(checks, failedChecks, errors, startTime);
468
+ }
469
+ }
470
+ } catch (error) {
471
+ errors.push(`Type check error: ${error.message}`);
472
+ checks.typeCheck = { success: false, message: error.message };
473
+ }
474
+ }
475
+
476
+ // 2. Linting
477
+ if (!skip.includes(CHECK_TYPES.LINT)) {
478
+ try {
479
+ checks.lint = await runLint();
480
+ if (!checks.lint.success && !checks.lint.skipped) {
481
+ failedChecks.push(CHECK_TYPES.LINT);
482
+ if (!continueOnError) {
483
+ return buildResult(checks, failedChecks, errors, startTime);
484
+ }
485
+ }
486
+ } catch (error) {
487
+ errors.push(`Lint error: ${error.message}`);
488
+ checks.lint = { success: false, message: error.message };
489
+ }
490
+ }
491
+
492
+ // 3. Security scanning
493
+ if (!skip.includes(CHECK_TYPES.SECURITY)) {
494
+ try {
495
+ checks.security = await runSecurityScan();
496
+ if (!checks.security.success && !checks.security.skipped) {
497
+ failedChecks.push(CHECK_TYPES.SECURITY);
498
+ if (!continueOnError) {
499
+ return buildResult(checks, failedChecks, errors, startTime);
500
+ }
501
+ }
502
+ } catch (error) {
503
+ errors.push(`Security scan error: ${error.message}`);
504
+ checks.security = { success: false, message: error.message };
505
+ }
506
+ }
507
+
508
+ // 4. Tests
509
+ if (!skip.includes(CHECK_TYPES.TESTS)) {
510
+ try {
511
+ checks.tests = await runAllTests();
512
+ if (!checks.tests.success && !checks.tests.skipped) {
513
+ failedChecks.push(CHECK_TYPES.TESTS);
514
+ if (!continueOnError) {
515
+ return buildResult(checks, failedChecks, errors, startTime);
516
+ }
517
+ }
518
+ } catch (error) {
519
+ errors.push(`Test execution error: ${error.message}`);
520
+ checks.tests = { success: false, message: error.message };
521
+ }
522
+ }
523
+
524
+ return buildResult(checks, failedChecks, errors, startTime);
525
+ }
526
+
527
+ /**
528
+ * Build final result object
529
+ * @private
530
+ */
531
+ function buildResult(checks, failedChecks, errors, startTime) {
532
+ const success = failedChecks.length === 0 && errors.length === 0;
533
+ const duration = Date.now() - startTime;
534
+
535
+ // Build summary using getCheckStatus helper
536
+ const checkResults = [];
537
+ const checkLabels = {
538
+ typeCheck: 'Type',
539
+ lint: 'Lint',
540
+ security: 'Security',
541
+ tests: 'Tests',
542
+ };
543
+
544
+ for (const [key, label] of Object.entries(checkLabels)) {
545
+ const status = getCheckStatus(checks[key]);
546
+ if (status) {
547
+ checkResults.push(`${label}: ${status}`);
548
+ }
549
+ }
550
+
551
+ const summary = success
552
+ ? `All checks passed (${checkResults.join(', ')})`
553
+ : `Checks failed: ${failedChecks.join(', ')}`;
554
+
555
+ const result = {
556
+ success,
557
+ checks,
558
+ summary,
559
+ duration,
560
+ };
561
+
562
+ if (failedChecks.length > 0) {
563
+ result.failedChecks = failedChecks;
564
+ }
565
+
566
+ if (errors.length > 0) {
567
+ result.errors = errors;
568
+ }
569
+
570
+ return result;
571
+ }
572
+
573
+ /**
574
+ * Execute 4-phase debug mode gate when validation fails.
575
+ * Always starts at D1 (Reproduce); D2 (Root-cause) → D3 (Fix) → D4 (Verify)
576
+ * progression is driven by validate.md workflow instructions, not by this function.
577
+ * This function only gates on escalation (3+ attempts) and weak completion claims.
578
+ *
579
+ * @param {{fixAttempts?: number, claim?: string}} [options] - Debug options
580
+ * @returns {{escalate: boolean, phase?: string, message?: string, valid?: boolean, reason?: string}} Debug result
581
+ */
582
+ function executeDebugMode({ fixAttempts = 0, claim } = {}) {
583
+ // Escalation takes priority — even if claim is also weak, escalation fires first.
584
+ if (fixAttempts >= 3) {
585
+ return { escalate: true, message: 'STOP: 3+ fixes. Question architecture before Fix #4.' };
586
+ }
587
+ // If claim uses forbidden phrases from dev.md HARD-GATE (exact phrases, not bare words)
588
+ if (claim && /should pass|looks good|seems to work/i.test(claim)) {
589
+ return { valid: false, reason: 'No fresh verification evidence — run validation fresh' };
590
+ }
591
+ // Otherwise start at Phase D1
592
+ return { escalate: false, phase: 'D1' };
593
+ }
594
+
595
+ module.exports = {
596
+ runTypeCheck,
597
+ runLint,
598
+ runSecurityScan,
599
+ runAllTests,
600
+ executeValidate,
601
+ executeDebugMode,
602
+ };