wogiflow 2.4.1 → 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 (198) 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/commit-log-gate.js +21 -10
  167. package/scripts/hooks/core/config-change.js +1 -0
  168. package/scripts/hooks/core/extension-registry.js +0 -2
  169. package/scripts/hooks/core/instructions-loaded.js +1 -1
  170. package/scripts/hooks/core/observation-capture.js +5 -5
  171. package/scripts/hooks/core/phase-gate.js +5 -0
  172. package/scripts/hooks/core/post-compact.js +1 -12
  173. package/scripts/hooks/core/research-gate.js +2 -12
  174. package/scripts/hooks/core/routing-gate.js +6 -0
  175. package/scripts/hooks/core/task-completed.js +12 -0
  176. package/scripts/hooks/core/task-gate.js +27 -0
  177. package/scripts/hooks/core/worktree-lifecycle.js +1 -1
  178. package/scripts/hooks/entry/claude-code/config-change.js +6 -29
  179. package/scripts/hooks/entry/claude-code/instructions-loaded.js +5 -30
  180. package/scripts/hooks/entry/claude-code/post-compact.js +4 -31
  181. package/scripts/hooks/entry/claude-code/post-tool-use.js +121 -172
  182. package/scripts/hooks/entry/claude-code/pre-tool-use.js +260 -361
  183. package/scripts/hooks/entry/claude-code/session-end.js +4 -28
  184. package/scripts/hooks/entry/claude-code/session-start.js +205 -243
  185. package/scripts/hooks/entry/claude-code/setup.js +8 -49
  186. package/scripts/hooks/entry/claude-code/stop.js +40 -72
  187. package/scripts/hooks/entry/claude-code/task-completed.js +4 -28
  188. package/scripts/hooks/entry/claude-code/user-prompt-submit.js +113 -195
  189. package/scripts/hooks/entry/claude-code/worktree-create.js +6 -25
  190. package/scripts/hooks/entry/claude-code/worktree-remove.js +6 -25
  191. package/scripts/hooks/entry/shared/hook-runner.js +99 -0
  192. package/scripts/hooks/entry/shared/read-stdin.js +0 -2
  193. package/scripts/registries/api-registry.js +0 -2
  194. package/scripts/registries/component-registry.js +5 -9
  195. package/scripts/registries/contract-scanner.js +2 -9
  196. package/scripts/registries/function-registry.js +0 -2
  197. package/scripts/registries/schema-registry.js +14 -18
  198. package/scripts/registries/service-registry.js +23 -27
@@ -70,6 +70,7 @@ flow parallel check # See available parallel tasks
70
70
  | 1.9.5+ | 2.1.74+ | SessionEnd timeout fix, managed policy ask rules, autoMemoryDirectory, Agent tool routing gate fix |
71
71
  | 2.0.0+ | 2.1.76+ | PostCompact hook, Elicitation/ElicitationResult events, deferred tool schema fix |
72
72
  | 2.1.0+ | 2.1.77+ | PreToolUse allow/deny separation, 128k output tokens, worktree sparse checkout, compaction circuit breaker |
73
+ | 2.4.0+ | 2.1.83+ | managed-settings.d/, CwdChanged/FileChanged hooks, ENV_SCRUB, --channels limitations, MEMORY.md 25KB cap |
73
74
 
74
75
  ### Environment Variables (2.1.19+)
75
76
 
@@ -236,6 +237,32 @@ await cancelTask('wf-123', 'superseded', false);
236
237
  - **Memory growth fix**: Fixed progress messages surviving compaction in long-running sessions. Reduces memory pressure during long WogiFlow bulk-loop sessions.
237
238
  - **Faster startup on macOS**: ~60ms faster by reading keychain credentials in parallel. Faster `--resume` on fork-heavy sessions — up to 45% faster loading and ~100-150MB less peak memory. Benefits WogiFlow sessions with heavy hook context.
238
239
 
