wogiflow 2.4.2 → 2.4.3

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 (196) hide show
  1. package/.claude/docs/claude-code-compatibility.md +27 -0
  2. package/.claude/settings.json +1 -1
  3. package/.workflow/models/registry.json +1 -1
  4. package/package.json +4 -4
  5. package/scripts/MEMORY-ARCHITECTURE.md +1 -1
  6. package/scripts/base-workflow-step.js +136 -0
  7. package/scripts/flow-adaptive-learning.js +8 -9
  8. package/scripts/flow-aggregate.js +11 -6
  9. package/scripts/flow-api-index.js +4 -6
  10. package/scripts/flow-assumption-detector.js +0 -2
  11. package/scripts/flow-audit.js +15 -2
  12. package/scripts/flow-auto-context.js +8 -12
  13. package/scripts/flow-auto-learn.js +49 -49
  14. package/scripts/flow-background.js +5 -6
  15. package/scripts/flow-bridge-state.js +8 -10
  16. package/scripts/flow-bulk-loop.js +1 -3
  17. package/scripts/flow-bulk-orchestrator.js +1 -3
  18. package/scripts/flow-cascade-completion.js +0 -2
  19. package/scripts/flow-cascade.js +4 -4
  20. package/scripts/flow-checkpoint.js +10 -13
  21. package/scripts/flow-code-intelligence.js +10 -12
  22. package/scripts/flow-community-sync.js +4 -4
  23. package/scripts/flow-community.js +12 -20
  24. package/scripts/flow-config-defaults.js +0 -2
  25. package/scripts/flow-config-interactive.js +9 -5
  26. package/scripts/flow-config-loader.js +49 -92
  27. package/scripts/flow-config-substitution.js +0 -2
  28. package/scripts/flow-context-estimator.js +4 -4
  29. package/scripts/flow-context-init.js +10 -12
  30. package/scripts/flow-context-manager.js +0 -2
  31. package/scripts/flow-context-scoring.js +2 -2
  32. package/scripts/flow-contract-scan.js +6 -9
  33. package/scripts/flow-correct.js +29 -27
  34. package/scripts/flow-correction-detector.js +5 -1
  35. package/scripts/flow-damage-control.js +47 -54
  36. package/scripts/flow-decisions-merge.js +4 -14
  37. package/scripts/flow-diff.js +5 -8
  38. package/scripts/flow-done-gates.js +786 -0
  39. package/scripts/flow-done-report.js +123 -0
  40. package/scripts/flow-done.js +71 -717
  41. package/scripts/flow-entropy-monitor.js +1 -3
  42. package/scripts/flow-eval.js +5 -5
  43. package/scripts/flow-extraction-review.js +1 -0
  44. package/scripts/flow-failure-categories.js +0 -2
  45. package/scripts/flow-figma-confirm.js +5 -9
  46. package/scripts/flow-figma-generate.js +8 -10
  47. package/scripts/flow-figma-index.js +8 -10
  48. package/scripts/flow-figma-match.js +3 -5
  49. package/scripts/flow-figma-mcp-server.js +2 -4
  50. package/scripts/flow-figma-orchestrator.js +2 -3
  51. package/scripts/flow-figma-registry.js +2 -3
  52. package/scripts/flow-framework-resolver.js +0 -2
  53. package/scripts/flow-function-index.js +4 -6
  54. package/scripts/flow-gate-confidence.js +2 -2
  55. package/scripts/flow-gitignore.js +0 -2
  56. package/scripts/flow-guided-edit.js +5 -6
  57. package/scripts/flow-health.js +5 -6
  58. package/scripts/flow-hook-errors.js +6 -0
  59. package/scripts/flow-hook-status.js +263 -0
  60. package/scripts/flow-hooks.js +17 -29
  61. package/scripts/flow-http-client.js +9 -8
  62. package/scripts/flow-hybrid-interactive.js +7 -12
  63. package/scripts/flow-hybrid-test.js +12 -13
  64. package/scripts/flow-instruction-richness.js +1 -1
  65. package/scripts/flow-io.js +21 -4
  66. package/scripts/flow-knowledge-router.js +9 -3
  67. package/scripts/flow-learning-orchestrator.js +318 -13
  68. package/scripts/flow-links.js +5 -7
  69. package/scripts/flow-long-input-association.js +275 -0
  70. package/scripts/flow-long-input-chunking.js +1 -0
  71. package/scripts/flow-long-input-cli.js +0 -2
  72. package/scripts/flow-long-input-complexity.js +0 -2
  73. package/scripts/flow-long-input-constants.js +0 -2
  74. package/scripts/flow-long-input-contradictions.js +351 -0
  75. package/scripts/flow-long-input-detection.js +0 -2
  76. package/scripts/flow-long-input-passes.js +885 -0
  77. package/scripts/flow-long-input-stories.js +1 -1
  78. package/scripts/flow-long-input-voice.js +0 -2
  79. package/scripts/flow-long-input.js +425 -3005
  80. package/scripts/flow-loop-retry-learning.js +2 -3
  81. package/scripts/flow-lsp.js +3 -3
  82. package/scripts/flow-mcp-docs.js +3 -4
  83. package/scripts/flow-memory-db.js +6 -8
  84. package/scripts/flow-memory-sync.js +18 -11
  85. package/scripts/flow-metrics.js +1 -2
  86. package/scripts/flow-model-adapter.js +2 -3
  87. package/scripts/flow-model-config.js +72 -104
  88. package/scripts/flow-model-router.js +2 -2
  89. package/scripts/flow-model-types.js +0 -2
  90. package/scripts/flow-multi-approach.js +5 -6
  91. package/scripts/flow-orchestrate-context.js +3 -7
  92. package/scripts/flow-orchestrate-rollback.js +3 -8
  93. package/scripts/flow-orchestrate-state.js +8 -14
  94. package/scripts/flow-orchestrate-templates.js +2 -6
  95. package/scripts/flow-orchestrate-validator.js +5 -9
  96. package/scripts/flow-orchestrate.js +126 -103
  97. package/scripts/flow-output.js +0 -2
  98. package/scripts/flow-parallel.js +1 -1
  99. package/scripts/flow-paths.js +23 -2
  100. package/scripts/flow-pattern-enforcer.js +30 -28
  101. package/scripts/flow-pattern-extractor.js +3 -4
  102. package/scripts/flow-pending.js +0 -2
  103. package/scripts/flow-permissions.js +2 -3
  104. package/scripts/flow-plugin-registry.js +10 -12
  105. package/scripts/flow-prd-manager.js +1 -1
  106. package/scripts/flow-progress.js +7 -9
  107. package/scripts/flow-prompt-composer.js +3 -3
  108. package/scripts/flow-prompt-template.js +2 -2
  109. package/scripts/flow-providers.js +7 -4
  110. package/scripts/flow-registry-manager.js +7 -12
  111. package/scripts/flow-regression.js +9 -11
  112. package/scripts/flow-roadmap.js +2 -2
  113. package/scripts/flow-run-trace.js +16 -15
  114. package/scripts/flow-safety.js +2 -5
  115. package/scripts/flow-scanner-base.js +5 -7
  116. package/scripts/flow-scenario-engine.js +1 -5
  117. package/scripts/flow-security.js +29 -0
  118. package/scripts/flow-session-end.js +32 -41
  119. package/scripts/flow-session-learning.js +53 -49
  120. package/scripts/flow-setup-hooks.js +2 -3
  121. package/scripts/flow-skill-create.js +7 -12
  122. package/scripts/flow-skill-generator.js +12 -16
  123. package/scripts/flow-skill-learn.js +17 -8
  124. package/scripts/flow-skill-matcher.js +1 -2
  125. package/scripts/flow-spec-generator.js +2 -4
  126. package/scripts/flow-stack-wizard.js +5 -7
  127. package/scripts/flow-standards-learner.js +35 -16
  128. package/scripts/flow-start.js +2 -0
  129. package/scripts/flow-stats-collector.js +2 -2
  130. package/scripts/flow-status.js +10 -10
  131. package/scripts/flow-statusline-setup.js +2 -2
  132. package/scripts/flow-step-changelog.js +2 -3
  133. package/scripts/flow-step-comments.js +66 -81
  134. package/scripts/flow-step-complexity.js +50 -70
  135. package/scripts/flow-step-coverage.js +3 -5
  136. package/scripts/flow-step-knowledge.js +2 -3
  137. package/scripts/flow-step-pr-tests.js +64 -74
  138. package/scripts/flow-step-regression.js +3 -5
  139. package/scripts/flow-step-review.js +86 -103
  140. package/scripts/flow-step-security.js +111 -121
  141. package/scripts/flow-step-silent-failures.js +56 -83
  142. package/scripts/flow-step-simplifier.js +52 -70
  143. package/scripts/flow-story.js +4 -7
  144. package/scripts/flow-strict-adherence.js +3 -4
  145. package/scripts/flow-task-checkpoint.js +36 -5
  146. package/scripts/flow-task-enforcer.js +2 -24
  147. package/scripts/flow-tech-debt.js +1 -1
  148. package/scripts/flow-template-extractor.js +1 -0
  149. package/scripts/flow-templates.js +11 -13
  150. package/scripts/flow-test-api.js +9 -13
  151. package/scripts/flow-test-discovery.js +1 -1
  152. package/scripts/flow-test-generate.js +5 -9
  153. package/scripts/flow-test-integrity.js +3 -7
  154. package/scripts/flow-test-ui.js +5 -9
  155. package/scripts/flow-testing-deps.js +1 -3
  156. package/scripts/flow-tiered-learning.js +4 -4
  157. package/scripts/flow-todowrite-sync.js +1 -1
  158. package/scripts/flow-tokens.js +0 -2
  159. package/scripts/flow-verification-profile.js +6 -10
  160. package/scripts/flow-verify.js +12 -16
  161. package/scripts/flow-version-check.js +4 -12
  162. package/scripts/flow-webmcp-generator.js +3 -5
  163. package/scripts/flow-workflow-steps.js +0 -2
  164. package/scripts/flow-workflow.js +9 -11
  165. package/scripts/hooks/adapters/claude-code.js +2 -0
  166. package/scripts/hooks/core/config-change.js +1 -0
  167. package/scripts/hooks/core/extension-registry.js +0 -2
  168. package/scripts/hooks/core/instructions-loaded.js +1 -1
  169. package/scripts/hooks/core/observation-capture.js +5 -5
  170. package/scripts/hooks/core/phase-gate.js +5 -0
  171. package/scripts/hooks/core/post-compact.js +1 -12
  172. package/scripts/hooks/core/research-gate.js +2 -12
  173. package/scripts/hooks/core/routing-gate.js +6 -0
  174. package/scripts/hooks/core/task-completed.js +12 -0
  175. package/scripts/hooks/core/worktree-lifecycle.js +1 -1
  176. package/scripts/hooks/entry/claude-code/config-change.js +6 -29
  177. package/scripts/hooks/entry/claude-code/instructions-loaded.js +5 -30
  178. package/scripts/hooks/entry/claude-code/post-compact.js +4 -31
  179. package/scripts/hooks/entry/claude-code/post-tool-use.js +121 -172
  180. package/scripts/hooks/entry/claude-code/pre-tool-use.js +260 -361
  181. package/scripts/hooks/entry/claude-code/session-end.js +4 -28
  182. package/scripts/hooks/entry/claude-code/session-start.js +205 -243
  183. package/scripts/hooks/entry/claude-code/setup.js +8 -49
  184. package/scripts/hooks/entry/claude-code/stop.js +40 -72
  185. package/scripts/hooks/entry/claude-code/task-completed.js +4 -28
  186. package/scripts/hooks/entry/claude-code/user-prompt-submit.js +113 -195
  187. package/scripts/hooks/entry/claude-code/worktree-create.js +6 -25
  188. package/scripts/hooks/entry/claude-code/worktree-remove.js +6 -25
  189. package/scripts/hooks/entry/shared/hook-runner.js +99 -0
  190. package/scripts/hooks/entry/shared/read-stdin.js +0 -2
  191. package/scripts/registries/api-registry.js +0 -2
  192. package/scripts/registries/component-registry.js +5 -9
  193. package/scripts/registries/contract-scanner.js +2 -9
  194. package/scripts/registries/function-registry.js +0 -2
  195. package/scripts/registries/schema-registry.js +14 -18
  196. package/scripts/registries/service-registry.js +23 -27
