wogiflow 2.4.2 → 2.4.4

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 (210) hide show
  1. package/.claude/commands/wogi-start.md +124 -0
  2. package/.claude/docs/claude-code-compatibility.md +51 -0
  3. package/.claude/docs/explore-agents.md +11 -0
  4. package/.claude/settings.json +12 -1
  5. package/.workflow/models/registry.json +1 -1
  6. package/bin/flow +11 -1
  7. package/lib/workspace-contracts.js +599 -0
  8. package/lib/workspace-intelligence.js +600 -0
  9. package/lib/workspace-messages.js +441 -0
  10. package/lib/workspace-routing.js +485 -0
  11. package/lib/workspace-sync.js +339 -0
  12. package/lib/workspace.js +1073 -0
  13. package/package.json +4 -4
  14. package/scripts/MEMORY-ARCHITECTURE.md +1 -1
  15. package/scripts/base-workflow-step.js +136 -0
  16. package/scripts/flow-adaptive-learning.js +8 -9
  17. package/scripts/flow-aggregate.js +11 -6
  18. package/scripts/flow-api-index.js +4 -6
  19. package/scripts/flow-assumption-detector.js +0 -2
  20. package/scripts/flow-audit.js +15 -2
  21. package/scripts/flow-auto-context.js +8 -12
  22. package/scripts/flow-auto-learn.js +49 -49
  23. package/scripts/flow-background.js +5 -6
  24. package/scripts/flow-bridge-state.js +8 -10
  25. package/scripts/flow-bulk-loop.js +1 -3
  26. package/scripts/flow-bulk-orchestrator.js +1 -3
  27. package/scripts/flow-cascade-completion.js +0 -2
  28. package/scripts/flow-cascade.js +4 -4
  29. package/scripts/flow-checkpoint.js +10 -13
  30. package/scripts/flow-code-intelligence.js +10 -12
  31. package/scripts/flow-community-sync.js +4 -4
  32. package/scripts/flow-community.js +12 -20
  33. package/scripts/flow-config-defaults.js +28 -2
  34. package/scripts/flow-config-interactive.js +9 -5
  35. package/scripts/flow-config-loader.js +49 -92
  36. package/scripts/flow-config-substitution.js +0 -2
  37. package/scripts/flow-context-estimator.js +4 -4
  38. package/scripts/flow-context-init.js +10 -12
  39. package/scripts/flow-context-manager.js +0 -2
  40. package/scripts/flow-context-scoring.js +2 -2
  41. package/scripts/flow-contract-scan.js +6 -9
  42. package/scripts/flow-correct.js +29 -27
  43. package/scripts/flow-correction-detector.js +5 -1
  44. package/scripts/flow-damage-control.js +47 -54
  45. package/scripts/flow-decisions-merge.js +4 -14
  46. package/scripts/flow-diff.js +5 -8
  47. package/scripts/flow-done-gates.js +786 -0
  48. package/scripts/flow-done-report.js +123 -0
  49. package/scripts/flow-done.js +71 -717
  50. package/scripts/flow-entropy-monitor.js +1 -3
  51. package/scripts/flow-eval-calibration.js +257 -0
  52. package/scripts/flow-eval-judge.js +10 -1
  53. package/scripts/flow-eval.js +14 -5
  54. package/scripts/flow-extraction-review.js +1 -0
  55. package/scripts/flow-failure-categories.js +0 -2
  56. package/scripts/flow-figma-confirm.js +5 -9
  57. package/scripts/flow-figma-generate.js +8 -10
  58. package/scripts/flow-figma-index.js +8 -10
  59. package/scripts/flow-figma-match.js +3 -5
  60. package/scripts/flow-figma-mcp-server.js +2 -4
  61. package/scripts/flow-figma-orchestrator.js +2 -3
  62. package/scripts/flow-figma-registry.js +2 -3
  63. package/scripts/flow-framework-resolver.js +0 -2
  64. package/scripts/flow-function-index.js +4 -6
  65. package/scripts/flow-gate-confidence.js +2 -2
  66. package/scripts/flow-gitignore.js +0 -2
  67. package/scripts/flow-guided-edit.js +5 -6
  68. package/scripts/flow-health.js +5 -6
  69. package/scripts/flow-hook-errors.js +6 -0
  70. package/scripts/flow-hook-status.js +263 -0
  71. package/scripts/flow-hooks.js +17 -29
  72. package/scripts/flow-http-client.js +9 -8
  73. package/scripts/flow-hybrid-interactive.js +7 -12
  74. package/scripts/flow-hybrid-test.js +12 -13
  75. package/scripts/flow-instruction-richness.js +1 -1
  76. package/scripts/flow-io.js +21 -4
  77. package/scripts/flow-knowledge-router.js +9 -3
  78. package/scripts/flow-learning-orchestrator.js +318 -13
  79. package/scripts/flow-links.js +5 -7
  80. package/scripts/flow-long-input-association.js +275 -0
  81. package/scripts/flow-long-input-chunking.js +1 -0
  82. package/scripts/flow-long-input-cli.js +0 -2
  83. package/scripts/flow-long-input-complexity.js +0 -2
  84. package/scripts/flow-long-input-constants.js +0 -2
  85. package/scripts/flow-long-input-contradictions.js +351 -0
  86. package/scripts/flow-long-input-detection.js +0 -2
  87. package/scripts/flow-long-input-passes.js +885 -0
  88. package/scripts/flow-long-input-stories.js +1 -1
  89. package/scripts/flow-long-input-voice.js +0 -2
  90. package/scripts/flow-long-input.js +425 -3005
  91. package/scripts/flow-loop-retry-learning.js +2 -3
  92. package/scripts/flow-lsp.js +3 -3
  93. package/scripts/flow-mcp-docs.js +3 -4
  94. package/scripts/flow-memory-db.js +6 -8
  95. package/scripts/flow-memory-sync.js +18 -11
  96. package/scripts/flow-metrics.js +1 -2
  97. package/scripts/flow-model-adapter.js +2 -3
  98. package/scripts/flow-model-config.js +72 -104
  99. package/scripts/flow-model-router.js +2 -2
  100. package/scripts/flow-model-types.js +0 -2
  101. package/scripts/flow-multi-approach.js +5 -6
  102. package/scripts/flow-orchestrate-context.js +3 -7
  103. package/scripts/flow-orchestrate-rollback.js +3 -8
  104. package/scripts/flow-orchestrate-state.js +8 -14
  105. package/scripts/flow-orchestrate-templates.js +2 -6
  106. package/scripts/flow-orchestrate-validator.js +5 -9
  107. package/scripts/flow-orchestrate.js +126 -103
  108. package/scripts/flow-output.js +0 -2
  109. package/scripts/flow-parallel.js +1 -1
  110. package/scripts/flow-paths.js +23 -2
  111. package/scripts/flow-pattern-enforcer.js +30 -28
  112. package/scripts/flow-pattern-extractor.js +3 -4
  113. package/scripts/flow-pending.js +0 -2
  114. package/scripts/flow-permissions.js +2 -3
  115. package/scripts/flow-plugin-registry.js +10 -12
  116. package/scripts/flow-prd-manager.js +1 -1
  117. package/scripts/flow-progress.js +7 -9
  118. package/scripts/flow-prompt-composer.js +3 -3
  119. package/scripts/flow-prompt-template.js +2 -2
  120. package/scripts/flow-providers.js +7 -4
  121. package/scripts/flow-registry-manager.js +7 -12
  122. package/scripts/flow-regression.js +9 -11
  123. package/scripts/flow-roadmap.js +2 -2
  124. package/scripts/flow-run-trace.js +16 -15
  125. package/scripts/flow-safety.js +2 -5
  126. package/scripts/flow-scanner-base.js +5 -7
  127. package/scripts/flow-scenario-engine.js +1 -5
  128. package/scripts/flow-security.js +29 -0
  129. package/scripts/flow-session-end.js +32 -41
  130. package/scripts/flow-session-learning.js +53 -49
  131. package/scripts/flow-setup-hooks.js +2 -3
  132. package/scripts/flow-skill-create.js +7 -12
  133. package/scripts/flow-skill-generator.js +12 -16
  134. package/scripts/flow-skill-learn.js +17 -8
  135. package/scripts/flow-skill-matcher.js +1 -2
  136. package/scripts/flow-spec-generator.js +2 -4
  137. package/scripts/flow-stack-wizard.js +5 -7
  138. package/scripts/flow-standards-learner.js +35 -16
  139. package/scripts/flow-start.js +2 -0
  140. package/scripts/flow-stats-collector.js +2 -2
  141. package/scripts/flow-status.js +10 -10
  142. package/scripts/flow-statusline-setup.js +2 -2
  143. package/scripts/flow-step-changelog.js +2 -3
  144. package/scripts/flow-step-comments.js +66 -81
  145. package/scripts/flow-step-complexity.js +50 -70
  146. package/scripts/flow-step-coverage.js +3 -5
  147. package/scripts/flow-step-knowledge.js +2 -3
  148. package/scripts/flow-step-pr-tests.js +64 -74
  149. package/scripts/flow-step-regression.js +3 -5
  150. package/scripts/flow-step-review.js +86 -103
  151. package/scripts/flow-step-security.js +111 -121
  152. package/scripts/flow-step-silent-failures.js +56 -83
  153. package/scripts/flow-step-simplifier.js +52 -70
  154. package/scripts/flow-story.js +4 -7
  155. package/scripts/flow-strict-adherence.js +3 -4
  156. package/scripts/flow-task-checkpoint.js +36 -5
  157. package/scripts/flow-task-enforcer.js +2 -24
  158. package/scripts/flow-tech-debt.js +1 -1
  159. package/scripts/flow-template-extractor.js +1 -0
  160. package/scripts/flow-templates.js +11 -13
  161. package/scripts/flow-test-api.js +9 -13
  162. package/scripts/flow-test-discovery.js +1 -1
  163. package/scripts/flow-test-generate.js +5 -9
  164. package/scripts/flow-test-integrity.js +3 -7
  165. package/scripts/flow-test-ui.js +5 -9
  166. package/scripts/flow-testing-deps.js +1 -3
  167. package/scripts/flow-tiered-learning.js +4 -4
  168. package/scripts/flow-todowrite-sync.js +1 -1
  169. package/scripts/flow-tokens.js +0 -2
  170. package/scripts/flow-verification-profile.js +6 -10
  171. package/scripts/flow-verify.js +12 -16
  172. package/scripts/flow-version-check.js +4 -12
  173. package/scripts/flow-webmcp-generator.js +3 -5
  174. package/scripts/flow-workflow-steps.js +0 -2
  175. package/scripts/flow-workflow.js +9 -11
  176. package/scripts/hooks/adapters/claude-code.js +31 -0
  177. package/scripts/hooks/core/config-change.js +1 -0
  178. package/scripts/hooks/core/extension-registry.js +0 -2
  179. package/scripts/hooks/core/instructions-loaded.js +1 -1
  180. package/scripts/hooks/core/observation-capture.js +5 -5
  181. package/scripts/hooks/core/phase-gate.js +5 -0
  182. package/scripts/hooks/core/post-compact.js +1 -12
  183. package/scripts/hooks/core/research-gate.js +2 -12
  184. package/scripts/hooks/core/routing-gate.js +6 -0
  185. package/scripts/hooks/core/task-completed.js +12 -0
  186. package/scripts/hooks/core/task-created.js +83 -0
  187. package/scripts/hooks/core/worktree-lifecycle.js +1 -1
  188. package/scripts/hooks/entry/claude-code/config-change.js +6 -29
  189. package/scripts/hooks/entry/claude-code/instructions-loaded.js +5 -30
  190. package/scripts/hooks/entry/claude-code/post-compact.js +4 -31
  191. package/scripts/hooks/entry/claude-code/post-tool-use.js +121 -172
  192. package/scripts/hooks/entry/claude-code/pre-tool-use.js +260 -361
  193. package/scripts/hooks/entry/claude-code/session-end.js +4 -28
  194. package/scripts/hooks/entry/claude-code/session-start.js +205 -243
  195. package/scripts/hooks/entry/claude-code/setup.js +8 -49
  196. package/scripts/hooks/entry/claude-code/stop.js +40 -72
  197. package/scripts/hooks/entry/claude-code/task-completed.js +4 -28
  198. package/scripts/hooks/entry/claude-code/task-created.js +15 -0
  199. package/scripts/hooks/entry/claude-code/user-prompt-submit.js +113 -195
  200. package/scripts/hooks/entry/claude-code/worktree-create.js +6 -25
  201. package/scripts/hooks/entry/claude-code/worktree-remove.js +6 -25
  202. package/scripts/hooks/entry/shared/hook-runner.js +99 -0
  203. package/scripts/hooks/entry/shared/read-stdin.js +0 -2
  204. package/scripts/postinstall.js +2 -0
  205. package/scripts/registries/api-registry.js +0 -2
  206. package/scripts/registries/component-registry.js +5 -9
  207. package/scripts/registries/contract-scanner.js +2 -9
  208. package/scripts/registries/function-registry.js +0 -2
  209. package/scripts/registries/schema-registry.js +14 -18
  210. package/scripts/registries/service-registry.js +23 -27
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wogiflow",
3
- "version": "2.4.2",
3
+ "version": "2.4.4",
4
4
  "description": "AI-powered development workflow management system with multi-model support",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -61,12 +61,12 @@
