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
@@ -22,15 +22,12 @@
22
22
  const fs = require('node:fs');
23
23
  const path = require('node:path');
24
24
  const { spawn, execSync } = require('node:child_process');
25
- const { getProjectRoot, getConfig, colors: c, readJson } = require('./flow-utils');
25
+ const { getProjectRoot, getConfig, colors: c, readJson, PATHS } = require('./flow-utils');
26
26
  const { success: printSuccess, error: printError } = require('./flow-output');
27
27
  const { recordCommandResult } = require('./flow-metrics');
28
28
  const { detectPackageManager, getExecCommand, getRunPrefix } = require('./flow-script-resolver');
29
29
  const { CREDENTIAL_SCAN_PATTERNS } = require('./flow-security');
30
30
 
31
- const PROJECT_ROOT = getProjectRoot();
32
- const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
33
-
34
31
  /**
35
32
  * Gate result structure
36
33
  */
@@ -197,7 +194,7 @@ const ERROR_PARSERS = {
197
194
  if (match) {
198
195
  const [, file, lineNum, col, message, rule] = match;
199
196
  errors.push({
200
- file: path.relative(PROJECT_ROOT, file),
197
+ file: path.relative(PATHS.root, file),
201
198
  line: parseInt(lineNum),
202
199
  column: parseInt(col),
203
200
  message: message.trim(),
@@ -220,7 +217,7 @@ const ERROR_PARSERS = {
220
217
  if (match) {
221
218
  const [, file, lineNum, col, severity, code, message] = match;
222
219
  errors.push({
223
- file: path.relative(PROJECT_ROOT, file),
220
+ file: path.relative(PATHS.root, file),
224
221
  line: parseInt(lineNum),
225
222
  column: parseInt(col),
226
223
  message,
@@ -250,7 +247,7 @@ const ERROR_PARSERS = {
250
247
  const [, file, line, col] = match;
251
248
  if (!file.includes('node_modules')) {
252
249
  errors.push({
253
- file: path.relative(PROJECT_ROOT, file),
250
+ file: path.relative(PATHS.root, file),
254
251
  line: parseInt(line),
255
252
  column: parseInt(col),
256
253
  message: 'Test assertion failed',
@@ -363,12 +360,11 @@ const ERROR_PARSERS = {
363
360
  }
364
361
  };
365
362
 
366
-
367
363
  /**
368
364
  * Detect which command to use based on installed packages
369
365
  */
370
366
  function detectCommand(gate) {
371
- const packageJsonPath = path.join(PROJECT_ROOT, 'package.json');
367
+ const packageJsonPath = path.join(PATHS.root, 'package.json');
372
368
  let deps = {};
373
369
 
374
370
  if (fs.existsSync(packageJsonPath)) {
@@ -405,7 +401,7 @@ function runCommand(cmd, args, timeout = 120000) {
405
401
  let stderrTruncated = false;
406
402
 
407
403
  const proc = spawn(cmd, args, {
408
- cwd: PROJECT_ROOT,
404
+ cwd: PATHS.root,
409
405
  timeout,
410
406
  stdio: ['pipe', 'pipe', 'pipe']
411
407
  });
@@ -687,7 +683,7 @@ function generateFixSuggestions(gateName, errors) {
687
683
  function getStagedFiles() {
688
684
  try {
689
685
  const output = execSync('git diff --cached --name-only', {
690
- cwd: PROJECT_ROOT,
686
+ cwd: PATHS.root,
691
687
  encoding: 'utf-8'
692
688
  });
693
689
  return output.split('\n').filter(f => f.trim() && /\.(ts|tsx|js|jsx|json|env)$/i.test(f));
@@ -720,7 +716,7 @@ function findSourceFiles(dir, extensions, limit) {
720
716
  if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
721
717
 
722
718
  const fullPath = path.join(currentDir, entry.name);
723
- const relativePath = path.relative(PROJECT_ROOT, fullPath);
719
+ const relativePath = path.relative(PATHS.root, fullPath);
724
720
 
725
721
  if (entry.isDirectory()) {
726
722
  walk(fullPath, depth + 1);
@@ -756,7 +752,7 @@ function checkForSecrets(files) {
756
752
  });
757
753
  if (shouldIgnore) continue;
758
754
 
759
- const filePath = path.join(PROJECT_ROOT, file);
755
+ const filePath = path.join(PATHS.root, file);
760
756
  if (!fs.existsSync(filePath)) continue;
761
757
 
762
758
  try {
@@ -809,7 +805,7 @@ function checkForInjection(files) {
809
805
  });
810
806
  if (shouldIgnore) continue;
811
807
 
812
- const filePath = path.join(PROJECT_ROOT, file);
808
+ const filePath = path.join(PATHS.root, file);
813
809
  if (!fs.existsSync(filePath)) continue;
814
810
 
815
811
  try {
@@ -853,7 +849,7 @@ async function runSecurityChecks(gateResult) {
853
849
  // Fall back to src directory if no staged files
854
850
  // Use fs.readdirSync instead of shell find to prevent command injection
855
851
  try {
856
- const srcDir = path.join(PROJECT_ROOT, 'src');
852
+ const srcDir = path.join(PATHS.root, 'src');
857
853
  if (fs.existsSync(srcDir)) {
858
854
  files = findSourceFiles(srcDir, ['.ts', '.tsx', '.js', '.jsx'], 100);
859
855
  }
@@ -1075,7 +1071,7 @@ function formatResults(results, options = {}) {
1075
1071
  * Save results to run artifacts
1076
1072
  */
1077
1073
  function saveResults(runId, results) {
1078
- const runDir = path.join(WORKFLOW_DIR, 'runs', runId);
1074
+ const runDir = path.join(PATHS.workflow, 'runs', runId);
1079
1075
  if (!fs.existsSync(runDir)) {
1080
1076
  return false;
1081
1077
  }
@@ -16,7 +16,7 @@
16
16
  const fs = require('node:fs');
17
17
  const path = require('node:path');
18
18
  const { execSync } = require('node:child_process');
19
- const { PATHS, meetsVersion, readJson } = require('./flow-utils');
19
+ const { PATHS, meetsVersion, readJson, safeJsonParse } = require('./flow-utils');
20
20
 
21
21
  const VERSION_CHECK_PATH = path.join(PATHS.state, '.version-check.json');
22
22
 
@@ -46,12 +46,7 @@ function getClaudeCodeVersion() {
46
46
  * @returns {{ version: string, checkedAt: string, wogiflowVersion: string } | null}
47
47
  */
48
48
  function readLastCheck() {
49
- try {
50
- const content = fs.readFileSync(VERSION_CHECK_PATH, 'utf-8');
51
- return JSON.parse(content);
52
- } catch (err) {
53
- return null;
54
- }
49
+ return safeJsonParse(VERSION_CHECK_PATH, null);
55
50
  }
56
51
 
57
52
  /**
@@ -195,9 +190,8 @@ function checkWogiFlowUpdateOnce() {
195
190
  if (localVersion === 'unknown') return null;
196
191
 
197
192
  // Check cached result
198
- try {
199
- const cached = fs.readFileSync(UPDATE_CHECK_PATH, 'utf-8');
200
- const data = JSON.parse(cached);
193
+ const data = safeJsonParse(UPDATE_CHECK_PATH, null);
194
+ if (data) {
201
195
  const age = Date.now() - new Date(data.checkedAt).getTime();
202
196
  if (age < UPDATE_CHECK_TTL_MS && data.localVersion === localVersion) {
203
197
  // Still within TTL and same local version — return cached result
@@ -206,8 +200,6 @@ function checkWogiFlowUpdateOnce() {
206
200
  }
207
201
  return null;
208
202
  }
209
- } catch (err) {
210
- // No cache or invalid — proceed with fresh check
211
203
  }
212
204
 
213
205
  // Fetch from npm
@@ -16,15 +16,13 @@
16
16
  const fs = require('node:fs');
17
17
  const path = require('node:path');
18
18
  const crypto = require('node:crypto');
19
- const { getProjectRoot, getConfig, color, success, warn, error } = require('./flow-utils');
19
+ const { getProjectRoot, getConfig, color, success, warn, error, PATHS } = require('./flow-utils');
20
20
  const { readJson, writeJson, ensureDir, fileExists } = require('./flow-utils');
21
21
  const { BaseScanner, PROJECT_ROOT } = require('./flow-scanner-base');
22
22
 
23
- const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
24
- const STATE_DIR = path.join(WORKFLOW_DIR, 'state');
25
- const WEBMCP_DIR = path.join(WORKFLOW_DIR, 'webmcp');
23
+ const WEBMCP_DIR = path.join(PATHS.workflow, 'webmcp');
26
24
  const TOOLS_PATH = path.join(WEBMCP_DIR, 'tools.json');
27
- const APP_MAP_PATH = path.join(STATE_DIR, 'app-map.md');
25
+ const APP_MAP_PATH = PATHS.appMap;
28
26
 
29
27
  // ============================================================
30
28
  // Configuration
@@ -15,8 +15,6 @@ const fs = require('node:fs');
15
15
  const path = require('node:path');
16
16
  const { getProjectRoot, colors, getConfig, invalidateConfigCache, writeJson, PATHS } = require('./flow-utils');
17
17
 
18
- const PROJECT_ROOT = getProjectRoot();
19
-
20
18
  // ============================================================
21
19
  // Step Registry - All available workflow steps
22
20
  // ============================================================
@@ -24,18 +24,16 @@
24
24
  const fs = require('node:fs');
25
25
  const path = require('node:path');
26
26
  const { spawn } = require('node:child_process');
27
- const { getProjectRoot, colors: c, readJson } = require('./flow-utils');
27
+ const { getProjectRoot, colors: c, readJson, PATHS } = require('./flow-utils');
28
28
  const { success: printSuccess, error: printError } = require('./flow-output');
29
29
  const { detectPackageManager } = require('./flow-script-resolver');
30
30
 
31
- const PROJECT_ROOT = getProjectRoot();
32
- const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
33
- const WORKFLOWS_DIR = path.join(WORKFLOW_DIR, 'workflows');
31
+ const WORKFLOWS_DIR = path.join(PATHS.workflow, 'workflows');
34
32
 
35
33
  /**
36
34
  * Validate that a path is within the project root (prevent path traversal)
37
35
  */
38
- function validatePathWithinProject(targetPath, baseRoot = PROJECT_ROOT) {
36
+ function validatePathWithinProject(targetPath, baseRoot = PATHS.root) {
39
37
  const resolvedPath = path.resolve(baseRoot, targetPath);
40
38
  const resolvedRoot = path.resolve(baseRoot);
41
39
 
@@ -62,11 +60,11 @@ const STEP_TYPES = {
62
60
  * Detect project type (language, package manager)
63
61
  * Returns { language, packageManager } with defaults to Node.js/npm
64
62
  */
65
- function detectProjectType(projectRoot = PROJECT_ROOT) {
63
+ function detectProjectType(projectRoot = PATHS.root) {
66
64
  // Validate projectRoot to prevent path traversal
67
- const safeRoot = projectRoot === PROJECT_ROOT
68
- ? PROJECT_ROOT
69
- : validatePathWithinProject(projectRoot, PROJECT_ROOT);
65
+ const safeRoot = projectRoot === PATHS.root
66
+ ? PATHS.root
67
+ : validatePathWithinProject(projectRoot, PATHS.root);
70
68
 
71
69
  // Check for Go
72
70
  if (fs.existsSync(path.join(safeRoot, 'go.mod'))) {
@@ -95,7 +93,7 @@ function detectProjectType(projectRoot = PROJECT_ROOT) {
95
93
  * Get quality gate command for an action (lint, test, build)
96
94
  * Adapts to detected package manager and language
97
95
  */
98
- function getQualityCommand(action, projectRoot = PROJECT_ROOT) {
96
+ function getQualityCommand(action, projectRoot = PATHS.root) {
99
97
  const { language, packageManager } = detectProjectType(projectRoot);
100
98
 
101
99
  const commands = {
@@ -312,7 +310,7 @@ function executeCommand(command, options = {}) {
312
310
  const startTime = Date.now();
313
311
 
314
312
  const proc = spawn('sh', ['-c', command], {
315
- cwd: options.cwd || PROJECT_ROOT,
313
+ cwd: options.cwd || PATHS.root,
316
314
  env: { ...process.env, ...options.env },
317
315
  timeout: options.timeout || 60000
318
316
  });
@@ -67,6 +67,8 @@ const CLAUDE_CODE_EVENTS = [
67
67
  // 'Notification', // Supported but not yet used by WogiFlow
68
68
  // 'Elicitation', // Claude Code 2.1.76+ — intercept MCP elicitation requests before dialog
69
69
  // 'ElicitationResult', // Claude Code 2.1.76+ — intercept/override elicitation responses before sending
70
+ // 'CwdChanged', // Claude Code 2.1.83+ — fires when working directory changes (e.g., direnv)
71
+ // 'FileChanged', // Claude Code 2.1.83+ — fires when watched files change on disk
70
72
  // ];
71
73
 
72
74
  /**
@@ -15,8 +15,10 @@
15
15
  * v1.0: Initial implementation — pre-commit blocking gate
16
16
  */
17
17
 
18
+ const fs = require('node:fs');
19
+ const path = require('node:path');
18
20
  const { execFileSync } = require('node:child_process');
19
- const { getConfig, getReadyData } = require('../../flow-utils');
21
+ const { getConfig, getReadyData, PATHS } = require('../../flow-utils');
20
22
 
21
23
  /**
22
24
  * Check if a Bash command contains a git commit
@@ -120,25 +122,34 @@ function checkCommitLogGate(command, config) {
120
122
  return { allowed: true, blocked: false };
121
123
  }
122
124
 
123
- // Check if request-log.md is in staged changes
124
- const hasLogEntry = stagedFiles.some(f => f.endsWith('request-log.md'));
125
+ // Check if request-log.md contains an entry for the active task.
126
+ // We check file content instead of git staging because .workflow/ may be
127
+ // in .gitignore (intentional user choice to keep state files out of git).
128
+ // Checking staged files would silently fail when the directory is gitignored.
129
+ const task = readyData.inProgress[0];
130
+ const taskId = (typeof task === 'string' ? task : task.id) || 'unknown';
131
+
132
+ const logPath = path.join(PATHS.state, 'request-log.md');
133
+ let hasLogEntry = false;
134
+ try {
135
+ const logContent = fs.readFileSync(logPath, 'utf-8');
136
+ hasLogEntry = logContent.includes(taskId);
137
+ } catch (_err) {
138
+ // File doesn't exist or can't be read — no log entry
139
+ }
140
+
125
141
  if (hasLogEntry) {
126
142
  return { allowed: true, blocked: false };
127
143
  }
128
144
 
129
- // Block: active task + code changes but no log entry
130
- const task = readyData.inProgress[0];
131
- const taskId = (typeof task === 'string' ? task : task.id) || 'unknown';
132
-
133
145
  return {
134
146
  allowed: false,
135
147
  blocked: true,
136
148
  reason: 'commit_without_log_entry',
137
149
  message: [
138
- `BLOCKED: Active task ${taskId} but request-log.md is not staged.`,
150
+ `BLOCKED: Active task ${taskId} but no request-log entry found.`,
139
151
  'Add a request-log entry before committing.',
140
- 'Append a ### R-[N] entry to .workflow/state/request-log.md following the existing format,',
141
- 'then stage it: git add .workflow/state/request-log.md'
152
+ `Append a ### R-[N] entry referencing ${taskId} to .workflow/state/request-log.md following the existing format.`
142
153
  ].join(' ')
143
154
  };
144
155
  }
@@ -65,6 +65,7 @@ function handleConfigChange(options = {}) {
65
65
  let bridgeState = null;
66
66
  try {
67
67
  bridgeState = require('../../flow-bridge-state');
68
+ const { PATHS } = require('../../flow-utils');
68
69
  } catch (_err) {
69
70
  // Bridge state module unavailable
70
71
  }
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  /**
4
2
  * Extension Hook Registry
5
3
  *
@@ -14,7 +14,7 @@
14
14
  const fs = require('node:fs');
15
15
  const path = require('node:path');
16
16
 
17
- const { safeJsonParse } = require('../../flow-utils');
17
+ const { safeJsonParse, PATHS } = require('../../flow-utils');
18
18
 
19
19
  /**
20
20
  * Check if new packages have been added since last scan.
@@ -266,6 +266,11 @@ async function captureObservation(options) {
266
266
  return { skipped: true, reason: 'missing_tool_name' };
267
267
  }
268
268
 
269
+ // Fast path: skip everything if no memory DB file exists (avoids getConfig() + WASM load)
270
+ if (!fs.existsSync(MEMORY_DB_PATH)) {
271
+ return { skipped: true, reason: 'no_memory_db' };
272
+ }
273
+
269
274
  try {
270
275
  // Load settings ONCE and use directly (avoids 4x redundant getConfig() calls)
271
276
  const settings = getObservationSettings();
@@ -327,11 +332,6 @@ async function captureObservation(options) {
327
332
  fullOutput = '[serialization failed]';
328
333
  }
329
334
 
330
- // Fast path: skip DB init if no database file exists yet (avoids 200-800ms WASM load)
331
- if (!fs.existsSync(MEMORY_DB_PATH)) {
332
- return { skipped: true, reason: 'no_memory_db' };
333
- }
334
-
335
335
  // Store observation
336
336
  const db = getMemoryDb();
337
337
  const result = await db.storeObservation({
@@ -119,6 +119,11 @@ function writePhaseState(state) {
119
119
  fs.mkdirSync(dir, { recursive: true });
120
120
  }
121
121
  fs.writeFileSync(PHASE_FILE, JSON.stringify(state, null, 2) + '\n', 'utf-8');
122
+ // Update aggregated hook status
123
+ try {
124
+ const { setPhase } = require('../../flow-hook-status');
125
+ setPhase(state.phase);
126
+ } catch (_err) { /* non-blocking */ }
122
127
  return true;
123
128
  } catch (err) {
124
129
  if (process.env.DEBUG) {
@@ -22,18 +22,7 @@
22
22
  const path = require('node:path');
23
23
  const fs = require('node:fs');
24
24
  const { PATHS, safeJsonParse, getReadyData } = require('../../flow-utils');
25
-
26
- /**
27
- * Sanitize a string value before injecting into AI context.
28
- * Strips markdown heading markers and truncates to prevent prompt manipulation.
29
- *
30
- * @param {string} value - Raw string from state files
31
- * @param {number} [maxLen=200] - Maximum length
32
- * @returns {string} Sanitized string
33
- */
34
- function sanitize(value, maxLen = 200) {
35
- return String(value).replace(/^#+\s/gm, '').slice(0, maxLen);
36
- }
25
+ const { sanitizeForContext: sanitize } = require('../../flow-io');
37
26
 
38
27
  /**
39
28
  * Handle PostCompact event.
@@ -11,7 +11,7 @@
11
11
 
12
12
  const fs = require('node:fs');
13
13
  const path = require('node:path');
14
- const { PATHS } = require('../../flow-utils');
14
+ const { PATHS, safeJsonParse } = require('../../flow-utils');
15
15
  const {
16
16
  checkResearchGate,
17
17
  isResearchEnabled: _isResearchEnabled,
@@ -423,17 +423,7 @@ function getCachePath() {
423
423
  */
424
424
  function readCache() {
425
425
  const cachePath = getCachePath();
426
- try {
427
- if (fs.existsSync(cachePath)) {
428
- const raw = fs.readFileSync(cachePath, 'utf-8');
429
- return JSON.parse(raw);
430
- }
431
- } catch (err) {
432
- if (process.env.DEBUG) {
433
- console.error(`[Research Cache] Read failed: ${err.message}`);
434
- }
435
- }
436
- return { entries: {}, lastCleanup: null };
426
+ return safeJsonParse(cachePath, { entries: {}, lastCleanup: null });
437
427
  }
438
428
 
439
429
  /**
@@ -205,6 +205,12 @@ function clearRoutingPending() {
205
205
  }
206
206
  }
207
207
 
208
+ // Update aggregated hook status
209
+ try {
210
+ const { setRouting } = require('../../flow-hook-status');
211
+ setRouting({ pending: false, cleared: true, clearedAt: new Date().toISOString() });
212
+ } catch (_err) { /* non-blocking */ }
213
+
208
214
  return { cleared: flagDeleted, reason: flagDeleted ? 'flag_cleared' : 'unlink_error' };
209
215
  }
210
216
 
@@ -20,6 +20,7 @@ const fs = require('node:fs');
20
20
  // Import from parent scripts directory
21
21
  const { getConfig, PATHS, safeJsonParse, writeJson, withLock, validateTaskId, archiveCompletedTasksToLog } = require('../../flow-utils');
22
22
  const { resetPhase, isPhaseGateEnabled } = require('./phase-gate');
23
+ const { clearOnTaskComplete } = require('../../flow-hook-status');
23
24
 
24
25
  /**
25
26
  * Check if task completed handling is enabled
@@ -146,6 +147,17 @@ async function handleTaskCompleted(input) {
146
147
  }
147
148
  }
148
149
 
150
+ // Clear hook status on task completion (single aggregated state file)
151
+ if (result.completed) {
152
+ try {
153
+ clearOnTaskComplete();
154
+ } catch (err) {
155
+ if (process.env.DEBUG) {
156
+ console.error(`[Task Completed] Hook status clear failed: ${err.message}`);
157
+ }
158
+ }
159
+ }
160
+
149
161
  // Clear progress tracker state on task completion
150
162
  if (result.completed) {
151
163
  try {
@@ -296,6 +296,33 @@ function checkTaskGate(options = {}, config) {
296
296
  };
297
297
  }
298
298
 
299
+ // getActiveTask() returned null — check if a task EXISTS in inProgress
300
+ // but was rejected for missing routing proof (routedAt + receipt).
301
+ // This gives the AI an actionable error instead of the misleading "no active task".
302
+ try {
303
+ const readyData = getReadyData();
304
+ if (readyData.inProgress && readyData.inProgress.length > 0) {
305
+ const task = readyData.inProgress[0];
306
+ const taskId = typeof task === 'string' ? task : task.id;
307
+ if (taskId) {
308
+ trackBypassAttempt({
309
+ filePath,
310
+ operation,
311
+ reason: 'task_missing_routing_proof',
312
+ taskId
313
+ });
314
+ return {
315
+ allowed: false,
316
+ blocked: true,
317
+ message: `Task ${taskId} is in inProgress but has no routing proof (missing routedAt and no routing receipt file).\n\nThis usually means the task was inserted into ready.json manually instead of through /wogi-start.\n\nTo fix:\n1. Use /wogi-start ${taskId} to properly route this task\n2. Or remove it from inProgress and start fresh: /wogi-ready`,
318
+ reason: 'task_missing_routing_proof'
319
+ };
320
+ }
321
+ }
322
+ } catch (_err) {
323
+ // Fall through to normal "no active task" path
324
+ }
325
+
299
326
  // No active task - should we block?
300
327
  const shouldBlock = config.enforcement?.taskGating?.blockWithoutTask !== false;
301
328
 
@@ -25,7 +25,7 @@ const CORE_STATE_FILES = ['ready.json', 'decisions.md'];
25
25
 
26
26
  function getEssentialStateFiles() {
27
27
  try {
28
- const { getRegistryMapFiles } = require('../../flow-utils');
28
+ const { getRegistryMapFiles, PATHS } = require('../../flow-utils');
29
29
  return [...CORE_STATE_FILES, ...getRegistryMapFiles()];
30
30
  } catch (_err) {
31
31
  return [...CORE_STATE_FILES, 'app-map.md', 'function-map.md', 'api-map.md'];
@@ -11,33 +11,10 @@
11
11
  */
12
12
 
13
13
  const { handleConfigChange } = require('../../core/config-change');
14
- const { claudeCodeAdapter } = require('../../adapters/claude-code');
15
- const { readHookInput } = require('../shared/read-stdin');
14
+ const { runHook } = require('../shared/hook-runner');
16
15
 
17
- process.stdin.setEncoding('utf8');
18
-
19
- async function main() {
20
- try {
21
- const { input: parsedStdin } = await readHookInput();
22
- const input = parsedStdin || {};
23
-
24
- // Extract the changed file path from the hook input
25
- const filePath = input.file_path || input.filePath || '';
26
- const projectRoot = input.cwd || process.cwd();
27
-
28
- // Handle the config change
29
- const result = handleConfigChange({ filePath, projectRoot });
30
-
31
- // Transform to Claude Code format via adapter (consistent with other hooks)
32
- const output = claudeCodeAdapter.transformResult('ConfigChange', result);
33
-
34
- process.stdout.write(JSON.stringify(output));
35
- process.exit(0);
36
- } catch (err) {
37
- // Never block on config change errors
38
- process.stdout.write(JSON.stringify({ continue: true }));
39
- process.exit(0);
40
- }
41
- }
42
-
43
- main();
16
+ runHook('ConfigChange', async ({ input }) => {
17
+ const filePath = input.file_path || input.filePath || '';
18
+ const projectRoot = input.cwd || process.cwd();
19
+ return handleConfigChange({ filePath, projectRoot });
20
+ }, { failMode: 'silent', useStdoutWrite: true });
@@ -14,34 +14,9 @@
14
14
  */
15
15
 
16
16
  const { handleInstructionsLoaded } = require('../../core/instructions-loaded');
17
- const { claudeCodeAdapter } = require('../../adapters/claude-code');
18
- const { readHookInput } = require('../shared/read-stdin');
17
+ const { runHook } = require('../shared/hook-runner');
19
18
 
20
- process.stdin.setEncoding('utf8');
21
-
22
- async function main() {
23
- try {
24
- const { input: parsedStdin } = await readHookInput();
25
- const input = parsedStdin || {};
26
- const parsedInput = claudeCodeAdapter.parseInput(input);
27
- const projectRoot = parsedInput.cwd || process.cwd();
28
-
29
- // Handle the instructions loaded event
30
- const result = handleInstructionsLoaded({ projectRoot });
31
-
32
- // Transform to Claude Code format via adapter
33
- const output = claudeCodeAdapter.transformResult('InstructionsLoaded', result);
34
-
35
- process.stdout.write(JSON.stringify(output));
36
- process.exit(0);
37
- } catch (err) {
38
- // Never block on errors
39
- if (process.env.DEBUG) {
40
- console.error(`[instructions-loaded] Error: ${err.message}`);
41
- }
42
- process.stdout.write(JSON.stringify({ continue: true }));
43
- process.exit(0);
44
- }
45
- }
46
-
47
- main();
19
+ runHook('InstructionsLoaded', async ({ parsedInput }) => {
20
+ const projectRoot = parsedInput.cwd || process.cwd();
21
+ return handleInstructionsLoaded({ projectRoot });
22
+ }, { failMode: 'silent', useStdoutWrite: true });
@@ -11,35 +11,8 @@
11
11
  */
12
12
 
13
13
  const { handlePostCompact } = require('../../core/post-compact');
14
- const { claudeCodeAdapter } = require('../../adapters/claude-code');
15
- const { readHookInput } = require('../shared/read-stdin');
14
+ const { runHook } = require('../shared/hook-runner');
16
15
 
17
- process.stdin.setEncoding('utf8');
18
-
19
- async function main() {
20
- try {
21
- // Consume stdin (required by hook protocol, even if we don't use the input)
22
- await readHookInput();
23
-
24
- // Handle post-compaction state recovery
25
- const result = handlePostCompact();
26
-
27
- // Transform to Claude Code format via adapter
28
- const output = claudeCodeAdapter.transformResult('PostCompact', result);
29
-
30
- process.stdout.write(JSON.stringify(output));
31
- process.exit(0);
32
- } catch (err) {
33
- // Never block on post-compact errors — fail open
34
- try {
35
- const { logHookError } = require('../../../flow-hook-errors');
36
- logHookError('PostCompact', err, { failMode: 'open', operation: 'post-compaction-recovery' });
37
- } catch (logErr) {
38
- console.error(`[WogiFlow] PostCompact hook error: ${err.message}`);
39
- }
40
- process.stdout.write(JSON.stringify({ continue: true }));
41
- process.exit(0);
42
- }
43
- }
44
-
45
- main();
16
+ runHook('PostCompact', async () => {
17
+ return handlePostCompact();
18
+ }, { failMode: 'warn', useStdoutWrite: true });