@@ -20,8 +20,6 @@ const { success, warn, error: errorMsg, info } = require('./flow-output');
20
20
  const { matchSkills, loadSkillContext } = require('./flow-skill-matcher');
21
21
  const { getCommand } = require('./flow-script-resolver');
22
22
 
23
- const PROJECT_ROOT = getProjectRoot();
24
-
25
23
  // ============================================================
26
24
  // Spec Generation
27
25
  // ============================================================
@@ -449,7 +447,7 @@ function generateRollbackPlan(taskContext) {
449
447
  function saveSpec(taskId, spec) {
450
448
  const config = getConfig();
451
449
  const specDir = config.specificationMode?.specDirectory || '.workflow/specs';
452
- const fullDir = path.join(PROJECT_ROOT, specDir);
450
+ const fullDir = path.join(PATHS.root, specDir);
453
451
 
454
452
  // Ensure directory exists
455
453
  if (!fs.existsSync(fullDir)) {
@@ -629,7 +627,7 @@ function formatSpecAsMarkdown(spec) {
629
627
  function loadSpec(taskId) {
630
628
  const config = getConfig();
631
629
  const specDir = config.specificationMode?.specDirectory || '.workflow/specs';
632
- const jsonPath = path.join(PROJECT_ROOT, specDir, `${taskId}.json`);
630
+ const jsonPath = path.join(PATHS.root, specDir, `${taskId}.json`);
633
631
 
634
632
  if (!fs.existsSync(jsonPath)) {
635
633
  return null;
@@ -6,7 +6,7 @@
6
6
  * and "Let AI decide" option for intelligent defaults
7
7
  */
8
8
 
9
- const readline = require('node:readline');
9
+ const readline = require('node:readline/promises');
10
10
  const path = require('node:path');
11
11
  const fs = require('node:fs');
12
12
 
@@ -852,6 +852,7 @@ class EnhancedStackWizard {
852
852
 
853
853
  try {
854
854
  const generator = require('./flow-skill-generator');
855
+ const { PATHS } = require('./flow-utils');
855
856
  await generator.generateSkills(technologies, this.selections);
856
857
 
857
858
  console.log(c('green', '\n✅ Skills generated successfully!\n'));
@@ -906,12 +907,9 @@ class EnhancedStackWizard {
906
907
  // INPUT HELPERS
907
908
  // ============================================
908
909
 
909
- askQuestion(prompt) {
910
- return new Promise((resolve) => {
911
- this.rl.question(prompt, (answer) => {
912
- resolve(answer.trim());
913
- });
914
- });
910
+ async askQuestion(prompt) {
911
+ const answer = await this.rl.question(prompt);
912
+ return answer.trim();
915
913
  }
916
914
 
917
915
  async askSingleChoice(question, options, defaultValue = null) {
@@ -404,9 +404,22 @@ function recordViolationPattern(learning) {
404
404
  }
405
405
  }
406
406
 
407
- // Write updated content
407
+ // Write updated content through orchestrator
408
408
  try {
409
- fs.writeFileSync(FEEDBACK_PATTERNS_PATH, content, 'utf-8');
409
+ const { writeToFeedbackPatterns } = require('./flow-learning-orchestrator');
410
+ const writeResult = writeToFeedbackPatterns({
411
+ content,
412
+ entryText: patternKey,
413
+ caller: 'flow-standards-learner/recordViolationPattern',
414
+ skipDedup: true // We already handle dedup via patternRegex above
415
+ });
416
+ // writeToFeedbackPatterns is async but we handle it fire-and-forget here
417
+ // since the sync callers can't await. The lock still protects the write.
418
+ if (writeResult && writeResult.then) {
419
+ writeResult.catch(err => {
420
+ if (process.env.DEBUG) console.error(`[DEBUG] recordViolationPattern write: ${err.message}`);
421
+ });
422
+ }
410
423
  } catch (err) {
411
424
  return { recorded: false, reason: err.message };
412
425
  }
@@ -508,24 +521,30 @@ ${learning.ruleTemplate}
508
521
  content += ruleEntry;
509
522
  }
510
523
 
511
- // Write updated content
524
+ // Write updated content through orchestrator
512
525
  try {
513
- fs.writeFileSync(DECISIONS_PATH, content, 'utf-8');
514
- } catch (err) {
515
- return { promoted: false, reason: err.message };
516
- }
526
+ const { writeToDecisions, modifyFeedbackPatterns } = require('./flow-learning-orchestrator');
527
+ const writeResult = writeToDecisions({
528
+ content,
529
+ entryText: learning.patternName,
530
+ caller: 'flow-standards-learner/promoteToDecisions'
531
+ });
532
+ if (writeResult && writeResult.then) {
533
+ writeResult.catch(err => {
534
+ if (process.env.DEBUG) console.error(`[DEBUG] promoteToDecisions write: ${err.message}`);
535
+ });
536
+ }
517
537
 
518
- // Update feedback-patterns.md to mark as promoted
519
- try {
520
- let patternsContent = readFile(FEEDBACK_PATTERNS_PATH, '');
538
+ // Update feedback-patterns.md to mark as promoted
521
539
  const patternKey = `${learning.violationType}-${learning.patternName}`.replace(/\s+/g, '-').toLowerCase();
522
- // escapeRegex prevents ReDoS from patternKey with regex metacharacters
523
540
  const escapedPromoteKey = escapeRegex(patternKey);
524
- patternsContent = patternsContent.replace(
525
- new RegExp(`(\\|\\s*[\\d-]+\\s*\\|\\s*${escapedPromoteKey}\\s*\\|\\s*\\d+\\s*\\|)\\s*-\\s*\\|\\s*Monitor\\s*\\|`, 'i'),
526
- `$1 decisions.md | **PROMOTED** |`
527
- );
528
- fs.writeFileSync(FEEDBACK_PATTERNS_PATH, patternsContent, 'utf-8');
541
+ modifyFeedbackPatterns((currentContent) => {
542
+ const updated = currentContent.replace(
543
+ new RegExp(`(\\|\\s*[\\d-]+\\s*\\|\\s*${escapedPromoteKey}\\s*\\|\\s*\\d+\\s*\\|)\\s*-\\s*\\|\\s*Monitor\\s*\\|`, 'i'),
544
+ `$1 decisions.md | **PROMOTED** |`
545
+ );
546
+ return { content: updated };
547
+ }, { caller: 'flow-standards-learner/promoteToDecisions-markPromoted', skipDedup: true });
529
548
  } catch (err) {
530
549
  // Non-fatal, rule was already promoted to decisions
531
550
  }
@@ -55,6 +55,7 @@ const assessTaskComplexity = complexityModule?.assessTaskComplexity || (() => ({
55
55
  const { warnIfContextHigh } = require('./flow-context-monitor');
56
56
  const { setCurrentTask } = require('./flow-memory-blocks');
57
57
  const { trackTaskStart, checkAndDisplayResumeContext } = require('./flow-session-state');
58
+ const { setActiveTask: setHookActiveTask } = require('./flow-hook-status');
58
59
 
59
60
  // v2.0 durable session support
60
61
  const {
@@ -505,6 +506,7 @@ async function main() {
505
506
  try {
506
507
  trackTaskStart(taskId, taskTitle);
507
508
  setCurrentTask(taskId, taskTitle);
509
+ setHookActiveTask({ id: taskId, title: taskTitle, routedAt: new Date().toISOString() });
508
510
  } catch (err) {
509
511
  if (process.env.DEBUG) console.error(`[DEBUG] Task tracking: ${err.message}`);
510
512
  }
@@ -31,8 +31,8 @@ const {
31
31
  // Constants
32
32
  // ============================================================
33
33
 
34
- const STATS_PATH = path.join(PATHS.root, '.workflow', 'models', 'stats.json');
35
- const STATS_ARCHIVE_DIR = path.join(PATHS.root, '.workflow', 'models', 'stats-archive');
34
+ const STATS_PATH = PATHS.modelStats;
35
+ const STATS_ARCHIVE_DIR = PATHS.modelStatsArchive;
36
36
  const MAX_RECENT_TASKS = 500;
37
37
 
38
38
  // ============================================================
@@ -86,8 +86,8 @@ function collectStatus() {
86
86
  status.cli.type = config.cli?.type || 'claude-code';
87
87
 
88
88
  // Check bridge status
89
- const bridgesDir = path.join(PROJECT_ROOT, '.workflow', 'bridges');
90
- const modelsDir = path.join(PROJECT_ROOT, '.workflow', 'models');
89
+ const bridgesDir = PATHS.bridges;
90
+ const modelsDir = PATHS.modelsDir;
91
91
 
92
92
  if (dirExists(bridgesDir) && dirExists(modelsDir)) {
93
93
  status.cli.bridgeStatus = 'configured';
@@ -105,9 +105,9 @@ function collectStatus() {
105
105
  if (fileExists(PATHS.config)) {
106
106
  const config = getConfig();
107
107
  status.config = {
108
- mandatoryAfterTask: config.mandatorySteps?.afterTask || [],
109
- strictMode: config.enforcement?.strictMode || false,
110
- priorities: config.priorities || {}
108
+ mandatoryAfterTask: config.mandatorySteps?.afterTask ?? [],
109
+ strictMode: config.enforcement?.strictMode ?? false,
110
+ priorities: config.priorities ?? {}
111
111
  };
112
112
  }
113
113
 
@@ -188,7 +188,7 @@ function main() {
188
188
  if (status.git.isRepo) {
189
189
  printSection('Git');
190
190
  console.log(` Branch: ${status.git.branch || 'unknown'}`);
191
- console.log(` Uncommitted: ${status.git.uncommitted || 0} files`);
191
+ console.log(` Uncommitted: ${status.git.uncommitted ?? 0} files`);
192
192
  console.log('');
193
193
  }
194
194
 
@@ -229,7 +229,7 @@ function main() {
229
229
  }
230
230
 
231
231
  // Show recent attempts (last 3)
232
- const recentAttempts = (status.bypassTracking.attempts || []).slice(-3);
232
+ const recentAttempts = (status.bypassTracking.attempts ?? []).slice(-3);
233
233
  if (recentAttempts.length > 0) {
234
234
  console.log(color('dim', ' Recent attempts:'));
235
235
  for (const attempt of recentAttempts) {
@@ -278,7 +278,7 @@ function getRecommendation() {
278
278
  const data = getReadyData();
279
279
 
280
280
  // Check in-progress tasks first
281
- const inProgress = data.inProgress || [];
281
+ const inProgress = data.inProgress ?? [];
282
282
  if (inProgress.length > 0) {
283
283
  const task = inProgress[0];
284
284
  const taskId = typeof task === 'string' ? task : task.id;
@@ -290,7 +290,7 @@ function getRecommendation() {
290
290
  }
291
291
 
292
292
  // Check ready tasks
293
- const ready = data.ready || [];
293
+ const ready = data.ready ?? [];
294
294
  if (ready.length > 0) {
295
295
  // Find highest priority task (P0=critical, P1=high, P2=medium, P3=low, P4=lowest)
296
296
  const priorityOrder = { P0: 0, P1: 1, P2: 2, P3: 3, P4: 4 };
@@ -311,7 +311,7 @@ function getRecommendation() {
311
311
  }
312
312
 
313
313
  // Check blocked tasks
314
- const blocked = data.blocked || [];
314
+ const blocked = data.blocked ?? [];
315
315
  if (blocked.length > 0) {
316
316
  return {
317
317
  action: `${blocked.length} task(s) are blocked - resolve dependencies`,
@@ -17,7 +17,7 @@
17
17
  const fs = require('node:fs');
18
18
  const path = require('node:path');
19
19
  const os = require('node:os');
20
- const readline = require('node:readline');
20
+ const readline = require('node:readline/promises');
21
21
  const { colors, printHeader, safeJsonParse } = require('./flow-utils');
22
22
  const { success, error: errorMsg } = require('./flow-output');
23
23
 
@@ -105,7 +105,7 @@ async function interactiveSetup() {
105
105
  output: process.stdout
106
106
  });
107
107
 
108
- const question = (q) => new Promise(resolve => rl.question(q, resolve));
108
+ const question = (q) => rl.question(q);
109
109
 
110
110
  printHeader('Status Line Setup');
111
111
  showCurrentConfig();
@@ -9,10 +9,9 @@
9
9
 
10
10
  const fs = require('node:fs');
11
11
  const path = require('node:path');
12
- const { getProjectRoot, colors, getConfig } = require('./flow-utils');
12
+ const { getProjectRoot, colors, getConfig, PATHS } = require('./flow-utils');
13
13
 
14
- const PROJECT_ROOT = getProjectRoot();
15
- const CHANGELOG_PATH = path.join(PROJECT_ROOT, 'CHANGELOG.md');
14
+ const CHANGELOG_PATH = path.join(PATHS.root, 'CHANGELOG.md');
16
15
 
17
16
  /**
18
17
  * Run update changelog step
@@ -11,96 +11,80 @@
11
11
  * - Missing documentation for public APIs
12
12
  */
13
13
 
14
- const fs = require('node:fs');
15
- const path = require('node:path');
16
- const { getProjectRoot, colors } = require('./flow-utils');
17
-
18
- const PROJECT_ROOT = getProjectRoot();
19
-
20
- /**
21
- * Run comment analysis as a workflow step
22
- *
23
- * @param {object} options
24
- * @param {string[]} options.files - Files modified
25
- * @param {object} options.stepConfig - Step configuration
26
- * @param {string} options.mode - Step mode (block/warn/prompt/auto)
27
- * @returns {object} - { passed: boolean, message: string, details?: object[] }
28
- */
29
- async function run(options = {}) {
30
- const { files = [], stepConfig = {} } = options;
31
- const flagTodo = stepConfig.flagTodo !== false;
32
- const flagFixme = stepConfig.flagFixme !== false;
33
- const checkJsdoc = stepConfig.checkJsdoc !== false;
34
- const flagCommentedCode = stepConfig.flagCommentedCode !== false;
35
- const flagStale = stepConfig.flagStale !== false;
36
-
37
- // Filter to analyzable files
38
- const analyzableExtensions = ['.js', '.ts', '.jsx', '.tsx'];
39
- const analyzableFiles = files.filter(f =>
40
- analyzableExtensions.some(ext => f.endsWith(ext)) &&
41
- !f.includes('.d.ts')
42
- );
43
-
44
- if (analyzableFiles.length === 0) {
45
- return { passed: true, message: 'No files to analyze' };
14
+ const { BaseWorkflowStep } = require('./base-workflow-step');
15
+ const { colors } = require('./flow-utils');
16
+
17
+ class CommentAnalyzerStep extends BaseWorkflowStep {
18
+ constructor() {
19
+ super('commentAnalyzer', {
20
+ extensions: ['.js', '.ts', '.jsx', '.tsx'],
21
+ excludeTests: false, // Comments step analyzes test files too
22
+ excludeDts: true,
23
+ });
46
24
  }
47
25
 
48
- const issues = [];
49
-
50
- for (const file of analyzableFiles) {
51
- const filePath = path.join(PROJECT_ROOT, file);
52
- if (!fs.existsSync(filePath)) continue;
53
-
54
- try {
55
- const content = fs.readFileSync(filePath, 'utf8');
56
- const fileIssues = analyzeComments(content, file, {
57
- flagTodo,
58
- flagFixme,
59
- checkJsdoc,
60
- flagCommentedCode,
61
- flagStale,
62
- });
63
- issues.push(...fileIssues);
64
- } catch (err) {
65
- // Skip unreadable files
26
+ async execute(files, options) {
27
+ const { stepConfig = {} } = options;
28
+ const flagTodo = stepConfig.flagTodo !== false;
29
+ const flagFixme = stepConfig.flagFixme !== false;
30
+ const checkJsdoc = stepConfig.checkJsdoc !== false;
31
+ const flagCommentedCode = stepConfig.flagCommentedCode !== false;
32
+ const flagStale = stepConfig.flagStale !== false;
33
+
34
+ const issues = [];
35
+
36
+ for (const file of files) {
37
+ const content = this.readFile(file);
38
+ if (!content) continue;
39
+
40
+ try {
41
+ const fileIssues = analyzeComments(content, file, {
42
+ flagTodo,
43
+ flagFixme,
44
+ checkJsdoc,
45
+ flagCommentedCode,
46
+ flagStale,
47
+ });
48
+ issues.push(...fileIssues);
49
+ } catch (_err) {
50
+ // Skip unreadable files
51
+ }
66
52
  }
67
- }
68
-
69
- if (issues.length === 0) {
70
- return {
71
- passed: true,
72
- message: 'Comment analysis passed',
73
- };
74
- }
75
53
 
76
- // Report issues
77
- console.log(colors.yellow + '\n Comment Analysis Issues:' + colors.reset);
54
+ if (issues.length === 0) {
55
+ return this.pass('Comment analysis passed');
56
+ }
78
57
 
79
- // Group by type
80
- const grouped = {};
81
- for (const issue of issues) {
82
- if (!grouped[issue.type]) grouped[issue.type] = [];
83
- grouped[issue.type].push(issue);
84
- }
58
+ // Report issues
59
+ console.log(colors.yellow + '\n Comment Analysis Issues:' + colors.reset);
85
60
 
86
- for (const [type, typeIssues] of Object.entries(grouped)) {
87
- console.log(colors.cyan + ` ${type}:` + colors.reset);
88
- for (const issue of typeIssues.slice(0, 5)) { // Limit to 5 per type
89
- console.log(` ${issue.file}:${issue.line}`);
90
- console.log(` ${issue.message}`);
61
+ // Group by type
62
+ const grouped = {};
63
+ for (const issue of issues) {
64
+ if (!grouped[issue.type]) grouped[issue.type] = [];
65
+ grouped[issue.type].push(issue);
91
66
  }
92
- if (typeIssues.length > 5) {
93
- console.log(colors.dim + ` ... and ${typeIssues.length - 5} more` + colors.reset);
67
+
68
+ for (const [type, typeIssues] of Object.entries(grouped)) {
69
+ console.log(colors.cyan + ` ${type}:` + colors.reset);
70
+ for (const issue of typeIssues.slice(0, 5)) {
71
+ console.log(` ${issue.file}:${issue.line}`);
72
+ console.log(` ${issue.message}`);
73
+ }
74
+ if (typeIssues.length > 5) {
75
+ console.log(colors.dim + ` ... and ${typeIssues.length - 5} more` + colors.reset);
76
+ }
94
77
  }
95
- }
96
78
 
97
- const highSeverity = issues.filter(i => i.severity === 'high');
79
+ const highSeverity = issues.filter(i => i.severity === 'high');
98
80
 
99
- return {
100
- passed: highSeverity.length === 0,
101
- message: `${issues.length} comment issue(s) found (${highSeverity.length} high severity)`,
102
- details: issues,
103
- };
81
+ return highSeverity.length === 0
82
+ ? this.pass(`${issues.length} comment issue(s) found (${highSeverity.length} high severity)`)
83
+ : this.fail(
84
+ `${issues.length} comment issue(s) found (${highSeverity.length} high severity)`,
85
+ issues
86
+ );
87
+ }
104
88
  }
105
89
 
106
90
  /**
@@ -303,4 +287,5 @@ function checkJsDocAccuracy(jsDocLines, functionLine, startLine, fileName) {
303
287
  return issues;
304
288
  }
305
289
 
306
- module.exports = { run, analyzeComments };
290
+ const step = new CommentAnalyzerStep();
291
+ module.exports = { run: (opts) => step.run(opts), analyzeComments };
@@ -7,83 +7,62 @@
7
7
  * Flags functions that exceed the configured threshold.
8
8
  */
9
9
 
10
- const fs = require('node:fs');
11
- const path = require('node:path');
12
- const { getProjectRoot, colors } = require('./flow-utils');
13
-
14
- const PROJECT_ROOT = getProjectRoot();
15
-
16
- /**
17
- * Run code complexity check step
18
- *
19
- * @param {object} options
20
- * @param {string[]} options.files - Files modified
21
- * @param {object} options.stepConfig - Step configuration
22
- * @param {string} options.mode - Step mode (block/warn/prompt/auto)
23
- * @returns {object} - { passed: boolean, message: string, details?: object[] }
24
- */
25
- async function run(options = {}) {
26
- const { files = [], stepConfig = {}, mode } = options;
27
- const threshold = stepConfig.threshold || 10;
28
-
29
- // Filter to analyzable files
30
- const analyzableExtensions = ['.js', '.ts', '.jsx', '.tsx'];
31
- const analyzableFiles = files.filter(f =>
32
- analyzableExtensions.some(ext => f.endsWith(ext)) &&
33
- !f.includes('.test.') &&
34
- !f.includes('.spec.') &&
35
- !f.includes('.d.ts')
36
- );
37
-
38
- if (analyzableFiles.length === 0) {
39
- return { passed: true, message: 'No analyzable files modified' };
10
+ const { BaseWorkflowStep } = require('./base-workflow-step');
11
+ const { colors } = require('./flow-utils');
12
+
13
+ class ComplexityStep extends BaseWorkflowStep {
14
+ constructor() {
15
+ super('codeComplexityCheck', {
16
+ extensions: ['.js', '.ts', '.jsx', '.tsx'],
17
+ excludeTests: true,
18
+ excludeDts: true,
19
+ });
40
20
  }
41
21
 
42
- const complexFunctions = [];
43
-
44
- for (const file of analyzableFiles) {
45
- const filePath = path.join(PROJECT_ROOT, file);
46
- if (!fs.existsSync(filePath)) continue;
47
-
48
- try {
49
- const content = fs.readFileSync(filePath, 'utf8');
50
- const functions = analyzeComplexity(content, file);
51
-
52
- for (const func of functions) {
53
- if (func.complexity > threshold) {
54
- complexFunctions.push({
55
- file,
56
- function: func.name,
57
- complexity: func.complexity,
58
- line: func.line,
59
- suggestion: getSuggestion(func.complexity, threshold),
60
- });
22
+ async execute(files, options) {
23
+ const { stepConfig = {} } = options;
24
+ const threshold = stepConfig.threshold || 10;
25
+ const complexFunctions = [];
26
+
27
+ for (const file of files) {
28
+ const content = this.readFile(file);
29
+ if (!content) continue;
30
+
31
+ try {
32
+ const functions = analyzeComplexity(content, file);
33
+
34
+ for (const func of functions) {
35
+ if (func.complexity > threshold) {
36
+ complexFunctions.push({
37
+ file,
38
+ function: func.name,
39
+ complexity: func.complexity,
40
+ line: func.line,
41
+ suggestion: getSuggestion(func.complexity, threshold),
42
+ });
43
+ }
61
44
  }
45
+ } catch (_err) {
46
+ // Skip files that can't be analyzed
62
47
  }
63
- } catch (err) {
64
- // Skip files that can't be analyzed
65
48
  }
66
- }
67
49
 
68
- if (complexFunctions.length === 0) {
69
- return {
70
- passed: true,
71
- message: `All functions under complexity threshold (${threshold})`,
72
- };
73
- }
50
+ if (complexFunctions.length === 0) {
51
+ return this.pass(`All functions under complexity threshold (${threshold})`);
52
+ }
74
53
 
75
- // Report complex functions
76
- console.log(colors.yellow + `\n Functions exceeding complexity threshold (${threshold}):` + colors.reset);
77
- for (const func of complexFunctions) {
78
- console.log(` ${func.file}:${func.line} - ${func.function} (${func.complexity})`);
79
- console.log(colors.gray + ` ${func.suggestion}` + colors.reset);
80
- }
54
+ // Report complex functions
55
+ console.log(colors.yellow + `\n Functions exceeding complexity threshold (${threshold}):` + colors.reset);
56
+ for (const func of complexFunctions) {
57
+ console.log(` ${func.file}:${func.line} - ${func.function} (${func.complexity})`);
58
+ console.log(colors.gray + ` ${func.suggestion}` + colors.reset);
59
+ }
81
60
 
82
- return {
83
- passed: false,
84
- message: `${complexFunctions.length} function(s) exceed complexity threshold`,
85
- details: complexFunctions,
86
- };
61
+ return this.fail(
62
+ `${complexFunctions.length} function(s) exceed complexity threshold`,
63
+ complexFunctions
64
+ );
65
+ }
87
66
  }
88
67
 
89
68
  /**
@@ -231,4 +210,5 @@ function getSuggestion(complexity, threshold) {
231
210
  return 'Consider simplifying conditional logic';
232
211
  }
233
212
 
234
- module.exports = { run, analyzeComplexity, calculateComplexity };
213
+ const step = new ComplexityStep();
214
+ module.exports = { run: (opts) => step.run(opts), analyzeComplexity, calculateComplexity };
@@ -13,8 +13,6 @@ const { execSync } = require('node:child_process');
13
13
  const { getProjectRoot, colors, getConfig, safeJsonParse, readJson, error } = require('./flow-utils');
14
14
  const { getCommand, getExec } = require('./flow-script-resolver');
15
15
 
16
- const PROJECT_ROOT = getProjectRoot();
17
-
18
16
  // Common coverage output locations
19
17
  const COVERAGE_PATHS = [
20
18
  'coverage/coverage-summary.json', // Jest/Vitest JSON summary
@@ -116,7 +114,7 @@ async function run(options = {}) {
116
114
  */
117
115
  function findExistingCoverage() {
118
116
  for (const coveragePath of COVERAGE_PATHS) {
119
- const fullPath = path.join(PROJECT_ROOT, coveragePath);
117
+ const fullPath = path.join(PATHS.root, coveragePath);
120
118
  if (fs.existsSync(fullPath)) {
121
119
  try {
122
120
  if (coveragePath.endsWith('.json')) {
@@ -142,7 +140,7 @@ function findExistingCoverage() {
142
140
  */
143
141
  async function runCoverageTests() {
144
142
  // Check package.json for test script
145
- const packagePath = path.join(PROJECT_ROOT, 'package.json');
143
+ const packagePath = path.join(PATHS.root, 'package.json');
146
144
  if (!fs.existsSync(packagePath)) return null;
147
145
 
148
146
  try {
@@ -169,7 +167,7 @@ async function runCoverageTests() {
169
167
 
170
168
  // Run coverage
171
169
  execSync(coverageCmd, {
172
- cwd: PROJECT_ROOT,
170
+ cwd: PATHS.root,
173
171
  stdio: ['pipe', 'pipe', 'pipe'],
174
172
  timeout: 300000, // 5 minute timeout
175
173
  });
@@ -9,10 +9,9 @@
9
9
 
10
10
  const fs = require('node:fs');
11
11
  const path = require('node:path');
12
- const { getProjectRoot, colors, getConfig } = require('./flow-utils');
12
+ const { getProjectRoot, colors, getConfig, PATHS } = require('./flow-utils');
13
13
 
14
- const PROJECT_ROOT = getProjectRoot();
15
- const KNOWLEDGE_DIR = path.join(PROJECT_ROOT, '.claude', 'docs', 'knowledge-base');
14
+ const KNOWLEDGE_DIR = path.join(PATHS.root, '.claude', 'docs', 'knowledge-base');
16
15
 
17
16
  /**
18
17
  * Run update knowledge base step