61
61
  "sql.js": "^1.14.1"
62
62
  },
63
63
  "optionalDependencies": {
64
- "@babel/parser": "^7.29.0",
64
+ "@babel/parser": "^7.29.2",
65
65
  "@babel/traverse": "^7.29.0",
66
- "@xenova/transformers": "^2.15.0"
66
+ "@huggingface/transformers": "^3.0.0"
67
67
  },
68
68
  "devDependencies": {
69
- "eslint": "^9.0.0"
69
+ "eslint": "^10.0.0"
70
70
  },
71
71
  "engines": {
72
72
  "node": ">=18.0.0"
@@ -88,7 +88,7 @@ flow knowledge-sync regenerate # Regenerate stale files
88
88
 
89
89
  **Features**:
90
90
  - SQLite database using sql.js (pure JS)
91
- - Embedding generation via @xenova/transformers
91
+ - Embedding generation via @huggingface/transformers
92
92
  - Semantic similarity search
93
93
  - Facts, proposals, and PRD chunk storage
94
94
 
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Wogi Flow - BaseWorkflowStep
3
+ *
4
+ * Base class for all flow-step-*.js workflow steps.
5
+ * Provides shared infrastructure: file filtering, context loading,
6
+ * validation, and result formatting.
7
+ *
8
+ * Subclasses override execute() with step-specific logic.
9
+ *
10
+ * Usage:
11
+ * const { BaseWorkflowStep } = require('./base-workflow-step');
12
+ *
13
+ * class MyStep extends BaseWorkflowStep {
14
+ * constructor() {
15
+ * super('myStep', {
16
+ * extensions: ['.js', '.ts'],
17
+ * excludeTests: true,
18
+ * excludeDts: true,
19
+ * });
20
+ * }
21
+ * async execute(files, options) {
22
+ * // ... step-specific logic
23
+ * return this.pass('All checks passed');
24
+ * }
25
+ * }
26
+ *
27
+ * module.exports = { run: (options) => new MyStep().run(options) };
28
+ */
29
+
30
+ const fs = require('node:fs');
31
+ const path = require('node:path');
32
+ const { PATHS } = require('./flow-utils');
33
+
34
+ class BaseWorkflowStep {
35
+ /**
36
+ * @param {string} name - Step identifier
37
+ * @param {object} [filterOpts] - File filtering options
38
+ * @param {string[]} [filterOpts.extensions] - File extensions to include (e.g., ['.js', '.ts'])
39
+ * @param {boolean} [filterOpts.excludeTests=true] - Exclude .test./.spec. files
40
+ * @param {boolean} [filterOpts.excludeDts=true] - Exclude .d.ts files
41
+ */
42
+ constructor(name, filterOpts = {}) {
43
+ this.name = name;
44
+ this.extensions = filterOpts.extensions ?? ['.js', '.ts', '.jsx', '.tsx'];
45
+ this.excludeTests = filterOpts.excludeTests ?? true;
46
+ this.excludeDts = filterOpts.excludeDts ?? true;
47
+ }
48
+
49
+ /**
50
+ * Main entry point — matches the `run(options)` interface expected by flow-workflow-steps.js
51
+ * @param {object} options
52
+ * @param {string[]} [options.files] - Files modified
53
+ * @param {object} [options.stepConfig] - Step configuration
54
+ * @param {string} [options.mode] - Step mode (block/warn/prompt/auto)
55
+ * @param {string} [options.taskType] - Task type
56
+ * @param {string} [options.taskId] - Task ID
57
+ * @param {string} [options.taskTitle] - Task title
58
+ * @returns {Promise<{passed: boolean, message: string, details?: any}>}
59
+ */
60
+ async run(options = {}) {
61
+ const { files = [], stepConfig = {}, ...rest } = options;
62
+
63
+ // Filter files to those this step cares about
64
+ const filteredFiles = this.filterFiles(files);
65
+
66
+ if (filteredFiles.length === 0) {
67
+ return this.pass(`No ${this.name}-eligible files modified`);
68
+ }
69
+
70
+ // Delegate to subclass
71
+ return await this.execute(filteredFiles, { stepConfig, ...rest });
72
+ }
73
+
74
+ /**
75
+ * Override in subclass — contains the step-specific logic.
76
+ * @param {string[]} files - Filtered files
77
+ * @param {object} options - Remaining options (stepConfig, mode, taskType, etc.)
78
+ * @returns {Promise<{passed: boolean, message: string, details?: any}>}
79
+ */
80
+ async execute(files, options) {
81
+ throw new Error(`${this.name}: execute() must be overridden`);
82
+ }
83
+
84
+ /**
85
+ * Filter files based on step's extension and exclusion rules.
86
+ * @param {string[]} files - Raw file list
87
+ * @returns {string[]} Filtered files
88
+ */
89
+ filterFiles(files) {
90
+ return files.filter(f => {
91
+ const hasExt = this.extensions.some(ext => f.endsWith(ext));
92
+ if (!hasExt) return false;
93
+ if (this.excludeTests && (f.includes('.test.') || f.includes('.spec.'))) return false;
94
+ if (this.excludeDts && f.endsWith('.d.ts')) return false;
95
+ return true;
96
+ });
97
+ }
98
+
99
+ /**
100
+ * Read a file safely, returning null on error.
101
+ * @param {string} relativePath - Path relative to project root
102
+ * @returns {string|null} File content or null
103
+ */
104
+ readFile(relativePath) {
105
+ const fullPath = path.join(PATHS.root, relativePath);
106
+ try {
107
+ if (!fs.existsSync(fullPath)) return null;
108
+ return fs.readFileSync(fullPath, 'utf8');
109
+ } catch (_err) {
110
+ return null;
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Create a passing result.
116
+ * @param {string} message
117
+ * @returns {{passed: true, message: string}}
118
+ */
119
+ pass(message) {
120
+ return { passed: true, message };
121
+ }
122
+
123
+ /**
124
+ * Create a failing result.
125
+ * @param {string} message
126
+ * @param {any} [details]
127
+ * @returns {{passed: false, message: string, details?: any}}
128
+ */
129
+ fail(message, details) {
130
+ const result = { passed: false, message };
131
+ if (details !== undefined) result.details = details;
132
+ return result;
133
+ }
134
+ }
135
+
136
+ module.exports = { BaseWorkflowStep };
@@ -15,7 +15,7 @@
15
15
  const fs = require('node:fs');
16
16
  const path = require('node:path');
17
17
  const { execFileSync } = require('node:child_process');
18
- const { getProjectRoot, colors } = require('./flow-utils');
18
+ const { getProjectRoot, colors, PATHS } = require('./flow-utils');
19
19
  const { error: errorMsg } = require('./flow-output');
20
20
  const { readJson } = require('./flow-io');
21
21
  const { storeSingleLearning, getAdapterPath } = require('./flow-model-adapter');
@@ -26,9 +26,8 @@ const {
26
26
  } = require('./flow-failure-categories');
27
27
  const { validateRepoFormat, safeGitCommand, sanitizeCommitMessage } = require('./flow-security');
28
28
 
29
- const PROJECT_ROOT = getProjectRoot();
30
- const LEARNING_LOG_PATH = path.join(PROJECT_ROOT, '.workflow', 'state', 'adaptive-learning.json');
31
- const STRATEGY_STATS_PATH = path.join(PROJECT_ROOT, '.workflow', 'state', 'strategy-effectiveness.json');
29
+ const LEARNING_LOG_PATH = path.join(PATHS.state, 'adaptive-learning.json');
30
+ const STRATEGY_STATS_PATH = path.join(PATHS.state, 'strategy-effectiveness.json');
32
31
 
33
32
  // ============================================================
34
33
  // Failure Analysis
@@ -690,7 +689,7 @@ function exportLearningsForSharing() {
690
689
  }
691
690
 
692
691
  // Load model adapter files and extract learnings sections
693
- const adaptersDir = path.join(PROJECT_ROOT, '.workflow', 'model-adapters');
692
+ const adaptersDir = PATHS.modelAdapters;
694
693
  if (fs.existsSync(adaptersDir)) {
695
694
  const adapterFiles = fs.readdirSync(adaptersDir).filter(f => f.endsWith('.md') && !f.startsWith('_'));
696
695
 
@@ -845,7 +844,7 @@ async function contributeLearnings(upstreamRepo = 'your-org/wogi-flow', options
845
844
 
846
845
  try {
847
846
  // Create contribution file
848
- const contributionDir = path.join(PROJECT_ROOT, '.workflow', 'contributions');
847
+ const contributionDir = path.join(PATHS.workflow, 'contributions');
849
848
  if (!fs.existsSync(contributionDir)) {
850
849
  fs.mkdirSync(contributionDir, { recursive: true });
851
850
  }
@@ -935,7 +934,7 @@ async function createAutoPR(upstreamRepo, options = {}) {
935
934
 
936
935
  try {
937
936
  // Clone fork, create branch, add files, push, create PR
938
- const tempDir = path.join(PROJECT_ROOT, '.workflow', 'temp-pr');
937
+ const tempDir = path.join(PATHS.workflow, 'temp-pr');
939
938
 
940
939
  console.log(`${colors.cyan}Creating PR automatically...${colors.reset}`);
941
940
 
@@ -1136,12 +1135,12 @@ if (require.main === module) {
1136
1135
  }
1137
1136
 
1138
1137
  // Save export file
1139
- const exportPath = path.join(PROJECT_ROOT, '.workflow', 'learnings-export.json');
1138
+ const exportPath = path.join(PATHS.workflow, 'learnings-export.json');
1140
1139
  fs.writeFileSync(exportPath, JSON.stringify(data, null, 2));
1141
1140
  console.log(`\n${colors.green}✅ Exported to: ${exportPath}${colors.reset}`);
1142
1141
 
1143
1142
  // Also create PR-ready markdown
1144
- const prPath = path.join(PROJECT_ROOT, '.workflow', 'learnings-contribution.md');
1143
+ const prPath = path.join(PATHS.workflow, 'learnings-contribution.md');
1145
1144
  fs.writeFileSync(prPath, formatExportForPR(data));
1146
1145
  console.log(`${colors.green}✅ PR-ready format: ${prPath}${colors.reset}`);
1147
1146
 
@@ -16,7 +16,7 @@
16
16
 
17
17
  const fs = require('node:fs');
18
18
  const path = require('node:path');
19
- const readline = require('node:readline');
19
+ const readline = require('node:readline/promises');
20
20
  const {
21
21
  PATHS,
22
22
  PROJECT_ROOT,
@@ -36,7 +36,7 @@ const { syncDecisionsToRules } = require('./flow-rules-sync');
36
36
  // ============================================================
37
37
 
38
38
  const SKILLS_DIR = PATHS.skills;
39
- const CORRECTIONS_DIR = path.join(PROJECT_ROOT, '.workflow', 'corrections');
39
+ const CORRECTIONS_DIR = PATHS.corrections;
40
40
 
41
41
  // ============================================================
42
42
  // Data Collection
@@ -386,7 +386,7 @@ async function runPromotionWizard(data) {
386
386
  output: process.stdout
387
387
  });
388
388
 
389
- const prompt = (q) => new Promise(r => rl.question(q, r));
389
+ const prompt = (q) => rl.question(q);
390
390
 
391
391
  console.log(color('cyan', 'Pattern Promotion Wizard'));
392
392
  console.log('');
@@ -435,10 +435,15 @@ function appendToDecisions(pattern) {
435
435
  const entry = `\n## ${date} - Promoted Pattern\n\n**Rule**: ${pattern}\n**Source**: Aggregated from learnings (3+ occurrences)\n\n`;
436
436
 
437
437
  content += entry;
438
- writeFile(decisionsPath, content);
439
438
 
440
- // Sync to .claude/rules/ for Claude Code integration
441
- syncDecisionsToRules();
439
+ // Route through orchestrator for locking and dedup
440
+ try {
441
+ const { writeToDecisions } = require('./flow-learning-orchestrator');
442
+ writeToDecisions({ content, entryText: pattern, caller: 'flow-aggregate/appendToDecisions', syncRules: true }).catch(() => {});
443
+ } catch (_err) {
444
+ writeFile(decisionsPath, content);
445
+ syncDecisionsToRules();
446
+ }
442
447
  }
443
448
 
444
449
  /**
@@ -15,7 +15,7 @@
15
15
 
16
16
  const fs = require('node:fs');
17
17
  const path = require('node:path');
18
- const { getProjectRoot, getConfig, color, success, warn, error, safeJsonParse } = require('./flow-utils');
18
+ const { getProjectRoot, getConfig, color, success, warn, error, safeJsonParse, PATHS } = require('./flow-utils');
19
19
  const {
20
20
  findSimilarItems,
21
21
  generateAIDecisionPrompt,
@@ -24,10 +24,8 @@ const {
24
24
  } = require('./flow-semantic-match');
25
25
  const { BaseScanner, PROJECT_ROOT } = require('./flow-scanner-base');
26
26
 
27
- const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
28
- const STATE_DIR = path.join(WORKFLOW_DIR, 'state');
29
- const INDEX_PATH = path.join(STATE_DIR, 'api-index.json');
30
- const MAP_PATH = path.join(STATE_DIR, 'api-map.md');
27
+ const INDEX_PATH = path.join(PATHS.state, 'api-index.json');
28
+ const MAP_PATH = path.join(PATHS.state, 'api-map.md');
31
29
 
32
30
  // ============================================================
33
31
  // Configuration
@@ -568,7 +566,7 @@ class APIScanner extends BaseScanner {
568
566
  */
569
567
  save() {
570
568
  this.prune();
571
- fs.mkdirSync(STATE_DIR, { recursive: true });
569
+ fs.mkdirSync(PATHS.state, { recursive: true });
572
570
  fs.writeFileSync(INDEX_PATH, JSON.stringify(this.registry, null, 2));
573
571
  success(`Saved to ${path.relative(PROJECT_ROOT, INDEX_PATH)}`);
574
572
  }
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  /**
4
2
  * Assumption Detector Library
5
3
  *
@@ -95,11 +95,24 @@ function findTodos() {
95
95
  if (!line) continue;
96
96
  const match = line.match(/^(.+):(\d+):(.+)$/);
97
97
  if (match) {
98
+ const file = match[1];
99
+ const text = match[3].trim();
100
+
101
+ // Exclude self-references: the TODO scanner's own code mentions
102
+ // TODO/FIXME/HACK as search patterns, not as actual action items.
103
+ // Also exclude lines that are clearly about scanning for TODOs
104
+ // (e.g., pattern arrays, grep invocations, variable names).
105
+ if (file === 'scripts/flow-audit.js') continue;
106
+
107
+ // Skip lines where the pattern appears only inside a string literal
108
+ // used as a search term (e.g., patterns = ['TODO', 'FIXME', ...])
109
+ if (/\[\s*['"](?:TODO|FIXME|HACK|WORKAROUND|TEMPORARY)['"]/.test(text)) continue;
110
+
98
111
  results.push({
99
112
  type: pattern,
100
- file: match[1],
113
+ file,
101
114
  line: parseInt(match[2], 10),
102
- text: match[3].trim()
115
+ text
103
116
  });
104
117
  }
105
118
  }
@@ -55,8 +55,6 @@ try {
55
55
  // Smart context gatherer not available - that's ok
56
56
  }
57
57
 
58
- const PROJECT_ROOT = getProjectRoot();
59
-
60
58
  // ============================================================
61
59
  // Index Freshness Check
62
60
  // ============================================================
@@ -241,7 +239,7 @@ function inferTaskType(keywords) {
241
239
  */
242
240
  function searchTraces(keywords) {
243
241
  const results = [];
244
- const tracesDir = path.join(PATHS.traces || path.join(PATHS.workflow, 'traces'));
242
+ const tracesDir = path.join(PATHS.traces || PATHS.traces);
245
243
 
246
244
  if (!fs.existsSync(tracesDir)) return results;
247
245
 
@@ -278,7 +276,7 @@ function searchTraces(keywords) {
278
276
 
279
277
  results.push({
280
278
  source: 'trace',
281
- path: path.relative(PROJECT_ROOT, tracePath),
279
+ path: path.relative(PATHS.root, tracePath),
282
280
  name: traceName,
283
281
  query: queryMatch ? queryMatch[1] : traceName,
284
282
  status: statusMatch ? statusMatch[1] : 'unknown',
@@ -415,7 +413,7 @@ function searchComponentIndex(keywords, config = null) {
415
413
  */
416
414
  function grepCodebase(keywords, maxResults = 10, config = null) {
417
415
  const results = [];
418
- const srcDir = path.join(PROJECT_ROOT, 'src');
416
+ const srcDir = path.join(PATHS.root, 'src');
419
417
 
420
418
  if (!fs.existsSync(srcDir)) return results;
421
419
 
@@ -453,7 +451,7 @@ function grepCodebase(keywords, maxResults = 10, config = null) {
453
451
  for (const file of files) {
454
452
  if (results.length >= effectiveMaxResults) break;
455
453
 
456
- const relPath = path.relative(PROJECT_ROOT, file);
454
+ const relPath = path.relative(PATHS.root, file);
457
455
  if (!results.some(r => r.path === relPath)) {
458
456
  // Optionally read file content with truncation
459
457
  let content = null;
@@ -637,11 +635,9 @@ async function enrichWithLSP(fileResults, config) {
637
635
  if (filesToEnrich.length === 0) return fileResults;
638
636
 
639
637
  try {
640
- // Create timeout with cleanup to prevent resource leak
641
- let timeoutId;
642
- const timeoutPromise = new Promise(resolve => {
643
- timeoutId = setTimeout(() => resolve(filesToEnrich), timeout);
644
- });
638
+ // Create timeout with cleanup using AbortController
639
+ const ac = new AbortController();
640
+ const timeoutPromise = require('node:timers/promises').setTimeout(timeout, filesToEnrich, { signal: ac.signal }).catch(() => filesToEnrich);
645
641
 
646
642
  const enriched = await Promise.race([
647
643
  Promise.all(filesToEnrich.map(async (result) => {
@@ -670,7 +666,7 @@ async function enrichWithLSP(fileResults, config) {
670
666
  ]);
671
667
 
672
668
  // Clean up timeout to prevent resource leak
673
- clearTimeout(timeoutId);
669
+ ac.abort();
674
670
 
675
671
  // Merge enriched results back into full list
676
672
  const enrichedMap = new Map(enriched.map(r => [r.path, r]));
@@ -202,6 +202,7 @@ function loadAutoPatterns() {
202
202
 
203
203
  /**
204
204
  * Save auto-captured patterns back to feedback-patterns.md
205
+ * Routes through the learning orchestrator for locking and dedup.
205
206
  * @param {Array} patterns - Array of pattern objects
206
207
  */
207
208
  function saveAutoPatterns(patterns) {
@@ -211,31 +212,35 @@ function saveAutoPatterns(patterns) {
211
212
  }
212
213
 
213
214
  try {
214
- let content = fs.readFileSync(FEEDBACK_PATTERNS_PATH, 'utf-8');
215
+ const { modifyFeedbackPatterns } = require('./flow-learning-orchestrator');
215
216
 
216
- // Build new table (using DRY constants)
217
- const rows = patterns.map(p =>
218
- `| ${p.date} | ${p.pattern} | ${p.source} | ${p.count} | ${p.confidence}% | ${p.status} |`
219
- );
217
+ modifyFeedbackPatterns((currentContent) => {
218
+ let content = currentContent;
220
219
 
221
- const newSection = `${TABLE_FORMAT.sectionHeader}\n\n${TABLE_FORMAT.header}\n${TABLE_FORMAT.separator}\n${rows.join('\n')}`;
222
-
223
- // Replace or add section
224
- if (content.includes('## Auto-Captured Patterns')) {
225
- content = content.replace(
226
- /## Auto-Captured Patterns[\s\S]*?(?=\n## |\n---\n\n## |$)/,
227
- newSection + '\n\n'
220
+ // Build new table (using DRY constants)
221
+ const rows = patterns.map(p =>
222
+ `| ${p.date} | ${p.pattern} | ${p.source} | ${p.count} | ${p.confidence}% | ${p.status} |`
228
223
  );
229
- } else {
230
- // Add before "## Promotion History" or at the end
231
- if (content.includes('## Promotion History')) {
232
- content = content.replace('## Promotion History', newSection + '\n\n---\n\n## Promotion History');
224
+
225
+ const newSection = `${TABLE_FORMAT.sectionHeader}\n\n${TABLE_FORMAT.header}\n${TABLE_FORMAT.separator}\n${rows.join('\n')}`;
226
+
227
+ // Replace or add section
228
+ if (content.includes('## Auto-Captured Patterns')) {
229
+ content = content.replace(
230
+ /## Auto-Captured Patterns[\s\S]*?(?=\n## |\n---\n\n## |$)/,
231
+ newSection + '\n\n'
232
+ );
233
233
  } else {
234
- content = content.trimEnd() + '\n\n---\n\n' + newSection + '\n';
234
+ // Add before "## Promotion History" or at the end
235
+ if (content.includes('## Promotion History')) {
236
+ content = content.replace('## Promotion History', newSection + '\n\n---\n\n## Promotion History');
237
+ } else {
238
+ content = content.trimEnd() + '\n\n---\n\n' + newSection + '\n';
239
+ }
235
240
  }
236
- }
237
241
 
238
- fs.writeFileSync(FEEDBACK_PATTERNS_PATH, content, 'utf-8');
242
+ return { content, entryText: null }; // Bulk rewrite, no single entry dedup
243
+ }, { caller: 'flow-auto-learn/saveAutoPatterns', skipDedup: true });
239
244
  } catch (err) {
240
245
  warn(`Could not save feedback-patterns.md: ${err.message}`);
241
246
  }
@@ -449,6 +454,7 @@ function handlePromotion(pattern, config) {
449
454
 
450
455
  /**
451
456
  * Promote pattern to decisions.md
457
+ * Routes through the learning orchestrator for locking and dedup.
452
458
  * @param {Object} pattern - Pattern to promote
453
459
  */
454
460
  function promoteToDecisions(pattern) {
@@ -458,37 +464,39 @@ function promoteToDecisions(pattern) {
458
464
  }
459
465
 
460
466
  try {
461
- let content = fs.readFileSync(DECISIONS_PATH, 'utf-8');
467
+ const { modifyDecisions } = require('./flow-learning-orchestrator');
468
+ const today = getTodayDate();
469
+ const escapedPattern = pattern.pattern.replace(/[#*_\[\]()\\]/g, '\\$&');
462
470
 
463
- // Find Coding Standards section
464
- const sectionHeader = '## Coding Standards';
471
+ modifyDecisions((currentContent) => {
472
+ let content = currentContent;
465
473
 
466
- if (!content.includes(sectionHeader)) {
467
- warn(`Could not promote pattern: Coding Standards section not found in decisions.md`);
468
- return;
469
- }
474
+ // Find Coding Standards section
475
+ const sectionHeader = '## Coding Standards';
470
476
 
471
- // Generate rule entry (escape markdown special characters in pattern name)
472
- const today = getTodayDate();
473
- const escapedPattern = pattern.pattern.replace(/[#*_\[\]()\\]/g, '\\$&');
474
- const ruleEntry = `\n### ${escapedPattern} (${today})
477
+ if (!content.includes(sectionHeader)) {
478
+ warn(`Could not promote pattern: Coding Standards section not found in decisions.md`);
479
+ return null;
480
+ }
481
+
482
+ // Generate rule entry
483
+ const ruleEntry = `\n### ${escapedPattern} (${today})
475
484
  **Source**: Auto-learned from ${pattern.count} occurrences (${pattern.source})
476
485
  **Rule**: [Describe the pattern rule here]
477
486
  `;
478
487
 
479
- // Insert after Coding Standards header
480
- const insertPoint = content.indexOf(sectionHeader) + sectionHeader.length;
481
- const nextSection = content.indexOf('\n## ', insertPoint);
488
+ // Insert after Coding Standards header
489
+ const insertPoint = content.indexOf(sectionHeader) + sectionHeader.length;
490
+ const nextSection = content.indexOf('\n## ', insertPoint);
482
491
 
483
- if (nextSection > insertPoint) {
484
- // Insert before next section
485
- content = content.slice(0, nextSection) + ruleEntry + content.slice(nextSection);
486
- } else {
487
- // Append to section
488
- content = content.slice(0, insertPoint) + ruleEntry + content.slice(insertPoint);
489
- }
492
+ if (nextSection > insertPoint) {
493
+ content = content.slice(0, nextSection) + ruleEntry + content.slice(nextSection);
494
+ } else {
495
+ content = content.slice(0, insertPoint) + ruleEntry + content.slice(insertPoint);
496
+ }
490
497
 
491
- fs.writeFileSync(DECISIONS_PATH, content, 'utf-8');
498
+ return { content, entryText: pattern.pattern };
499
+ }, { caller: 'flow-auto-learn/promoteToDecisions', syncRules: true });
492
500
 
493
501
  // Update pattern status
494
502
  const patterns = loadAutoPatterns();
@@ -497,14 +505,6 @@ function promoteToDecisions(pattern) {
497
505
  updated.status = 'Promoted';
498
506
  saveAutoPatterns(patterns);
499
507
  }
500
-
501
- // Sync rules
502
- try {
503
- require('./flow-rules-sync');
504
- } catch (syncErr) {
505
- // Log the failure for debugging
506
- info(`Note: Rules sync skipped - ${syncErr.code === 'MODULE_NOT_FOUND' ? 'module not found' : syncErr.message}`);
507
- }
508
508
  } catch (err) {
509
509
  warn(`Could not promote to decisions.md: ${err.message}`);
510
510
  }
@@ -23,15 +23,14 @@ const {
23
23
  fileExists,
24
24
  color,
25
25
  printHeader,
26
- printSection, success } = require('./flow-utils');
26
+ printSection, success, PATHS } = require('./flow-utils');
27
27
 
28
28
  // ============================================================
29
29
  // Configuration
30
30
  // ============================================================
31
31
 
32
- const PROJECT_ROOT = getProjectRoot();
33
- const BACKGROUND_STATE_PATH = path.join(PROJECT_ROOT, '.workflow', 'state', 'background-tasks.json');
34
- const LOGS_DIR = path.join(PROJECT_ROOT, '.workflow', 'logs');
32
+ const BACKGROUND_STATE_PATH = path.join(PATHS.state, 'background-tasks.json');
33
+ const LOGS_DIR = PATHS.logs;
35
34
 
36
35
  // Available background tasks with their configurations
37
36
  const AVAILABLE_TASKS = {
@@ -141,7 +140,7 @@ function runBackgroundTask(taskName, options = {}) {
141
140
  throw new Error(`Unknown task: ${taskName}. Use 'flow background list' to see available tasks.`);
142
141
  }
143
142
 
144
- const scriptPath = path.join(PROJECT_ROOT, 'scripts', taskConfig.script);
143
+ const scriptPath = path.join(PATHS.root, 'scripts', taskConfig.script);
145
144
 
146
145
  if (!fileExists(scriptPath)) {
147
146
  throw new Error(`Script not found: ${taskConfig.script}`);
@@ -163,7 +162,7 @@ function runBackgroundTask(taskName, options = {}) {
163
162
 
164
163
  // Spawn the process
165
164
  const child = spawn('node', [scriptPath, ...args], {
166
- cwd: PROJECT_ROOT,
165
+ cwd: PATHS.root,
167
166
  detached: true,
168
167
  stdio: ['ignore', 'pipe', 'pipe'],
169
168
  env: { ...process.env, BACKGROUND_TASK: 'true', TASK_ID: taskId }
@@ -18,14 +18,12 @@ const path = require('node:path');
18
18
  const crypto = require('node:crypto');
19
19
 
20
20
  // Import canonical safeJsonParse from flow-utils (consolidated per code review)
21
- const { safeJsonParse } = require('./flow-utils');
21
+ const { safeJsonParse, PATHS } = require('./flow-utils');
22
22
 
23
23
  // Project paths
24
24
  const PROJECT_ROOT = path.resolve(__dirname, '..');
25
- const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
26
- const STATE_DIR = path.join(WORKFLOW_DIR, 'state');
27
- const CONFIG_PATH = path.join(WORKFLOW_DIR, 'config.json');
28
- const SYNC_STATE_PATH = path.join(STATE_DIR, 'bridge-sync.json');
25
+ const CONFIG_PATH = path.join(PATHS.workflow, 'config.json');
26
+ const SYNC_STATE_PATH = path.join(PATHS.state, 'bridge-sync.json');
29
27
 
30
28
  // CLI type to output file mapping (Claude Code only)
31
29
  const CLI_OUTPUT_FILES = {
@@ -60,7 +58,7 @@ function getTemplateChecksum(cliType) {
60
58
  const templateName = CLI_TEMPLATES[cliType];
61
59
  if (!templateName) return '';
62
60
 
63
- const templatePath = path.join(WORKFLOW_DIR, 'templates', templateName);
61
+ const templatePath = path.join(PATHS.workflow, 'templates', templateName);
64
62
  const content = fs.readFileSync(templatePath, 'utf-8');
65
63
  return crypto.createHash('md5').update(content).digest('hex');
66
64
  } catch (_err) {
@@ -94,8 +92,8 @@ function readSyncState() {
94
92
  function writeSyncState(state) {
95
93
  try {
96
94
  // Ensure state directory exists
97
- if (!fs.existsSync(STATE_DIR)) {
98
- fs.mkdirSync(STATE_DIR, { recursive: true });
95
+ if (!fs.existsSync(PATHS.state)) {
96
+ fs.mkdirSync(PATHS.state, { recursive: true });
99
97
  }
100
98
  fs.writeFileSync(SYNC_STATE_PATH, JSON.stringify(state, null, 2));
101
99
  } catch (err) {
@@ -220,7 +218,7 @@ async function autoSyncBridge(cliType = 'claude-code', options = {}) {
220
218
  // Load bridges module
221
219
  let bridges;
222
220
  try {
223
- bridges = require(path.join(PROJECT_ROOT, '.workflow', 'bridges'));
221
+ bridges = require(PATHS.bridges);
224
222
  } catch (err) {
225
223
  if (process.env.DEBUG) {
226
224
  console.error(`[bridge-state] Failed to load bridges: ${err.message}`);
@@ -294,7 +292,7 @@ function getSyncStatus() {
294
292
  const check = needsSync(cliType);
295
293
  const outputPath = getOutputFilePath(cliType);
296
294
  const templateName = CLI_TEMPLATES[cliType];
297
- const templatePath = templateName ? path.join(WORKFLOW_DIR, 'templates', templateName) : null;
295
+ const templatePath = templateName ? path.join(PATHS.workflow, 'templates', templateName) : null;
298
296
 
299
297
  return {
300
298
  'claude-code': {
@@ -339,9 +339,7 @@ async function runCompact() {
339
339
  * Sleep for specified milliseconds
340
340
  * @param {number} ms - Milliseconds to sleep
341
341
  */
342
- function sleep(ms) {
343
- return new Promise(resolve => setTimeout(resolve, ms));
344
- }
342
+ const { setTimeout: sleep } = require('node:timers/promises');
345
343
 
346
344
  /**
347
345
  * Format duration in human-readable form