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
@@ -111,7 +111,7 @@ function readSpecFile(taskId) {
111
111
  return null;
112
112
  }
113
113
 
114
- const specPath = path.join(PATHS.root, '.workflow', 'specs', `${taskId}.md`);
114
+ const specPath = path.join(PATHS.specs, `${taskId}.md`);
115
115
 
116
116
  // Use try-catch only, no existsSync (prevents TOCTOU race condition)
117
117
  try {
@@ -127,7 +127,7 @@ function readSpecFile(taskId) {
127
127
  }
128
128
 
129
129
  // Also check changes directory
130
- const changesDir = path.join(PATHS.root, '.workflow', 'changes');
130
+ const changesDir = PATHS.changes;
131
131
  try {
132
132
  const entries = fs.readdirSync(changesDir, { withFileTypes: true });
133
133
  for (const entry of entries) {
@@ -499,7 +499,7 @@ if (require.main === module) {
499
499
  process.exit(1);
500
500
  }
501
501
 
502
- const readyPath = path.join(PATHS.state, 'ready.json');
502
+ const readyPath = PATHS.ready;
503
503
  const readyData = safeJsonParse(readyPath, { ready: [], inProgress: [] });
504
504
 
505
505
  const allTasks = [
@@ -546,7 +546,7 @@ if (require.main === module) {
546
546
 
547
547
  const currentPercent = parseFloat(args[2]) / 100;
548
548
 
549
- const readyPath = path.join(PATHS.state, 'ready.json');
549
+ const readyPath = PATHS.ready;
550
550
  const readyData = safeJsonParse(readyPath, { ready: [], inProgress: [] });
551
551
 
552
552
  const allTasks = [
@@ -19,13 +19,11 @@
19
19
 
20
20
  const fs = require('node:fs');
21
21
  const path = require('node:path');
22
- const { getProjectRoot, colors: c, readJson } = require('./flow-utils');
22
+ const { getProjectRoot, colors: c, readJson, PATHS } = require('./flow-utils');
23
23
  const { success: printSuccess } = require('./flow-output');
24
24
  const { detectPackageManager } = require('./flow-script-resolver');
25
25
 
26
- const PROJECT_ROOT = getProjectRoot();
27
- const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
28
- const CONTEXT_DIR = path.join(WORKFLOW_DIR, 'context');
26
+ const CONTEXT_DIR = path.join(PATHS.workflow, 'context');
29
27
  const TEMPLATES_DIR = path.join(__dirname, '..', 'templates', 'context');
30
28
 
31
29
  /**
@@ -53,7 +51,7 @@ function detectStack() {
53
51
  };
54
52
 
55
53
  // Check package.json for Node.js projects
56
- const packageJsonPath = path.join(PROJECT_ROOT, 'package.json');
54
+ const packageJsonPath = path.join(PATHS.root, 'package.json');
57
55
  if (fs.existsSync(packageJsonPath)) {
58
56
  try {
59
57
  const pkg = readJson(packageJsonPath, null);
@@ -196,7 +194,7 @@ function detectStack() {
196
194
  }
197
195
 
198
196
  // Detect package manager — uses canonical detectPackageManager() from flow-script-resolver
199
- const detectedPm = detectPackageManager(PROJECT_ROOT);
197
+ const detectedPm = detectPackageManager(PATHS.root);
200
198
  stack.packageManager = detectedPm;
201
199
  if (detectedPm === 'bun') {
202
200
  stack.runtime = 'Bun';
@@ -205,17 +203,17 @@ function detectStack() {
205
203
  // Check for Python projects
206
204
  // Note: When a non-JS primary language is detected, JS framework fields are cleared
207
205
  // to prevent incoherent reports (e.g., "Python + React" from a polyglot repo)
208
- const requirementsPath = path.join(PROJECT_ROOT, 'requirements.txt');
209
- const pyprojectPath = path.join(PROJECT_ROOT, 'pyproject.toml');
206
+ const requirementsPath = path.join(PATHS.root, 'requirements.txt');
207
+ const pyprojectPath = path.join(PATHS.root, 'pyproject.toml');
210
208
  if (fs.existsSync(requirementsPath) || fs.existsSync(pyprojectPath)) {
211
209
  stack.language = 'Python';
212
210
  stack.runtime = 'Python';
213
211
  stack.frameworks.frontend = '';
214
212
  stack.frameworks.fullStack = '';
215
213
 
216
- if (fs.existsSync(path.join(PROJECT_ROOT, 'poetry.lock'))) {
214
+ if (fs.existsSync(path.join(PATHS.root, 'poetry.lock'))) {
217
215
  stack.packageManager = 'Poetry';
218
- } else if (fs.existsSync(path.join(PROJECT_ROOT, 'Pipfile.lock'))) {
216
+ } else if (fs.existsSync(path.join(PATHS.root, 'Pipfile.lock'))) {
219
217
  stack.packageManager = 'Pipenv';
220
218
  } else {
221
219
  stack.packageManager = 'pip';
@@ -240,7 +238,7 @@ function detectStack() {
240
238
  }
241
239
 
242
240
  // Check for Rust projects
243
- const cargoPath = path.join(PROJECT_ROOT, 'Cargo.toml');
241
+ const cargoPath = path.join(PATHS.root, 'Cargo.toml');
244
242
  if (fs.existsSync(cargoPath)) {
245
243
  stack.language = 'Rust';
246
244
  stack.runtime = 'Rust';
@@ -257,7 +255,7 @@ function detectStack() {
257
255
  }
258
256
 
259
257
  // Check for Go projects
260
- const goModPath = path.join(PROJECT_ROOT, 'go.mod');
258
+ const goModPath = path.join(PATHS.root, 'go.mod');
261
259
  if (fs.existsSync(goModPath)) {
262
260
  stack.language = 'Go';
263
261
  stack.runtime = 'Go';
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  /**
4
2
  * Context Manager Facade
5
3
  *
@@ -36,7 +36,7 @@ const {
36
36
  outputJson,
37
37
  printHeader,
38
38
  printSection,
39
- estimateTokens
39
+ estimateTokens, PATHS
40
40
  } = require('./flow-utils');
41
41
 
42
42
  // ============================================================
@@ -356,7 +356,7 @@ function collectContext({ description, targetFiles = [], additionalContext = []
356
356
  }
357
357
 
358
358
  // Add patterns from decisions.md
359
- const decisionsPath = path.join(PROJECT_ROOT, '.workflow', 'state', 'decisions.md');
359
+ const decisionsPath = PATHS.decisions;
360
360
  try {
361
361
  if (fs.existsSync(decisionsPath)) {
362
362
  const decisions = fs.readFileSync(decisionsPath, 'utf8');
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- 'use strict';
4
-
5
3
  /**
6
4
  * Wogi Flow - Contract Surface Scanner CLI
7
5
  *
@@ -18,10 +16,9 @@
18
16
 
19
17
  const fs = require('node:fs');
20
18
  const path = require('node:path');
21
- const { getProjectRoot, getConfig } = require('./flow-utils');
19
+ const { getProjectRoot, getConfig, PATHS } = require('./flow-utils');
22
20
 
23
- const PROJECT_ROOT = getProjectRoot();
24
- const DEFAULT_OUTPUT = path.join(PROJECT_ROOT, '.workflow', 'state', 'contract-surface.json');
21
+ const DEFAULT_OUTPUT = path.join(PATHS.state, 'contract-surface.json');
25
22
 
26
23
  function parseArgs(args) {
27
24
  const options = {
@@ -102,7 +99,7 @@ function main() {
102
99
  const { scanContracts } = require('./registries/contract-scanner');
103
100
 
104
101
  const scanOptions = {
105
- projectName: path.basename(PROJECT_ROOT),
102
+ projectName: path.basename(PATHS.root),
106
103
  projectType: options.type || contractConfig.projectType || undefined,
107
104
  maxFiles: contractConfig.maxFiles || 500,
108
105
  maxDepth: contractConfig.maxDepth || 6,
@@ -110,11 +107,11 @@ function main() {
110
107
  };
111
108
 
112
109
  if (options.verbose) {
113
- console.log(`Scanning ${PROJECT_ROOT}...`);
110
+ console.log(`Scanning ${PATHS.root}...`);
114
111
  console.log('');
115
112
  }
116
113
 
117
- const surface = scanContracts(PROJECT_ROOT, scanOptions);
114
+ const surface = scanContracts(PATHS.root, scanOptions);
118
115
 
119
116
  if (options.json) {
120
117
  console.log(JSON.stringify(surface, null, 2));
@@ -126,7 +123,7 @@ function main() {
126
123
  fs.mkdirSync(outputDir, { recursive: true });
127
124
  fs.writeFileSync(options.output, JSON.stringify(surface, null, 2));
128
125
 
129
- const relOutput = path.relative(PROJECT_ROOT, options.output);
126
+ const relOutput = path.relative(PATHS.root, options.output);
130
127
  console.log(`Contract surface saved to ${relOutput}`);
131
128
  console.log('');
132
129
  console.log('Summary:');
@@ -15,7 +15,7 @@
15
15
 
16
16
  const fs = require('node:fs');
17
17
  const path = require('node:path');
18
- const readline = require('node:readline');
18
+ const readline = require('node:readline/promises');
19
19
  const {
20
20
  PATHS,
21
21
  PROJECT_ROOT,
@@ -44,7 +44,7 @@ function getCorrectionsDir() {
44
44
  } catch (err) {
45
45
  // Fall back to default if config can't be read
46
46
  }
47
- return path.join(PROJECT_ROOT, '.workflow', 'corrections');
47
+ return PATHS.corrections;
48
48
  }
49
49
 
50
50
  const CORRECTIONS_DIR = getCorrectionsDir();
@@ -60,33 +60,23 @@ function createInterface() {
60
60
  });
61
61
  }
62
62
 
63
- function prompt(rl, question, defaultValue = '') {
64
- return new Promise((resolve) => {
65
- const suffix = defaultValue ? ` (${defaultValue})` : '';
66
- rl.question(`${question}${suffix}: `, (answer) => {
67
- resolve(answer.trim() || defaultValue);
68
- });
69
- });
63
+ async function prompt(rl, question, defaultValue = '') {
64
+ const suffix = defaultValue ? ` (${defaultValue})` : '';
65
+ const answer = await rl.question(`${question}${suffix}: `);
66
+ return answer.trim() || defaultValue;
70
67
  }
71
68
 
72
- function promptMultiline(rl, question) {
73
- return new Promise((resolve) => {
74
- console.log(`${question} (end with empty line):`);
75
- const lines = [];
76
-
77
- const readLine = () => {
78
- rl.question(' ', (line) => {
79
- if (line === '') {
80
- resolve(lines.join('\n'));
81
- } else {
82
- lines.push(line);
83
- readLine();
84
- }
85
- });
86
- };
69
+ async function promptMultiline(rl, question) {
70
+ console.log(`${question} (end with empty line):`);
71
+ const lines = [];
87
72
 
88
- readLine();
89
- });
73
+ while (true) {
74
+ const line = await rl.question(' ');
75
+ if (line === '') {
76
+ return lines.join('\n');
77
+ }
78
+ lines.push(line);
79
+ }
90
80
  }
91
81
 
92
82
  // ============================================================
@@ -221,7 +211,19 @@ function updateFeedbackPatterns(brief, taskId, skillName) {
221
211
  }
222
212
  }
223
213
 
224
- writeFile(PATHS.feedbackPatterns, content);
214
+ try {
215
+ const { writeToFeedbackPatterns } = require('./flow-learning-orchestrator');
216
+ writeToFeedbackPatterns({
217
+ content,
218
+ entryText: brief,
219
+ caller: 'flow-correct/updateFeedbackPatterns',
220
+ skipDedup: true // We already handle dedup via existing entry check above
221
+ }).catch(_err => {
222
+ if (process.env.DEBUG) console.error(`[DEBUG] updateFeedbackPatterns: ${_err.message}`);
223
+ });
224
+ } catch (_err) {
225
+ writeFile(PATHS.feedbackPatterns, content); // Fallback to direct write
226
+ }
225
227
  return existingCount;
226
228
  }
227
229
 
@@ -289,6 +289,10 @@ function spawnBackgroundDetection(userMessage, taskId) {
289
289
  const { spawn } = require('node:child_process');
290
290
  // Pass user message via env var instead of CLI args to prevent argument injection.
291
291
  // Only propagate necessary env vars to minimize exposure.
292
+ // NOTE: When CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 (Claude Code 2.1.83+), Anthropic/cloud
293
+ // credentials are stripped from subprocess environments. Since hooks run as subprocesses,
294
+ // ANTHROPIC_API_KEY may already be scrubbed by the time we reach here. The detector
295
+ // gracefully degrades (returns isCorrection: false) when no API key is available.
292
296
  const child = spawn(process.execPath, [
293
297
  __filename, 'detect-and-queue'
294
298
  ], {
@@ -298,7 +302,7 @@ function spawnBackgroundDetection(userMessage, taskId) {
298
302
  PATH: process.env.PATH,
299
303
  HOME: process.env.HOME,
300
304
  NODE_PATH: process.env.NODE_PATH || '',
301
- ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
305
+ ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || '',
302
306
  DEBUG: process.env.DEBUG || '',
303
307
  WOGI_DETECT_MESSAGE: userMessage.trim().slice(0, MAX_PROMPT_LENGTH),
304
308
  WOGI_DETECT_TASK_ID: taskId || ''
@@ -8,9 +8,10 @@
8
8
  *
9
9
  * Inspired by Hookify plugin patterns, adapted for multi-CLI compatibility.
10
10
  *
11
- * LIMITATION: The 'prompt' event type's AI analysis hook is not yet implemented.
12
- * Currently returns 'allow' for all prompts. See evaluatePromptWithAI() around line 734.
13
- * TODO: Integrate with an AI API for prompt safety evaluation.
11
+ * DESIGN NOTE: The 'prompt' event type uses pattern-matching only for safety evaluation.
12
+ * AI-based prompt analysis was considered but is out of scope — the pattern-matching
13
+ * approach (blocked/ask lists + event-based rules) provides sufficient protection
14
+ * without requiring external API calls or model inference.
14
15
  *
15
16
  * Usage:
16
17
  * flow damage-control check "<command>" Check if command is allowed
@@ -22,11 +23,9 @@
22
23
 
23
24
  const fs = require('node:fs');
24
25
  const path = require('node:path');
25
- const { getProjectRoot, colors, getConfig } = require('./flow-utils');
26
+ const { getProjectRoot, colors, getConfig, PATHS } = require('./flow-utils');
26
27
 
27
- const PROJECT_ROOT = getProjectRoot();
28
- const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
29
- const PATTERNS_FILE = path.join(WORKFLOW_DIR, 'damage-control.yaml');
28
+ const PATTERNS_FILE = path.join(PATHS.workflow, 'damage-control.yaml');
30
29
 
31
30
  // ============================================================
32
31
  // Event Types and Actions
@@ -200,7 +199,7 @@ function parseSimpleYaml(content) {
200
199
  // Sub-section under paths (indent 2): zeroAccess:, readOnly:, noDelete:
201
200
  if (currentSection === 'paths' && indent === 2 && trimmed.endsWith(':')) {
202
201
  currentSubSection = trimmed.slice(0, -1);
203
- result.paths[currentSubSection] = result.paths[currentSubSection] || [];
202
+ result.paths[currentSubSection] = result.paths[currentSubSection] ?? [];
204
203
  continue;
205
204
  }
206
205
 
@@ -351,9 +350,9 @@ function parseSimpleYaml(content) {
351
350
  */
352
351
  function loadPatterns() {
353
352
  const config = getConfig();
354
- const dcConfig = config.damageControl || {};
353
+ const dcConfig = config.damageControl ?? {};
355
354
  const patternsPath = dcConfig.patternsFile
356
- ? path.join(PROJECT_ROOT, dcConfig.patternsFile)
355
+ ? path.join(PATHS.root, dcConfig.patternsFile)
357
356
  : PATTERNS_FILE;
358
357
 
359
358
  if (!fs.existsSync(patternsPath)) {
@@ -435,7 +434,7 @@ function checkEventRule(rule, eventType, context) {
435
434
  */
436
435
  function checkEvent(eventType, context = {}) {
437
436
  const config = getConfig();
438
- const dcConfig = config.damageControl || {};
437
+ const dcConfig = config.damageControl ?? {};
439
438
 
440
439
  // Check if damage control is enabled
441
440
  if (!dcConfig.enabled) {
@@ -443,7 +442,7 @@ function checkEvent(eventType, context = {}) {
443
442
  }
444
443
 
445
444
  // Check if this event type is enabled
446
- const events = dcConfig.events || { bash: true };
445
+ const events = dcConfig.events ?? { bash: true };
447
446
  if (events[eventType] === false) {
448
447
  return { allowed: true, action: 'allow', message: `Event type '${eventType}' disabled` };
449
448
  }
@@ -451,7 +450,7 @@ function checkEvent(eventType, context = {}) {
451
450
  const patterns = loadPatterns();
452
451
 
453
452
  // Check event-based rules first (new format)
454
- for (const rule of patterns.rules || []) {
453
+ for (const rule of patterns.rules ?? []) {
455
454
  const action = checkEventRule(rule, eventType, context);
456
455
  if (action) {
457
456
  const result = {
@@ -481,7 +480,7 @@ function checkEvent(eventType, context = {}) {
481
480
 
482
481
  // Fall back to legacy patterns for bash events
483
482
  if (eventType === 'bash') {
484
- const cmd = context.command || '';
483
+ const cmd = context.command ?? '';
485
484
  const legacyResult = checkCommand(cmd);
486
485
  if (legacyResult.action !== 'allow') {
487
486
  return {
@@ -494,7 +493,8 @@ function checkEvent(eventType, context = {}) {
494
493
 
495
494
  // Fall back to legacy path patterns for file events
496
495
  if (eventType === 'file') {
497
- const filePath = context.file_path || context.filePath || '';
496
+ const rawPath = context.file_path ?? context.filePath ?? '';
497
+ const filePath = typeof rawPath === 'string' ? rawPath : '';
498
498
  const operation = context.operation || 'edit';
499
499
  const pathResult = checkPath(filePath, operation);
500
500
  if (!pathResult.allowed) {
@@ -515,11 +515,11 @@ function checkEvent(eventType, context = {}) {
515
515
  */
516
516
  function logDamageControl(eventType, context, result) {
517
517
  const config = getConfig();
518
- const dcConfig = config.damageControl || {};
518
+ const dcConfig = config.damageControl ?? {};
519
519
 
520
520
  if (!dcConfig.logging) return;
521
521
 
522
- const logDir = path.join(PROJECT_ROOT, '.workflow', 'logs');
522
+ const logDir = PATHS.logs;
523
523
  const logPath = path.join(logDir, 'damage-control.log');
524
524
 
525
525
  // Ensure log directory exists
@@ -586,7 +586,7 @@ function isSafeCommand(cmd) {
586
586
  */
587
587
  function checkCommand(cmd) {
588
588
  const config = getConfig();
589
- const dcConfig = config.damageControl || {};
589
+ const dcConfig = config.damageControl ?? {};
590
590
 
591
591
  if (!dcConfig.enabled) {
592
592
  return { action: 'allow' };
@@ -600,7 +600,7 @@ function checkCommand(cmd) {
600
600
  const patterns = loadPatterns();
601
601
 
602
602
  // Check blocked patterns
603
- for (const pattern of patterns.blocked || []) {
603
+ for (const pattern of patterns.blocked ?? []) {
604
604
  const regex = safeRegExp(pattern, 'i');
605
605
  if (!regex) continue; // Skip invalid/unsafe patterns
606
606
  if (regex.test(cmd)) {
@@ -613,7 +613,7 @@ function checkCommand(cmd) {
613
613
  }
614
614
 
615
615
  // Check ask patterns
616
- for (const item of patterns.ask || []) {
616
+ for (const item of patterns.ask ?? []) {
617
617
  const pattern = typeof item === 'string' ? item : item.pattern;
618
618
  const reason = typeof item === 'object' ? item.reason : 'Matches sensitive pattern';
619
619
 
@@ -660,20 +660,20 @@ function pathMatchesPattern(normalizedPath, pattern) {
660
660
  */
661
661
  function checkPath(filePath, operation) {
662
662
  const config = getConfig();
663
- const dcConfig = config.damageControl || {};
663
+ const dcConfig = config.damageControl ?? {};
664
664
 
665
665
  if (!dcConfig.enabled) {
666
666
  return { allowed: true };
667
667
  }
668
668
 
669
669
  const patterns = loadPatterns();
670
- const paths = patterns.paths || {};
670
+ const paths = patterns.paths ?? {};
671
671
 
672
672
  // Normalize path (handle both forward and backslashes)
673
673
  const normalizedPath = filePath.replace(/\\/g, '/');
674
674
 
675
675
  // Zero access - block all operations (read, write, delete)
676
- for (const p of paths.zeroAccess || []) {
676
+ for (const p of paths.zeroAccess ?? []) {
677
677
  if (pathMatchesPattern(normalizedPath, p)) {
678
678
  return {
679
679
  allowed: false,
@@ -685,7 +685,7 @@ function checkPath(filePath, operation) {
685
685
 
686
686
  // Read-only - block write/delete
687
687
  if (operation === 'write' || operation === 'delete') {
688
- for (const p of paths.readOnly || []) {
688
+ for (const p of paths.readOnly ?? []) {
689
689
  if (pathMatchesPattern(normalizedPath, p)) {
690
690
  return {
691
691
  allowed: false,
@@ -698,7 +698,7 @@ function checkPath(filePath, operation) {
698
698
 
699
699
  // No-delete - block delete only
700
700
  if (operation === 'delete') {
701
- for (const p of paths.noDelete || []) {
701
+ for (const p of paths.noDelete ?? []) {
702
702
  if (pathMatchesPattern(normalizedPath, p)) {
703
703
  return {
704
704
  allowed: false,
@@ -713,44 +713,37 @@ function checkPath(filePath, operation) {
713
713
  }
714
714
 
715
715
  /**
716
- * AI prompt hook for unknown dangerous commands
717
- * Returns: { action: 'allow' | 'block' | 'ask', reason?: string }
716
+ * Prompt hook for unknown dangerous commands — pattern-matching only.
717
+ *
718
+ * Checks the command against blocked/ask pattern lists and safe command whitelist.
719
+ * No AI/LLM evaluation is performed; the pattern-matching approach provides
720
+ * sufficient protection without external API calls.
718
721
  *
719
- * Note: Full implementation requires API integration.
720
- * This is a stub that can be enhanced with actual AI call.
722
+ * @param {string} cmd - The command to check
723
+ * @returns {Promise<{ action: 'allow' | 'block' | 'ask', reason?: string }>}
721
724
  */
722
725
  async function promptHookCheck(cmd) {
723
726
  const config = getConfig();
724
- const dcConfig = config.damageControl || {};
725
- const promptConfig = dcConfig.promptHook || {};
727
+ const dcConfig = config.damageControl ?? {};
728
+ const promptConfig = dcConfig.promptHook ?? {};
726
729
 
727
730
  if (!dcConfig.enabled || !promptConfig.enabled) {
728
731
  return { action: 'allow' };
729
732
  }
730
733
 
731
- // Warn users that the AI prompt analysis feature is not yet implemented.
732
- // Pattern-based checking below still works — only the AI analysis is stubbed.
733
- if (process.env.DEBUG) {
734
- console.error('[damage-control] promptHook AI analysis is not yet implemented — using pattern matching only');
735
- }
736
-
737
- // Skip if already caught by patterns
734
+ // Check against blocked/ask patterns
738
735
  const patternResult = checkCommand(cmd);
739
736
  if (patternResult.action !== 'allow') {
740
737
  return patternResult;
741
738
  }
742
739
 
743
- // Skip safe commands
740
+ // Safe commands are always allowed
744
741
  if (isSafeCommand(cmd)) {
745
742
  return { action: 'allow', reason: 'Safe command' };
746
743
  }
747
744
 
748
- // TODO: Implement actual AI API call
749
- // For now, return allow with a note
750
- return {
751
- action: 'allow',
752
- reason: 'Prompt hook enabled but API not yet integrated'
753
- };
745
+ // No pattern matched allow by default
746
+ return { action: 'allow', reason: 'No dangerous patterns matched' };
754
747
  }
755
748
 
756
749
  /**
@@ -758,22 +751,22 @@ async function promptHookCheck(cmd) {
758
751
  */
759
752
  function getStatus() {
760
753
  const config = getConfig();
761
- const dcConfig = config.damageControl || {};
754
+ const dcConfig = config.damageControl ?? {};
762
755
  const patterns = loadPatterns();
763
756
 
764
757
  return {
765
- enabled: dcConfig.enabled || false,
758
+ enabled: dcConfig.enabled ?? false,
766
759
  promptHook: {
767
- enabled: dcConfig.promptHook?.enabled || false,
760
+ enabled: dcConfig.promptHook?.enabled ?? false,
768
761
  model: dcConfig.promptHook?.model || 'haiku',
769
- aiAnalysis: 'not-implemented (pattern matching only)'
762
+ mode: 'pattern-matching-only'
770
763
  },
771
764
  patternsFile: dcConfig.patternsFile || '.workflow/damage-control.yaml',
772
765
  events: dcConfig.events || { bash: true, file: false, stop: false, prompt: false },
773
766
  patternsLoaded: {
774
- rules: (patterns.rules || []).length,
775
- blocked: (patterns.blocked || []).length,
776
- ask: (patterns.ask || []).length,
767
+ rules: (patterns.rules ?? []).length,
768
+ blocked: (patterns.blocked ?? []).length,
769
+ ask: (patterns.ask ?? []).length,
777
770
  paths: {
778
771
  zeroAccess: (patterns.paths?.zeroAccess || []).length,
779
772
  readOnly: (patterns.paths?.readOnly || []).length,
@@ -943,10 +936,10 @@ Configuration (config.json):
943
936
 
944
937
  // Show legacy patterns
945
938
  log('cyan', 'Legacy Blocked Patterns:');
946
- (patterns.blocked || []).forEach(p => log('red', ` - ${p}`));
939
+ (patterns.blocked ?? []).forEach(p => log('red', ` - ${p}`));
947
940
  console.log('');
948
941
  log('cyan', 'Legacy Ask Patterns:');
949
- (patterns.ask || []).forEach(p => {
942
+ (patterns.ask ?? []).forEach(p => {
950
943
  if (typeof p === 'object') {
951
944
  log('yellow', ` - ${p.pattern}`);
952
945
  log('dim', ` Reason: ${p.reason}`);
@@ -20,6 +20,7 @@
20
20
 
21
21
  const fs = require('node:fs');
22
22
  const path = require('node:path');
23
+ const { safeJsonParse } = require('./flow-io');
23
24
 
24
25
  // ============================================================
25
26
  // Section Parser
@@ -368,20 +369,9 @@ function main() {
368
369
  process.exit(1);
369
370
  }
370
371
 
371
- let resolutions;
372
- try {
373
- const raw = fs.readFileSync(resolutionsPath, 'utf-8');
374
- resolutions = JSON.parse(raw);
375
- // Prototype pollution protection (per security-patterns.md #2)
376
- if (resolutions && typeof resolutions === 'object') {
377
- for (const key of Object.keys(resolutions)) {
378
- if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
379
- delete resolutions[key];
380
- }
381
- }
382
- }
383
- } catch (err) {
384
- console.error(`Error reading resolutions file: ${err.message}`);
372
+ const resolutions = safeJsonParse(resolutionsPath, null);
373
+ if (!resolutions) {
374
+ console.error(`Error reading resolutions file: ${resolutionsPath}`);
385
375
  process.exit(1);
386
376
  }
387
377
 
@@ -17,8 +17,8 @@
17
17
 
18
18
  const fs = require('node:fs');
19
19
  const path = require('node:path');
20
- const readline = require('node:readline');
21
- const { colors: c, getProjectRoot, readJson } = require('./flow-utils');
20
+ const readline = require('node:readline/promises');
21
+ const { colors: c, getProjectRoot, readJson, PATHS } = require('./flow-utils');
22
22
  const { success: printSuccess, warn: printWarn, error: printError } = require('./flow-output');
23
23
 
24
24
  /**
@@ -462,12 +462,9 @@ async function confirmApply(diffs) {
462
462
  output: process.stdout
463
463
  });
464
464
 
465
- return new Promise((resolve) => {
466
- rl.question(`\nApply these changes? [${c.green}y${c.reset}/${c.red}N${c.reset}]: `, (answer) => {
467
- rl.close();
468
- resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
469
- });
470
- });
465
+ const answer = await rl.question(`\nApply these changes? [${c.green}y${c.reset}/${c.red}N${c.reset}]: `);
466
+ rl.close();
467
+ return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
471
468
  }
472
469
 
473
470
  /**