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
@@ -35,7 +35,6 @@ const {
35
35
  } = require('./flow-utils')
36
36
  const { color, success, warn, error } = require('./flow-output');;
37
37
 
38
- const PROJECT_ROOT = getProjectRoot();
39
38
  const SESSION_FILE = path.join(PATHS.state, 'guided-edit-session.json');
40
39
 
41
40
  // ============================================================
@@ -141,14 +140,14 @@ function findAffectedFiles(search, options = {}) {
141
140
 
142
141
  // Try options first
143
142
  if (options.srcDir) {
144
- srcDir = path.isAbsolute(options.srcDir) ? options.srcDir : path.join(PROJECT_ROOT, options.srcDir);
143
+ srcDir = path.isAbsolute(options.srcDir) ? options.srcDir : path.join(PATHS.root, options.srcDir);
145
144
  }
146
145
 
147
146
  // Try config
148
147
  if (!srcDir || !fs.existsSync(srcDir)) {
149
148
  const configSrcDir = config.guidedEdit?.srcDir;
150
149
  if (configSrcDir) {
151
- const resolved = path.isAbsolute(configSrcDir) ? configSrcDir : path.join(PROJECT_ROOT, configSrcDir);
150
+ const resolved = path.isAbsolute(configSrcDir) ? configSrcDir : path.join(PATHS.root, configSrcDir);
152
151
  if (fs.existsSync(resolved)) {
153
152
  srcDir = resolved;
154
153
  }
@@ -157,8 +156,8 @@ function findAffectedFiles(search, options = {}) {
157
156
 
158
157
  // Fallback to src/ or project root
159
158
  if (!srcDir || !fs.existsSync(srcDir)) {
160
- const defaultSrc = path.join(PROJECT_ROOT, 'src');
161
- srcDir = fs.existsSync(defaultSrc) ? defaultSrc : PROJECT_ROOT;
159
+ const defaultSrc = path.join(PATHS.root, 'src');
160
+ srcDir = fs.existsSync(defaultSrc) ? defaultSrc : PATHS.root;
162
161
  }
163
162
 
164
163
  const extensions = options.extensions || config.guidedEdit?.extensions || ['ts', 'tsx', 'js', 'jsx', 'vue', 'svelte'];
@@ -190,7 +189,7 @@ function findAffectedFiles(search, options = {}) {
190
189
  }
191
190
 
192
191
  results.push({
193
- path: path.relative(PROJECT_ROOT, file),
192
+ path: path.relative(PATHS.root, file),
194
193
  absolutePath: file,
195
194
  matchCount: matches.length,
196
195
  matches: matches.slice(0, 5), // First 5 matches
@@ -267,9 +267,9 @@ function main() {
267
267
  printSection('Checking universal structure...');
268
268
 
269
269
  const universalDirs = [
270
- { path: path.join(PROJECT_ROOT, '.workflow', 'models'), name: '.workflow/models' },
271
- { path: path.join(PROJECT_ROOT, '.workflow', 'bridges'), name: '.workflow/bridges' },
272
- { path: path.join(PROJECT_ROOT, '.workflow', 'templates'), name: '.workflow/templates' },
270
+ { path: PATHS.modelsDir, name: '.workflow/models' },
271
+ { path: PATHS.bridges, name: '.workflow/bridges' },
272
+ { path: PATHS.templates, name: '.workflow/templates' },
273
273
  ];
274
274
 
275
275
  for (const dir of universalDirs) {
@@ -282,7 +282,7 @@ function main() {
282
282
  }
283
283
 
284
284
  // Check model registry
285
- const registryPath = path.join(PROJECT_ROOT, '.workflow', 'models', 'registry.json');
285
+ const registryPath = path.join(PATHS.workflow, 'models', 'registry.json');
286
286
  if (fileExists(registryPath)) {
287
287
  const result = validateJson(registryPath);
288
288
  if (result.valid) {
@@ -376,8 +376,7 @@ function main() {
376
376
 
377
377
  if (fileExists(tsconfigPath)) {
378
378
  try {
379
- const tsconfigContent = fs.readFileSync(tsconfigPath, 'utf-8');
380
- const tsconfig = JSON.parse(tsconfigContent);
379
+ const tsconfig = safeJsonParse(tsconfigPath, {});
381
380
  const hasProjectRefs = Array.isArray(tsconfig.references) && tsconfig.references.length > 0;
382
381
  const hasEmptyFiles = Array.isArray(tsconfig.files) && tsconfig.files.length === 0;
383
382
  const isProjectRefsMode = hasProjectRefs && hasEmptyFiles;
@@ -153,6 +153,12 @@ function formatHookError(hookName, err, options = {}) {
153
153
  */
154
154
  function logHookError(hookName, err, options = {}) {
155
155
  const formatted = formatHookError(hookName, err, options);
156
+ // Debug env var hierarchy:
157
+ // DEBUG — General-purpose debug flag (used across ~30 scripts for verbose logging)
158
+ // WOGIFLOW_DEBUG — WogiFlow-specific debug flag (equivalent to DEBUG but namespaced)
159
+ // DEBUG_LSP — LSP-only debug flag (used exclusively in flow-lsp.js for LSP protocol debugging)
160
+ // DEBUG and WOGIFLOW_DEBUG are interchangeable; either enables verbose error output.
161
+ // DEBUG_LSP is independent — it controls only LSP stderr/parse-error logging.
156
162
  const isDebug = process.env.DEBUG || process.env.WOGIFLOW_DEBUG;
157
163
 
158
164
  if (isDebug) {
@@ -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