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
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Wogi Flow - Hook Status Aggregator
3
+ *
4
+ * Pre-computes a single hook-status.json file containing all state
5
+ * that PreToolUse/PostToolUse hooks need. Hooks read 1 file instead
6
+ * of 6-8 separate reads per invocation.
7
+ *
8
+ * Writers: flow-start.js, task-completed.js, routing-gate.js, phase-gate.js
9
+ * Reader: pre-tool-use.js, post-tool-use.js
10
+ *
11
+ * IMPORTANT: This module must NOT require flow-utils at load time (circular dep risk).
12
+ * flow-utils does NOT require this module — keep it that way.
13
+ */
14
+
15
+ const fs = require('node:fs');
16
+ const path = require('node:path');
17
+
18
+ // Lazy-load to avoid circular deps at module level
19
+ let _PATHS = null;
20
+ function getPATHS() {
21
+ if (!_PATHS) {
22
+ _PATHS = require('./flow-paths').PATHS;
23
+ }
24
+ return _PATHS;
25
+ }
26
+
27
+ // Lazy-load safeJsonParseString to avoid circular deps
28
+ let _safeJsonParseString = null;
29
+ function getSafeJsonParseString() {
30
+ if (!_safeJsonParseString) {
31
+ _safeJsonParseString = require('./flow-io').safeJsonParseString;
32
+ }
33
+ return _safeJsonParseString;
34
+ }
35
+
36
+ const HOOK_STATUS_FILENAME = 'hook-status.json';
37
+
38
+ function getHookStatusPath() {
39
+ return path.join(getPATHS().state, HOOK_STATUS_FILENAME);
40
+ }
41
+
42
+ /**
43
+ * Read the current hook status (single file read).
44
+ * Uses safeJsonParseString for prototype pollution protection.
45
+ * Returns null if file doesn't exist, is corrupt, or stale vs config.json.
46
+ * @returns {Object|null}
47
+ */
48
+ function readHookStatus() {
49
+ try {
50
+ const statusPath = getHookStatusPath();
51
+ const content = fs.readFileSync(statusPath, 'utf-8');
52
+ const parsed = getSafeJsonParseString()(content, null);
53
+ if (!parsed) return null;
54
+
55
+ // Staleness check: if config.json is newer than hook-status.json, invalidate
56
+ try {
57
+ const configPath = path.join(getPATHS().workflow, 'config.json');
58
+ const configMtime = fs.statSync(configPath).mtimeMs;
59
+ const statusMtime = fs.statSync(statusPath).mtimeMs;
60
+ if (configMtime > statusMtime) {
61
+ // Config changed after hook-status was written — refresh needed
62
+ return null;
63
+ }
64
+ } catch (_err) { /* config missing or stat failed — use cached status */ }
65
+
66
+ return parsed;
67
+ } catch (_err) {
68
+ return null;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Write the hook status file atomically with PID-scoped temp file.
74
+ * @param {Object} status - The full status object
75
+ */
76
+ function writeHookStatus(status) {
77
+ status.updatedAt = new Date().toISOString();
78
+ const statusPath = getHookStatusPath();
79
+ const tmpPath = statusPath + '.tmp.' + process.pid;
80
+ try {
81
+ fs.writeFileSync(tmpPath, JSON.stringify(status, null, 2));
82
+ fs.renameSync(tmpPath, statusPath);
83
+ } catch (_err) {
84
+ // Non-blocking — hooks fall back to individual reads
85
+ try { fs.unlinkSync(tmpPath); } catch (_e) { /* ignore */ }
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Build enforcement block from config (shared by createDefaultStatus + refreshHookStatus).
91
+ * @param {Object} config - Parsed config object
92
+ * @returns {Object} enforcement flags + componentReuse + phaseGate
93
+ */
94
+ function buildEnforcementFromConfig(config) {
95
+ return {
96
+ enforcement: {
97
+ taskGating: config.enforcement?.taskGating?.enabled === true,
98
+ scopeGating: config.enforcement?.scopeGating?.enabled === true,
99
+ routingGate: config.enforcement?.routingGate?.enabled === true,
100
+ commitLogGate: config.enforcement?.commitLogGate?.enabled === true,
101
+ todoWriteGate: config.enforcement?.todoWriteGate?.enabled === true,
102
+ loopEnforcement: config.enforcement?.loopEnforcement?.enabled === true
103
+ },
104
+ componentReuse: config.componentReuse?.enabled === true,
105
+ // Correct path: hooks.rules.phaseGate.enabled (matches phase-gate.js:84)
106
+ phaseGate: config.hooks?.rules?.phaseGate?.enabled === true
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Update specific fields in hook status (read-modify-write).
112
+ * Creates the file if it doesn't exist.
113
+ * Deep-merges known nested keys (enforcement, routing) to prevent clobbering.
114
+ * @param {Object} updates - Fields to merge into current status
115
+ */
116
+ function updateHookStatus(updates) {
117
+ const current = readHookStatus() || createDefaultStatus();
118
+ // Deep-merge known nested keys to prevent shallow-assign clobbering
119
+ if (updates.enforcement) {
120
+ updates.enforcement = { ...current.enforcement, ...updates.enforcement };
121
+ }
122
+ if (updates.routing) {
123
+ updates.routing = { ...current.routing, ...updates.routing };
124
+ }
125
+ Object.assign(current, updates);
126
+ writeHookStatus(current);
127
+ }
128
+
129
+ /**
130
+ * Create default hook status from current config + state.
131
+ * Called when hook-status.json doesn't exist yet.
132
+ * @returns {Object}
133
+ */
134
+ function createDefaultStatus() {
135
+ let config = {};
136
+ try {
137
+ const { getConfig } = require('./flow-utils');
138
+ config = getConfig() || {};
139
+ } catch (_err) { /* ignore */ }
140
+
141
+ return {
142
+ ...buildEnforcementFromConfig(config),
143
+ activeTask: null,
144
+ phase: 'idle',
145
+ routing: {
146
+ pending: false,
147
+ cleared: false,
148
+ clearedAt: null
149
+ },
150
+ changedFiles: [],
151
+ updatedAt: new Date().toISOString()
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Refresh hook status from current config and state files.
157
+ * Called at session start and after config changes.
158
+ */
159
+ function refreshHookStatus() {
160
+ let config = {};
161
+ try {
162
+ const { getConfig } = require('./flow-utils');
163
+ config = getConfig() || {};
164
+ } catch (_err) { /* ignore */ }
165
+
166
+ let activeTask = null;
167
+ try {
168
+ const { getReadyData } = require('./flow-utils');
169
+ const readyData = getReadyData();
170
+ if (readyData.inProgress && readyData.inProgress.length > 0) {
171
+ const task = readyData.inProgress[0];
172
+ activeTask = { id: task.id, title: task.title, routedAt: task.routedAt || null };
173
+ }
174
+ } catch (_err) { /* ignore */ }
175
+
176
+ let phase = 'idle';
177
+ try {
178
+ const { safeJsonParse } = require('./flow-utils');
179
+ const phasePath = path.join(getPATHS().state, 'workflow-phase.json');
180
+ const phaseData = safeJsonParse(phasePath, null);
181
+ if (phaseData) phase = phaseData.phase || 'idle';
182
+ } catch (_err) { /* ignore */ }
183
+
184
+ const status = {
185
+ ...buildEnforcementFromConfig(config),
186
+ activeTask,
187
+ phase,
188
+ routing: {
189
+ pending: false,
190
+ cleared: false,
191
+ clearedAt: null
192
+ },
193
+ changedFiles: []
194
+ };
195
+
196
+ writeHookStatus(status);
197
+ return status;
198
+ }
199
+
200
+ /**
201
+ * Update just the active task field.
202
+ * @param {Object|null} task - { id, title, routedAt } or null
203
+ */
204
+ function setActiveTask(task) {
205
+ updateHookStatus({ activeTask: task });
206
+ }
207
+
208
+ /**
209
+ * Update just the phase field.
210
+ * @param {string} phase
211
+ */
212
+ function setPhase(phase) {
213
+ updateHookStatus({ phase });
214
+ }
215
+
216
+ /**
217
+ * Update routing state.
218
+ * @param {Object} routing - { pending, cleared, clearedAt }
219
+ */
220
+ function setRouting(routing) {
221
+ updateHookStatus({ routing });
222
+ }
223
+
224
+ /**
225
+ * Add a file to the changed files list (deduped).
226
+ * @param {string} filePath
227
+ */
228
+ function trackFile(filePath) {
229
+ const current = readHookStatus();
230
+ if (!current) return;
231
+ if (!current.changedFiles) current.changedFiles = [];
232
+ if (!current.changedFiles.includes(filePath)) {
233
+ current.changedFiles.push(filePath);
234
+ writeHookStatus(current);
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Clear hook status on task completion.
240
+ */
241
+ function clearOnTaskComplete() {
242
+ const current = readHookStatus() || createDefaultStatus();
243
+ current.activeTask = null;
244
+ current.phase = 'idle';
245
+ current.routing = { pending: false, cleared: false, clearedAt: null };
246
+ current.changedFiles = [];
247
+ writeHookStatus(current);
248
+ }
249
+
250
+ module.exports = {
251
+ readHookStatus,
252
+ writeHookStatus,
253
+ updateHookStatus,
254
+ refreshHookStatus,
255
+ createDefaultStatus,
256
+ setActiveTask,
257
+ setPhase,
258
+ setRouting,
259
+ trackFile,
260
+ clearOnTaskComplete,
261
+ getHookStatusPath,
262
+ HOOK_STATUS_FILENAME
263
+ };
@@ -18,28 +18,26 @@ const fs = require('node:fs');
18
18
  const path = require('node:path');
19
19
  const {
20
20
  getProjectRoot,
21
- getConfig
21
+ getConfig, PATHS
22
22
  } = require('./flow-utils')
23
23
  const { color, success, warn, error } = require('./flow-output');;
24
24
 
25
25
  const { getAdapter, getAllAdapters, getAvailableAdapters } = require('./hooks/adapters');
26
- const { readJson } = require('./flow-io');
27
-
28
- const PROJECT_ROOT = getProjectRoot();
26
+ const { readJson, safeJsonParse } = require('./flow-io');
29
27
 
30
28
  /**
31
29
  * Get installed WogiFlow version from package.json (canonical source)
32
30
  * @returns {string} Version string or 'unknown'
33
31
  */
34
32
  function getInstalledVersion() {
35
- const pkgPath = path.join(PROJECT_ROOT, 'node_modules', 'wogiflow', 'package.json');
33
+ const pkgPath = path.join(PATHS.root, 'node_modules', 'wogiflow', 'package.json');
36
34
  const pkg = readJson(pkgPath, null);
37
35
  if (pkg) {
38
36
  return pkg.version || 'unknown';
39
37
  }
40
38
 
41
39
  // Fallback: try settings.json (legacy)
42
- const settingsPath = path.join(PROJECT_ROOT, '.claude', 'settings.json');
40
+ const settingsPath = path.join(PATHS.root, '.claude', 'settings.json');
43
41
  const settings = readJson(settingsPath, null);
44
42
  if (settings) {
45
43
  return settings._wogiFlowVersion || 'unknown';
@@ -113,7 +111,7 @@ function installForTarget(targetName) {
113
111
  headers: config.httpHeaders || {},
114
112
  allowedEnvVars: config.httpAllowedEnvVars || []
115
113
  } : undefined;
116
- const hooksConfig = adapter.generateConfig(config.rules, PROJECT_ROOT, transportConfig);
114
+ const hooksConfig = adapter.generateConfig(config.rules, PATHS.root, transportConfig);
117
115
 
118
116
  // For Claude Code, we need to merge into settings.local.json
119
117
  if (targetName === 'claude-code') {
@@ -138,15 +136,7 @@ function installClaudeCodeHooks(adapter, hooksConfig) {
138
136
  }
139
137
 
140
138
  // Read existing config
141
- let existingConfig = {};
142
- if (fs.existsSync(configPath)) {
143
- try {
144
- const content = fs.readFileSync(configPath, 'utf-8');
145
- existingConfig = JSON.parse(content);
146
- } catch (err) {
147
- warn(` Could not parse existing config, will create new`);
148
- }
149
- }
139
+ let existingConfig = safeJsonParse(configPath, {});
150
140
 
151
141
  // Check if we're overwriting non-Wogi hooks
152
142
  if (existingConfig.hooks && !existingConfig._wogiFlowManaged) {
@@ -166,7 +156,7 @@ function installClaudeCodeHooks(adapter, hooksConfig) {
166
156
 
167
157
  // Write config
168
158
  fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2));
169
- success(`Hooks written to ${path.relative(PROJECT_ROOT, configPath)}`);
159
+ success(`Hooks written to ${path.relative(PATHS.root, configPath)}`);
170
160
 
171
161
  return true;
172
162
  }
@@ -250,8 +240,12 @@ function removeClaudeCodeHooks(adapter) {
250
240
  }
251
241
 
252
242
  try {
253
- const content = fs.readFileSync(configPath, 'utf-8');
254
- const config = JSON.parse(content);
243
+ const config = safeJsonParse(configPath, null);
244
+
245
+ if (!config) {
246
+ warn(` Config file is corrupt or unreadable (skipping)`);
247
+ return false;
248
+ }
255
249
 
256
250
  if (!config._wogiFlowManaged) {
257
251
  warn(` Config not managed by Wogi Flow (skipping)`);
@@ -275,8 +269,7 @@ function removeClaudeCodeHooks(adapter) {
275
269
  // Restore backup if exists
276
270
  const backupPath = configPath + '.backup';
277
271
  if (fs.existsSync(backupPath)) {
278
- const backupContent = fs.readFileSync(backupPath, 'utf-8');
279
- const backupConfig = JSON.parse(backupContent);
272
+ const backupConfig = safeJsonParse(backupPath, {});
280
273
  if (backupConfig.hooks) {
281
274
  const finalConfig = { ...config, hooks: backupConfig.hooks };
282
275
  fs.writeFileSync(configPath, JSON.stringify(finalConfig, null, 2));
@@ -375,13 +368,8 @@ function checkIfInstalled(adapter) {
375
368
  if (!fs.existsSync(configPath)) {
376
369
  return false;
377
370
  }
378
- try {
379
- const content = fs.readFileSync(configPath, 'utf-8');
380
- const config = JSON.parse(content);
381
- return config._wogiFlowManaged === true;
382
- } catch (_err) {
383
- return false;
384
- }
371
+ const config = safeJsonParse(configPath, {});
372
+ return config._wogiFlowManaged === true;
385
373
  }
386
374
  return false;
387
375
  }
@@ -434,7 +422,7 @@ async function testHook(hookName) {
434
422
 
435
423
  const { spawn } = require('node:child_process');
436
424
  const proc = spawn('node', [hookPath], {
437
- cwd: PROJECT_ROOT,
425
+ cwd: PATHS.root,
438
426
  stdio: ['pipe', 'pipe', 'pipe']
439
427
  });
440
428
 
@@ -22,9 +22,9 @@ const { TIMEOUTS, LIMITS, BACKOFF } = require('./flow-constants');
22
22
  class HttpClient {
23
23
  constructor(baseUrl, options = {}) {
24
24
  this.baseUrl = baseUrl;
25
- this.defaultHeaders = options.headers || {};
26
- this.timeout = options.timeout || TIMEOUTS.HTTP_DEFAULT;
27
- this.maxRetries = options.maxRetries || LIMITS.HTTP_MAX_RETRIES;
25
+ this.defaultHeaders = options.headers ?? {};
26
+ this.timeout = options.timeout ?? TIMEOUTS.HTTP_DEFAULT;
27
+ this.maxRetries = options.maxRetries ?? LIMITS.HTTP_MAX_RETRIES;
28
28
  }
29
29
 
30
30
  /**
@@ -55,10 +55,10 @@ class HttpClient {
55
55
  port: url.port || (isHttps ? 443 : 80),
56
56
  path: url.pathname + url.search,
57
57
  headers,
58
- timeout: options.timeout || this.timeout,
58
+ timeout: options.timeout ?? this.timeout,
59
59
  };
60
60
 
61
- return this._executeWithRetry(lib, requestOptions, body, options.retries || 0);
61
+ return this._executeWithRetry(lib, requestOptions, body, options.retries ?? 0);
62
62
  }
63
63
 
64
64
  /**
@@ -147,7 +147,8 @@ class HttpClient {
147
147
  * Sleep helper
148
148
  */
149
149
  _sleep(ms) {
150
- return new Promise(resolve => setTimeout(resolve, ms));
150
+ const { setTimeout: sleep } = require('node:timers/promises');
151
+ return sleep(ms);
151
152
  }
152
153
 
153
154
  // Convenience methods
@@ -222,7 +223,7 @@ class HttpClient {
222
223
  * Simple fetch JSON helper (for one-off requests)
223
224
  */
224
225
  async function fetchJson(url, options = {}) {
225
- const client = new HttpClient(url, { timeout: options.timeout || TIMEOUTS.HTTP_DEFAULT });
226
+ const client = new HttpClient(url, { timeout: options.timeout ?? TIMEOUTS.HTTP_DEFAULT });
226
227
  const parsedUrl = new URL(url);
227
228
  const response = await client.get(parsedUrl.pathname + parsedUrl.search, {
228
229
  headers: options.headers,
@@ -234,7 +235,7 @@ async function fetchJson(url, options = {}) {
234
235
  * Simple post JSON helper (for one-off requests)
235
236
  */
236
237
  async function postJson(url, body, options = {}) {
237
- const client = new HttpClient(url, { timeout: options.timeout || TIMEOUTS.HTTP_DEFAULT });
238
+ const client = new HttpClient(url, { timeout: options.timeout ?? TIMEOUTS.HTTP_DEFAULT });
238
239
  const parsedUrl = new URL(url);
239
240
  const response = await client.post(parsedUrl.pathname + parsedUrl.search, body, {
240
241
  headers: options.headers,
@@ -10,11 +10,11 @@
10
10
 
11
11
  const fs = require('node:fs');
12
12
  const path = require('node:path');
13
- const readline = require('node:readline');
13
+ const readline = require('node:readline/promises');
14
14
  const http = require('node:http');
15
15
  const { HttpClient } = require('./flow-http-client');
16
16
  const { URL, URLSearchParams } = require('node:url');
17
- const { getProjectRoot, colors, safeJsonParse } = require('./flow-utils');
17
+ const { getProjectRoot, colors, safeJsonParse, PATHS } = require('./flow-utils');
18
18
  const { error: errorMsg } = require('./flow-output');
19
19
 
20
20
  // Import model registry for smart model selection
@@ -26,9 +26,7 @@ try {
26
26
  // Registry not available, will use hardcoded models
27
27
  }
28
28
 
29
- const PROJECT_ROOT = getProjectRoot();
30
- const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
31
- const CONFIG_PATH = path.join(WORKFLOW_DIR, 'config.json');
29
+ const CONFIG_PATH = path.join(PATHS.workflow, 'config.json');
32
30
 
33
31
  const symbols = {
34
32
  success: '✅',
@@ -96,12 +94,9 @@ async function prompt(question) {
96
94
  output: process.stdout
97
95
  });
98
96
 
99
- return new Promise(resolve => {
100
- rl.question(question, answer => {
101
- rl.close();
102
- resolve(answer.trim());
103
- });
104
- });
97
+ const answer = await rl.question(question);
98
+ rl.close();
99
+ return answer.trim();
105
100
  }
106
101
 
107
102
  class Spinner {
@@ -700,7 +695,7 @@ ${colors.cyan}╔═════════════════════
700
695
  `);
701
696
 
702
697
  // Check if workflow dir exists
703
- if (!fs.existsSync(WORKFLOW_DIR)) {
698
+ if (!fs.existsSync(PATHS.workflow)) {
704
699
  console.log(`${colors.red}${symbols.error} Wogi Flow not installed in this project.${colors.reset}`);
705
700
  console.log(`Run /wogi-onboard first.`);
706
701
  process.exit(1);
@@ -9,9 +9,8 @@
9
9
  const fs = require('node:fs');
10
10
  const path = require('node:path');
11
11
  const { spawnSync } = require('node:child_process');
12
- const { getProjectRoot, getConfig, error, success } = require('./flow-utils');
12
+ const { getProjectRoot, getConfig, error, success, PATHS } = require('./flow-utils');
13
13
 
14
- const PROJECT_ROOT = getProjectRoot();
15
14
  const TESTS = [];
16
15
  let passed = 0;
17
16
  let failed = 0;
@@ -45,7 +44,7 @@ async function run() {
45
44
  // Tests
46
45
 
47
46
  test('Config file exists', () => {
48
- const configPath = path.join(PROJECT_ROOT, '.workflow', 'config.json');
47
+ const configPath = path.join(PATHS.workflow, 'config.json');
49
48
  if (!fs.existsSync(configPath)) {
50
49
  throw new Error('config.json not found');
51
50
  }
@@ -59,7 +58,7 @@ test('Config has hybrid section', () => {
59
58
  });
60
59
 
61
60
  test('Detection script exists and runs', () => {
62
- const scriptPath = path.join(PROJECT_ROOT, 'scripts', 'flow-hybrid-detect.js');
61
+ const scriptPath = path.join(PATHS.root, 'scripts', 'flow-hybrid-detect.js');
63
62
  if (!fs.existsSync(scriptPath)) {
64
63
  throw new Error('flow-hybrid-detect.js not found');
65
64
  }
@@ -77,56 +76,56 @@ test('Detection script exists and runs', () => {
77
76
  });
78
77
 
79
78
  test('Orchestrator script exists', () => {
80
- const scriptPath = path.join(PROJECT_ROOT, 'scripts', 'flow-orchestrate.js');
79
+ const scriptPath = path.join(PATHS.root, 'scripts', 'flow-orchestrate.js');
81
80
  if (!fs.existsSync(scriptPath)) {
82
81
  throw new Error('flow-orchestrate.js not found');
83
82
  }
84
83
  });
85
84
 
86
85
  test('Templates directory exists', () => {
87
- const templatesDir = path.join(PROJECT_ROOT, 'templates', 'hybrid');
86
+ const templatesDir = path.join(PATHS.root, 'templates', 'hybrid');
88
87
  if (!fs.existsSync(templatesDir)) {
89
88
  throw new Error('templates/hybrid directory not found');
90
89
  }
91
90
  });
92
91
 
93
92
  test('Base template exists', () => {
94
- const basePath = path.join(PROJECT_ROOT, 'templates', 'hybrid', '_base.md');
93
+ const basePath = path.join(PATHS.root, 'templates', 'hybrid', '_base.md');
95
94
  if (!fs.existsSync(basePath)) {
96
95
  throw new Error('_base.md template not found');
97
96
  }
98
97
  });
99
98
 
100
99
  test('Component template exists', () => {
101
- const templatePath = path.join(PROJECT_ROOT, 'templates', 'hybrid', 'create-component.md');
100
+ const templatePath = path.join(PATHS.root, 'templates', 'hybrid', 'create-component.md');
102
101
  if (!fs.existsSync(templatePath)) {
103
102
  throw new Error('create-component.md template not found');
104
103
  }
105
104
  });
106
105
 
107
106
  test('State directory exists', () => {
108
- const stateDir = path.join(PROJECT_ROOT, '.workflow', 'state');
107
+ const stateDir = path.join(PATHS.workflow, 'state');
109
108
  if (!fs.existsSync(stateDir)) {
110
109
  throw new Error('.workflow/state directory not found');
111
110
  }
112
111
  });
113
112
 
114
113
  test('Progress module exists', () => {
115
- const scriptPath = path.join(PROJECT_ROOT, 'scripts', 'flow-progress.js');
114
+ const scriptPath = path.join(PATHS.root, 'scripts', 'flow-progress.js');
116
115
  if (!fs.existsSync(scriptPath)) {
117
116
  throw new Error('flow-progress.js not found');
118
117
  }
119
118
  });
120
119
 
121
120
  test('Templates module exists', () => {
122
- const scriptPath = path.join(PROJECT_ROOT, 'scripts', 'flow-templates.js');
121
+ const scriptPath = path.join(PATHS.root, 'scripts', 'flow-templates.js');
123
122
  if (!fs.existsSync(scriptPath)) {
124
123
  throw new Error('flow-templates.js not found');
125
124
  }
126
125
  });
127
126
 
128
127
  test('Interactive setup exists', () => {
129
- const scriptPath = path.join(PROJECT_ROOT, 'scripts', 'flow-hybrid-interactive.js');
128
+ const scriptPath = path.join(PATHS.root, 'scripts', 'flow-hybrid-interactive.js');
130
129
  if (!fs.existsSync(scriptPath)) {
131
130
  throw new Error('flow-hybrid-interactive.js not found');
132
131
  }
@@ -140,7 +139,7 @@ test('Slash command files exist', () => {
140
139
  ];
141
140
 
142
141
  for (const cmd of commands) {
143
- const cmdPath = path.join(PROJECT_ROOT, '.claude', 'commands', cmd);
142
+ const cmdPath = path.join(PATHS.root, '.claude', 'commands', cmd);
144
143
  if (!fs.existsSync(cmdPath)) {
145
144
  throw new Error(`${cmd} not found`);
146
145
  }
@@ -427,7 +427,7 @@ function loadRelevantTypes(projectRoot, filePath, options = {}) {
427
427
  * @returns {Promise<string|null>} Formatted type information
428
428
  */
429
429
  async function loadRelevantTypesWithLSP(projectRoot, filePath, options = {}) {
430
- const { getConfig } = require('./flow-utils');
430
+ const { getConfig, PATHS } = require('./flow-utils');
431
431
  const config = getConfig();
432
432
 
433
433
  // Check if LSP is enabled
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  /**
4
2
  * Wogi Flow - File I/O Operations
5
3
  *
@@ -510,7 +508,7 @@ async function acquireLock(filePath, options = {}) {
510
508
  if (process.env.DEBUG) {
511
509
  console.warn(`[DEBUG] Stale lock cleanup failed: ${err.message}`);
512
510
  }
513
- await new Promise(resolve => setTimeout(resolve, retryDelay));
511
+ await require('node:timers/promises').setTimeout(retryDelay);
514
512
  }
515
513
  // Try again
516
514
  continue;
@@ -521,7 +519,7 @@ async function acquireLock(filePath, options = {}) {
521
519
  const delay = exponentialBackoff
522
520
  ? retryDelay * Math.pow(2, attempt)
523
521
  : retryDelay * (attempt + 1);
524
- await new Promise(resolve => setTimeout(resolve, delay));
522
+ await require('node:timers/promises').setTimeout(delay);
525
523
  continue;
526
524
  }
527
525
  }
@@ -646,6 +644,22 @@ function cleanupStaleLocks(dirPath, staleMs = CLEANUP_LOCK_STALE_MS) {
646
644
  }
647
645
  }
648
646
 
647
+ // ============================================================
648
+ // String Sanitization (for AI context injection)
649
+ // ============================================================
650
+
651
+ /**
652
+ * Sanitize a string value before injecting into AI context.
653
+ * Strips markdown heading markers and truncates to prevent prompt manipulation.
654
+ *
655
+ * @param {string} value - Raw string from state files
656
+ * @param {number} [maxLen=200] - Maximum length
657
+ * @returns {string} Sanitized string
658
+ */
659
+ function sanitizeForContext(value, maxLen = 200) {
660
+ return String(value).replace(/^#+\s/gm, '').slice(0, maxLen);
661
+ }
662
+
649
663
  // ============================================================
650
664
  // Exports
651
665
  // ============================================================
@@ -689,4 +703,7 @@ module.exports = {
689
703
  withLock,
690
704
  withLockSync,
691
705
  cleanupStaleLocks,
706
+
707
+ // String Sanitization
708
+ sanitizeForContext,
692
709
  };
@@ -303,10 +303,16 @@ ${correction}
303
303
  `;
304
304
 
305
305
  content += entry;
306
- fs.writeFileSync(decisionsPath, content);
307
306
 
308
- // Sync to .claude/rules/ for Claude Code integration
309
- syncDecisionsToRules();
307
+ // Route through orchestrator for locking and dedup
308
+ try {
309
+ const { writeToDecisions } = require('./flow-learning-orchestrator');
310
+ await writeToDecisions({ content, entryText: correction.slice(0, 100), caller: 'flow-knowledge-router', skipDedup: false, syncRules: true });
311
+ } catch (_err) {
312
+ // Fallback to direct write if orchestrator unavailable
313
+ fs.writeFileSync(decisionsPath, content);
314
+ syncDecisionsToRules();
315
+ }
310
316
 
311
317
  return {
312
318
  success: true,