240
+ ### Features in 2.1.83+
241
+
242
+ - **managed-settings.d/ drop-in directory**: A `managed-settings.d/` directory alongside `managed-settings.json` allows separate teams/tools to deploy independent policy fragments that merge alphabetically. WogiFlow currently generates `settings.local.json` — for wogiflow-cloud teams, this opens the door to deploying team policies as individual fragments (e.g., `00-wogiflow-hooks.json`, `50-team-policy.json`). No code change needed yet; tracked as cloud opportunity.
243
+
244
+ - **CwdChanged and FileChanged hook events**: Two new hook events. `CwdChanged` fires when the working directory changes (useful for direnv-style setups). `FileChanged` fires when watched files change on disk — WogiFlow could use this to detect external changes to `.workflow/state/` files and auto-rescan. Added to `UNUSED_SUPPORTED_EVENTS` in `claude-code.js`. Implementation deferred to a future task.
245
+
246
+ - **CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1**: Strips Anthropic and cloud provider credentials from subprocess environments (Bash tool, hooks, MCP stdio servers). **Impact on WogiFlow**: Hooks are subprocesses, so any hook that needs API keys (e.g., `flow-correction-detector.js` spawning a child process for async correction detection) will not have credentials available. The correction detector now gracefully degrades (returns `isCorrection: false`) when no API key is available. `flow-providers.js` makes direct `https.request()` calls from the hook process itself (not a subprocess of the subprocess), so it reads `process.env` before scrubbing — but if ENV_SCRUB applies transitively to hook processes, provider calls would also be affected. For wogiflow-cloud: if cloud sync hooks need API keys, they must use an alternative credential mechanism (file-based, keychain, or passed via hook input JSON).
247
+
248
+ - **Agents can declare initialPrompt in frontmatter**: Agents can now auto-submit a first turn without the AI composing it. WogiFlow's 11 persona agents in `agents/` could use this for standardized opening probes. No code change needed; optimization opportunity.
249
+
250
+ - **Background subagent fixes**: (1) Fixed subagents becoming invisible after context compaction — this prevented duplicate agent spawns in WogiFlow's parallel explore phase. (2) Fixed agents staying stuck in "running" state when git/API calls hang during cleanup. Both fixes improve reliability of `/wogi-start` explore phase and `/wogi-bulk-loop`.
251
+
252
+ - **--channels disables AskUserQuestion and plan mode**: When `--channels` is active (remote/SDK), `AskUserQuestion` and plan-mode tools are disabled. **Impact on WogiFlow**: WogiFlow uses `AskUserQuestion` extensively for approval gates, clarifying questions, and interactive decisions. In `--channels` mode, these will silently fail or be unavailable. WogiFlow should detect channels mode and fall back to non-interactive patterns: auto-approve with defaults, skip clarifying questions, use best-effort decisions. Documented in CLAUDE.md template and wogi-start command.
253
+
254
+ - **TaskOutput deprecated**: `TaskOutput` tool is deprecated in favor of using `Read` on the background task's output file path. WogiFlow does not use `TaskOutput` directly (confirmed by codebase search). No change needed.
255
+
256
+ - **MEMORY.md index truncation**: Now truncates at **25KB** as well as 200 lines (previously only 200 lines). WogiFlow's MEMORY.md enforcement block at the top consumes space from this budget. Projects with large MEMORY.md files may lose entries silently. The CLAUDE.md template's auto-memory section already mentions 200 lines; the 25KB limit is enforced by Claude Code's system prompt and does not need to be duplicated in the template.
257
+
258
+ - **Plugin manifest.userConfig**: Plugins can now prompt for configuration at enable time, with `sensitive: true` values stored in keychain (macOS) or protected credentials file. If WogiFlow becomes a Claude Code plugin, this provides native credential storage for cloud API tokens and model API keys — replacing `wogi login`'s file-based token storage. Tracked as cloud opportunity.
259
+
260
+ - **WebFetch identifies as Claude-User**: `WebFetch` now sends a `Claude-User` user agent so site operators can recognize and allowlist/block Claude Code traffic via `robots.txt`. WogiFlow's explore agents (Agent 2: Best Practices, Agent 3: Version Verifier) use `WebFetch` for research. If sites block `Claude-User`, research agents will get empty results. Agents should treat unexpectedly empty WebFetch results as potentially blocked and log a warning.
261
+
262
+ - **--mcp-config bypass fix**: Fixed `--mcp-config` CLI flag bypassing `allowedMcpServers`/`deniedMcpServers` managed policy enforcement. Security improvement — no WogiFlow code change needed.
263
+
264
+ - **Uninstalled plugin hooks fix**: Fixed uninstalled plugin hooks continuing to fire until the next session. Improves hook hygiene for WogiFlow plugin management.
265
+
239
266
  ### Simple Mode Naming Distinction
240
267
 
241
268
  Claude Code's `CLAUDE_CODE_SIMPLE` environment variable (which enables a simplified tool set) is **unrelated** to WogiFlow's `loops.simpleMode` (a lightweight task completion loop using string detection). They are separate features that happen to share the word "simple":
@@ -135,6 +135,6 @@
135
135
  ]
136
136
  },
137
137
  "_wogiFlowManaged": true,
138
- "_wogiFlowVersion": "1.9.10",
138
+ "_wogiFlowVersion": "2.4.2",
139
139
  "_comment": "Shared WogiFlow hook configuration. Committed to repo for team use. User-specific overrides go in settings.local.json."
140
140
  }
@@ -100,7 +100,7 @@
100
100
  "displayName": "Claude Sonnet 4.6",
101
101
  "contextWindow": 200000,
102
102
  "contextWindowBeta": 1000000,
103
- "maxOutputTokens": 64000,
103
+ "maxOutputTokens": 128000,
104
104
  "costTier": "standard",
105
105
  "pricing": {
106
106
  "inputPer1kTokens": 0.003,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wogiflow",
3
- "version": "2.4.1",
3
+ "version": "2.4.3",
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
  }