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
@@ -21,7 +21,8 @@ const { checkCommitLogGate } = require('../../core/commit-log-gate');
21
21
  const { claudeCodeAdapter } = require('../../adapters/claude-code');
22
22
  const { markSkillPending } = require('../../../flow-durable-session');
23
23
  const { getConfig } = require('../../../flow-utils');
24
- const { readHookInput } = require('../shared/read-stdin');
24
+ const { readHookStatus } = require('../../../flow-hook-status');
25
+ const { runHook } = require('../shared/hook-runner');
25
26
 
26
27
  // Lazy-load strict adherence to avoid circular deps and startup cost
27
28
  let _strictAdherence = null;
@@ -30,427 +31,325 @@ function getStrictAdherence() {
30
31
  try {
31
32
  _strictAdherence = require('../../../flow-strict-adherence');
32
33
  } catch (err) {
33
- // Module not available - strict adherence disabled
34
34
  _strictAdherence = { isEnabled: () => false, validateCommand: () => ({ valid: true }) };
35
35
  }
36
36
  }
37
37
  return _strictAdherence;
38
38
  }
39
39
 
40
- async function main() {
41
- try {
42
- // Read input from stdin with size limit and parse JSON safely
43
- const { input } = await readHookInput();
44
-
45
- // Handle empty or invalid input gracefully
46
- if (!input) {
47
- console.log(JSON.stringify({
48
- continue: true,
49
- hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow' }
50
- }));
51
- process.exit(0);
52
- return;
53
- }
54
-
55
- const parsedInput = claudeCodeAdapter.parseInput(input);
56
-
57
- const toolName = parsedInput.toolName;
58
- const toolInput = parsedInput.toolInput || {};
59
- const filePath = toolInput.file_path;
60
-
61
- // Agent-aware gating: detect subagent context from hook event fields
62
- // Claude Code now provides agent_id and agent_type for subagent tool calls.
63
- // Subagents in read-only phases (explore, review) should be allowed to read freely.
64
- const rawAgentId = input.agent_id || null;
65
- const rawAgentType = input.agent_type || null;
66
-
67
- // Validate agent_id format (alphanumeric + hyphens, reasonable length)
68
- // and agent_type against known values to prevent spoofing
69
- const VALID_AGENT_TYPES = new Set([
70
- 'general-purpose', 'Explore', 'Plan', 'code-reviewer', 'bug-analyzer',
71
- 'statusline-setup', 'claude-code-guide', 'ui-sketcher'
72
- ]);
73
- const agentId = (typeof rawAgentId === 'string' && /^[a-zA-Z0-9_-]{1,128}$/.test(rawAgentId)) ? rawAgentId : null;
74
- const agentType = (typeof rawAgentType === 'string' && VALID_AGENT_TYPES.has(rawAgentType)) ? rawAgentType : null;
75
- const isSubagent = !!agentId;
76
-
77
- // Determine subagent intent from agent_type and apply dynamic permissions
78
- // agent_type values from Claude Code: 'general-purpose', 'Explore', 'Plan', 'code-reviewer', etc.
79
- const readOnlyAgentTypes = new Set(['Explore', 'Plan', 'code-reviewer', 'bug-analyzer']);
80
- const subagentReadOnly = isSubagent && agentType ? readOnlyAgentTypes.has(agentType) : false;
81
-
82
- // Load config ONCE and pass to all gate functions (avoids 7-8 redundant reads per tool call)
83
- let config;
84
- try {
85
- config = getConfig();
86
- } catch (err) {
87
- if (process.env.DEBUG) console.error(`[Hook] Config load error: ${err.message}`);
88
- config = null; // Gates will fall back to their own getConfig() calls
89
- }
40
+ runHook('PreToolUse', async ({ input, parsedInput }) => {
41
+ const hookStart = process.hrtime.bigint();
90
42
 
91
- let coreResult = { allowed: true, blocked: false };
92
-
93
- // Phase gate check blocks tools not allowed in current workflow phase
94
- // Runs before all other gates. Fail-open: errors skip the check.
95
- // Subagents with read-only intent (Explore, Plan, code-reviewer) are allowed
96
- // to use read tools (Read, Glob, Grep, WebSearch, WebFetch) regardless of phase.
97
- const isReadTool = ['Read', 'Glob', 'Grep', 'WebSearch', 'WebFetch'].includes(toolName);
98
- const skipPhaseGateForSubagent = isSubagent && subagentReadOnly && isReadTool;
43
+ // Handle empty or invalid input gracefully
44
+ if (!input || Object.keys(input).length === 0) {
45
+ return { __raw: true, continue: true, hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow' } };
46
+ }
99
47
 
100
- if (!skipPhaseGateForSubagent) {
101
- try {
102
- const phaseResult = checkPhaseGate(toolName, toolInput, config);
103
- if (phaseResult.blocked) {
104
- coreResult = { allowed: false, blocked: true, reason: phaseResult.reason, message: phaseResult.message };
105
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
106
- console.log(JSON.stringify(output));
107
- process.exit(0);
108
- return;
109
- }
110
- } catch (err) {
111
- if (process.env.DEBUG) console.error(`[Hook] Phase gate error (fail-open): ${err.message}`);
48
+ const toolName = parsedInput.toolName;
49
+ const toolInput = parsedInput.toolInput || {};
50
+ const filePath = toolInput.file_path;
51
+
52
+ // Agent-aware gating: detect subagent context from hook event fields
53
+ const rawAgentId = input.agent_id || null;
54
+ const rawAgentType = input.agent_type || null;
55
+
56
+ const VALID_AGENT_TYPES = new Set([
57
+ 'general-purpose', 'Explore', 'Plan', 'code-reviewer', 'bug-analyzer',
58
+ 'statusline-setup', 'claude-code-guide', 'ui-sketcher'
59
+ ]);
60
+ const agentId = (typeof rawAgentId === 'string' && /^[a-zA-Z0-9_-]{1,128}$/.test(rawAgentId)) ? rawAgentId : null;
61
+ const agentType = (typeof rawAgentType === 'string' && VALID_AGENT_TYPES.has(rawAgentType)) ? rawAgentType : null;
62
+ const isSubagent = !!agentId;
63
+
64
+ const readOnlyAgentTypes = new Set(['Explore', 'Plan', 'code-reviewer', 'bug-analyzer']);
65
+ const subagentReadOnly = isSubagent && agentType ? readOnlyAgentTypes.has(agentType) : false;
66
+
67
+ // Fast path: read pre-computed hook status
68
+ const hookStatus = readHookStatus();
69
+ if (hookStatus && hookStatus.enforcement) {
70
+ const enf = hookStatus.enforcement;
71
+ const allGatesDisabled = enf.taskGating === false && enf.scopeGating === false
72
+ && enf.routingGate === false && enf.commitLogGate === false
73
+ && enf.todoWriteGate === false && enf.loopEnforcement === false
74
+ && hookStatus.componentReuse === false && hookStatus.phaseGate === false;
75
+ if (allGatesDisabled) {
76
+ if (process.env.DEBUG) {
77
+ const elapsed = Number(process.hrtime.bigint() - hookStart) / 1e6;
78
+ console.error(`[Hook] PreToolUse fast-path: ${elapsed.toFixed(1)}ms`);
112
79
  }
80
+ return { __raw: true, continue: true, hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow' } };
113
81
  }
82
+ }
83
+
84
+ // Load config ONCE
85
+ let config;
86
+ try {
87
+ config = getConfig();
88
+ } catch (err) {
89
+ if (process.env.DEBUG) console.error(`[Hook] Config load error: ${err.message}`);
90
+ config = null;
91
+ }
92
+
93
+ let coreResult = { allowed: true, blocked: false };
114
94
 
115
- // Task + scope gating check (for Edit and Write)
116
- // v4.0: checkScopeGate wraps checkTaskGate and adds scope validation
117
- if (toolName === 'Edit' || toolName === 'Write') {
118
- coreResult = checkScopeGate({
119
- filePath,
120
- operation: toolName.toLowerCase()
121
- }, config);
95
+ // Phase gate check
96
+ const isReadTool = ['Read', 'Glob', 'Grep', 'WebSearch', 'WebFetch'].includes(toolName);
97
+ const skipPhaseGateForSubagent = isSubagent && subagentReadOnly && isReadTool;
122
98
 
123
- // If blocked by task or scope gating, return early
124
- if (coreResult.blocked) {
99
+ if (!skipPhaseGateForSubagent) {
100
+ try {
101
+ const phaseResult = checkPhaseGate(toolName, toolInput, config);
102
+ if (phaseResult.blocked) {
103
+ coreResult = { allowed: false, blocked: true, reason: phaseResult.reason, message: phaseResult.message };
125
104
  const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
126
- console.log(JSON.stringify(output));
127
- process.exit(0);
128
- return;
105
+ return { __raw: true, ...output };
129
106
  }
107
+ } catch (err) {
108
+ if (process.env.DEBUG) console.error(`[Hook] Phase gate error (fail-open): ${err.message}`);
130
109
  }
110
+ }
131
111
 
132
- // TodoWrite gating check (for TodoWrite)
133
- if (toolName === 'TodoWrite') {
134
- const todos = toolInput.todos || [];
135
- coreResult = checkTodoWriteGate({ todos }, config);
112
+ // Task + scope gating check (for Edit and Write)
113
+ if (toolName === 'Edit' || toolName === 'Write') {
114
+ coreResult = checkScopeGate({
115
+ filePath,
116
+ operation: toolName.toLowerCase()
117
+ }, config);
136
118
 
137
- // If blocked by TodoWrite gating, return early
138
- if (coreResult.blocked) {
139
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
140
- console.log(JSON.stringify(output));
141
- process.exit(0);
142
- return;
143
- }
119
+ if (coreResult.blocked) {
120
+ const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
121
+ return { __raw: true, ...output };
144
122
  }
123
+ }
145
124
 
146
- // v4.1: Skill execution tracking (for Skill tool)
147
- // Catches natural language skill invocations (e.g., "do the bulk tasks")
148
- if (toolName === 'Skill') {
149
- const skillName = toolInput.skill;
150
- if (typeof skillName === 'string' && /^wogi-(bulk|start)$/i.test(skillName)) {
151
- markSkillPending(skillName.toLowerCase(), { args: toolInput.args });
152
- if (process.env.DEBUG) {
153
- console.error(`[Hook] Marked skill ${skillName} as pending (via Skill tool)`);
154
- }
155
- }
125
+ // TodoWrite gating check
126
+ if (toolName === 'TodoWrite') {
127
+ const todos = toolInput.todos || [];
128
+ coreResult = checkTodoWriteGate({ todos }, config);
156
129
 
157
- // v6.0: Clear routing-pending flag on ANY /wogi-* skill invocation
158
- // This is the "routing happened" signal that unblocks Bash calls
159
- if (typeof skillName === 'string' && /^wogi-/i.test(skillName)) {
160
- try {
161
- clearRoutingPending();
162
- if (process.env.DEBUG) {
163
- console.error(`[Hook] Cleared routing-pending flag (Skill: ${skillName})`);
164
- }
165
- } catch (err) {
166
- // Non-blocking - don't fail the hook if clear fails
167
- if (process.env.DEBUG) {
168
- console.error(`[Hook] Failed to clear routing flag: ${err.message}`);
169
- }
170
- }
171
- }
130
+ if (coreResult.blocked) {
131
+ const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
132
+ return { __raw: true, ...output };
172
133
  }
134
+ }
173
135
 
174
- // v6.0: Routing gate — blocks tools when no /wogi-* command has been invoked first.
175
- // Edit/Write MUST be gated — AI can edit ready.json to create fake active tasks.
176
- // Agent MUST be gated — AI can spawn subagents that bypass routing entirely.
177
- // WebSearch/WebFetch gated for consistency with GATED_TOOLS in routing-gate.js.
178
- // NOTE: This list must stay in sync with GATED_TOOLS in routing-gate.js and the
179
- // PreToolUse matcher in settings.json. The matcher is a SUPERSET (adds Skill, TodoWrite).
180
- // v7.0: Subagents exempt spawned by main agent which already went through routing.
181
- // v7.1: Defense-in-depth — only bypass when an active task exists.
182
- // v8.0: Added Agent, WebSearch, WebFetch to close bypass vectors.
183
- // v8.1: Whitelist read-only git commands — Claude naturally runs git status/log/diff
184
- // to gather context before routing. These are pure reads with no side effects.
185
- const skipRoutingGateForSubagent = isSubagent && hasActiveTask();
186
-
187
- // Read-only git commands whitelist — allowed before routing.
188
- // These are pure read operations that cannot bypass task tracking.
189
- // Safety: reject commands with shell chaining operators to prevent abuse.
190
- let skipRoutingGateForReadOnlyGit = false;
191
- if (toolName === 'Bash' && toolInput.command) {
192
- const cmd = toolInput.command.trim();
193
- const READ_ONLY_GIT_PREFIXES = [
194
- 'git status', 'git log', 'git diff', 'git branch',
195
- 'git show', 'git rev-parse', 'git remote -v', 'git tag -l',
196
- 'git ls-files', 'git describe'
197
- ];
198
- // Block shell chaining operators AND control characters that could bypass prefix matching
199
- const SHELL_CHAIN_OPERATORS = /[;&|`$()\n\r\\]/;
200
- // Block destructive flags that could appear after an otherwise-safe prefix
201
- const DESTRUCTIVE_GIT_FLAGS = /\s-[dD]\b|\s--delete\b|\s--force\b|\s--hard\b|\s--prune\b/;
202
- if (
203
- READ_ONLY_GIT_PREFIXES.some(prefix => cmd.startsWith(prefix)) &&
204
- !SHELL_CHAIN_OPERATORS.test(cmd) &&
205
- !DESTRUCTIVE_GIT_FLAGS.test(cmd)
206
- ) {
207
- skipRoutingGateForReadOnlyGit = true;
136
+ // v4.1: Skill execution tracking
137
+ if (toolName === 'Skill') {
138
+ const skillName = toolInput.skill;
139
+ if (typeof skillName === 'string' && /^wogi-(bulk|start)$/i.test(skillName)) {
140
+ markSkillPending(skillName.toLowerCase(), { args: toolInput.args });
141
+ if (process.env.DEBUG) {
142
+ console.error(`[Hook] Marked skill ${skillName} as pending (via Skill tool)`);
208
143
  }
209
144
  }
210
145
 
211
- if (!skipRoutingGateForSubagent && !skipRoutingGateForReadOnlyGit && (toolName === 'Bash' || toolName === 'EnterPlanMode' || toolName === 'Read' || toolName === 'Glob' || toolName === 'Grep' || toolName === 'Edit' || toolName === 'Write' || toolName === 'NotebookEdit' || toolName === 'Agent' || toolName === 'WebSearch' || toolName === 'WebFetch')) {
146
+ // v6.0: Clear routing-pending flag on ANY /wogi-* skill invocation
147
+ if (typeof skillName === 'string' && /^wogi-/i.test(skillName)) {
212
148
  try {
213
- const routingResult = checkRoutingGate(toolName, config);
214
- if (routingResult.blocked) {
215
- coreResult = {
216
- allowed: false,
217
- blocked: true,
218
- reason: `Routing gate: ${routingResult.reason}`,
219
- message: routingResult.message
220
- };
221
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
222
- console.log(JSON.stringify(output));
223
- process.exit(0);
224
- return;
149
+ clearRoutingPending();
150
+ if (process.env.DEBUG) {
151
+ console.error(`[Hook] Cleared routing-pending flag (Skill: ${skillName})`);
225
152
  }
226
153
  } catch (err) {
227
- // Fail-CLOSED for routing gate — users installed WogiFlow for enforcement.
228
- // If the gate check itself errors, deny the tool rather than silently allowing
229
- // the exact bypass this system exists to prevent.
230
154
  if (process.env.DEBUG) {
231
- console.error(`[Hook] Routing gate error (fail-closed): ${err.message}`);
155
+ console.error(`[Hook] Failed to clear routing flag: ${err.message}`);
232
156
  }
157
+ }
158
+ }
159
+ }
160
+
161
+ // v6.0: Routing gate
162
+ const skipRoutingGateForSubagent = isSubagent && hasActiveTask();
163
+
164
+ let skipRoutingGateForReadOnlyGit = false;
165
+ if (toolName === 'Bash' && toolInput.command) {
166
+ const cmd = toolInput.command.trim();
167
+ const READ_ONLY_GIT_PREFIXES = [
168
+ 'git status', 'git log', 'git diff', 'git branch',
169
+ 'git show', 'git rev-parse', 'git remote -v', 'git tag -l',
170
+ 'git ls-files', 'git describe'
171
+ ];
172
+ const SHELL_CHAIN_OPERATORS = /[;&|`$()\n\r\\]/;
173
+ const DESTRUCTIVE_GIT_FLAGS = /\s-[dD]\b|\s--delete\b|\s--force\b|\s--hard\b|\s--prune\b/;
174
+ if (
175
+ READ_ONLY_GIT_PREFIXES.some(prefix => cmd.startsWith(prefix)) &&
176
+ !SHELL_CHAIN_OPERATORS.test(cmd) &&
177
+ !DESTRUCTIVE_GIT_FLAGS.test(cmd)
178
+ ) {
179
+ skipRoutingGateForReadOnlyGit = true;
180
+ }
181
+ }
182
+
183
+ if (!skipRoutingGateForSubagent && !skipRoutingGateForReadOnlyGit && (toolName === 'Bash' || toolName === 'EnterPlanMode' || toolName === 'Read' || toolName === 'Glob' || toolName === 'Grep' || toolName === 'Edit' || toolName === 'Write' || toolName === 'NotebookEdit' || toolName === 'Agent' || toolName === 'WebSearch' || toolName === 'WebFetch')) {
184
+ try {
185
+ const routingResult = checkRoutingGate(toolName, config);
186
+ if (routingResult.blocked) {
233
187
  coreResult = {
234
188
  allowed: false,
235
189
  blocked: true,
236
- reason: `Routing gate error: ${err.message}`,
237
- message: 'Routing gate check failed. Please invoke /wogi-start first. Use Skill(skill="wogi-start", args="<your request>").'
190
+ reason: `Routing gate: ${routingResult.reason}`,
191
+ message: routingResult.message
238
192
  };
239
- const errOutput = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
240
- console.log(JSON.stringify(errOutput));
241
- process.exit(0);
242
- return;
193
+ const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
194
+ return { __raw: true, ...output };
243
195
  }
244
- }
245
-
246
- // Commit log gate check (for Bash git commit commands)
247
- // v9.0: Block git commit when active task has no request-log entry staged.
248
- // Same mechanical enforcement pattern as routing gate.
249
- if (toolName === 'Bash' && toolInput.command) {
250
- try {
251
- const commitLogResult = checkCommitLogGate(toolInput.command, config);
252
- if (commitLogResult.blocked) {
253
- coreResult = {
254
- allowed: false,
255
- blocked: true,
256
- reason: `Commit log gate: ${commitLogResult.reason}`,
257
- message: commitLogResult.message
258
- };
259
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
260
- console.log(JSON.stringify(output));
261
- process.exit(0);
262
- return;
263
- }
264
- } catch (err) {
265
- // Fail-open for commit log gate — don't block work if gate has issues
266
- if (process.env.DEBUG) {
267
- console.error(`[Hook] Commit log gate error (fail-open): ${err.message}`);
268
- }
196
+ } catch (err) {
197
+ // Fail-CLOSED for routing gate
198
+ if (process.env.DEBUG) {
199
+ console.error(`[Hook] Routing gate error (fail-closed): ${err.message}`);
269
200
  }
201
+ coreResult = {
202
+ allowed: false,
203
+ blocked: true,
204
+ reason: `Routing gate error: ${err.message}`,
205
+ message: 'Routing gate check failed. Please invoke /wogi-start first. Use Skill(skill="wogi-start", args="<your request>").'
206
+ };
207
+ const errOutput = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
208
+ return { __raw: true, ...errOutput };
270
209
  }
210
+ }
271
211
 
272
- // Strict adherence check (for Bash commands)
273
- // v5.0: Block AI from using wrong package manager or port
274
- if (toolName === 'Bash') {
275
- const command = toolInput.command;
276
- if (command) {
277
- const strictAdherence = getStrictAdherence();
278
- if (strictAdherence.isEnabled()) {
279
- const cmdResult = strictAdherence.validateCommand(command);
280
- if (cmdResult.blocked) {
281
- // Return with auto-corrected command suggestion
282
- coreResult = {
283
- allowed: false,
284
- blocked: true,
285
- reason: `Strict adherence: ${cmdResult.reason}`,
286
- message: cmdResult.autoCorrect
287
- ? `⚠️ BLOCKED: ${cmdResult.reason}\n\n✅ Auto-correcting to: ${cmdResult.autoCorrect}`
288
- : `⚠️ BLOCKED: ${cmdResult.reason}\n\n💡 ${cmdResult.suggestion || 'Please use the correct pattern.'}`
289
- };
290
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
291
- console.log(JSON.stringify(output));
292
- process.exit(0);
293
- return;
294
- }
295
- }
212
+ // Commit log gate check
213
+ if (toolName === 'Bash' && toolInput.command) {
214
+ try {
215
+ const commitLogResult = checkCommitLogGate(toolInput.command, config);
216
+ if (commitLogResult.blocked) {
217
+ coreResult = {
218
+ allowed: false,
219
+ blocked: true,
220
+ reason: `Commit log gate: ${commitLogResult.reason}`,
221
+ message: commitLogResult.message
222
+ };
223
+ const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
224
+ return { __raw: true, ...output };
225
+ }
226
+ } catch (err) {
227
+ if (process.env.DEBUG) {
228
+ console.error(`[Hook] Commit log gate error (fail-open): ${err.message}`);
296
229
  }
297
230
  }
231
+ }
298
232
 
299
- // Damage control check (for Bash commands)
300
- // Checks commands against damage-control rules (force-push, rm -rf, etc.)
301
- if (toolName === 'Bash' && toolInput.command && config?.damageControl?.enabled) {
302
- try {
303
- const dc = require('../../../flow-damage-control');
304
- const dcResult = dc.checkBashEvent(toolInput.command);
305
- if (dcResult && dcResult.action === 'block') {
233
+ // Strict adherence check (for Bash commands)
234
+ if (toolName === 'Bash') {
235
+ const command = toolInput.command;
236
+ if (command) {
237
+ const strictAdherence = getStrictAdherence();
238
+ if (strictAdherence.isEnabled()) {
239
+ const cmdResult = strictAdherence.validateCommand(command);
240
+ if (cmdResult.blocked) {
306
241
  coreResult = {
307
242
  allowed: false,
308
243
  blocked: true,
309
- reason: `Damage control: ${dcResult.reason || dcResult.message || 'blocked by rule'}`,
310
- message: `\u26d4 BLOCKED by damage control: ${dcResult.message || dcResult.reason || 'This command matches a blocked pattern.'}\n\nRule: ${dcResult.rule || 'unknown'}`
311
- };
312
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
313
- console.log(JSON.stringify(output));
314
- process.exit(0);
315
- return;
316
- }
317
- if (dcResult && dcResult.action === 'ask') {
318
- // Emit 'ask' warning immediately so it's visible to the user
319
- // (don't let subsequent hook stages overwrite it)
320
- coreResult = {
321
- allowed: true,
322
- blocked: false,
323
- reason: `Damage control warning: ${dcResult.reason || dcResult.message || 'requires confirmation'}`,
324
- message: `\u26a0\ufe0f Damage control: ${dcResult.message || dcResult.reason || 'This command matches an ask-before-execute pattern.'}`
244
+ reason: `Strict adherence: ${cmdResult.reason}`,
245
+ message: cmdResult.autoCorrect
246
+ ? `⚠️ BLOCKED: ${cmdResult.reason}\n\n✅ Auto-correcting to: ${cmdResult.autoCorrect}`
247
+ : `⚠️ BLOCKED: ${cmdResult.reason}\n\n💡 ${cmdResult.suggestion || 'Please use the correct pattern.'}`
325
248
  };
326
249
  const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
327
- console.log(JSON.stringify(output));
328
- process.exit(0);
329
- return;
330
- }
331
- } catch (err) {
332
- // Fail-open for damage control — don't block work if DC module has issues
333
- if (process.env.DEBUG) {
334
- console.error(`[Hook] Damage control error (fail-open): ${err.message}`);
250
+ return { __raw: true, ...output };
335
251
  }
336
252
  }
337
253
  }
254
+ }
338
255
 
339
- // Damage control check (for file operations)
340
- if ((toolName === 'Edit' || toolName === 'Write') && filePath && config?.damageControl?.enabled && config?.damageControl?.events?.file) {
341
- try {
342
- const dc = require('../../../flow-damage-control');
343
- const dcResult = dc.checkFileEvent(filePath, toolName.toLowerCase());
344
- if (dcResult && dcResult.action === 'block') {
345
- coreResult = {
346
- allowed: false,
347
- blocked: true,
348
- reason: `Damage control: ${dcResult.reason || 'file access blocked'}`,
349
- message: `\u26d4 BLOCKED by damage control: ${dcResult.message || 'This file is protected.'}`
350
- };
351
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
352
- console.log(JSON.stringify(output));
353
- process.exit(0);
354
- return;
355
- }
356
- } catch (err) {
357
- if (process.env.DEBUG) {
358
- console.error(`[Hook] Damage control file check error (fail-open): ${err.message}`);
359
- }
256
+ // Damage control check (for Bash commands)
257
+ if (toolName === 'Bash' && toolInput.command && config?.damageControl?.enabled) {
258
+ try {
259
+ const dc = require('../../../flow-damage-control');
260
+ const dcResult = dc.checkBashEvent(toolInput.command);
261
+ if (dcResult && dcResult.action === 'block') {
262
+ coreResult = {
263
+ allowed: false,
264
+ blocked: true,
265
+ reason: `Damage control: ${dcResult.reason || dcResult.message || 'blocked by rule'}`,
266
+ message: `\u26d4 BLOCKED by damage control: ${dcResult.message || dcResult.reason || 'This command matches a blocked pattern.'}\n\nRule: ${dcResult.rule || 'unknown'}`
267
+ };
268
+ const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
269
+ return { __raw: true, ...output };
270
+ }
271
+ if (dcResult && dcResult.action === 'ask') {
272
+ coreResult = {
273
+ allowed: true,
274
+ blocked: false,
275
+ reason: `Damage control warning: ${dcResult.reason || dcResult.message || 'requires confirmation'}`,
276
+ message: `\u26a0\ufe0f Damage control: ${dcResult.message || dcResult.reason || 'This command matches an ask-before-execute pattern.'}`
277
+ };
278
+ const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
279
+ return { __raw: true, ...output };
280
+ }
281
+ } catch (err) {
282
+ if (process.env.DEBUG) {
283
+ console.error(`[Hook] Damage control error (fail-open): ${err.message}`);
360
284
  }
361
285
  }
286
+ }
362
287
 
363
- // Component reuse check (for Write only)
364
- if (toolName === 'Write' && filePath) {
365
- const componentResult = checkComponentReuse({
366
- filePath,
367
- content: toolInput.content
368
- }, config);
369
-
370
- // Merge results - component check can add warning or block
371
- if (componentResult.blocked || componentResult.warning) {
288
+ // Damage control check (for file operations)
289
+ if ((toolName === 'Edit' || toolName === 'Write') && filePath && config?.damageControl?.enabled && config?.damageControl?.events?.file) {
290
+ try {
291
+ const dc = require('../../../flow-damage-control');
292
+ const dcResult = dc.checkFileEvent(filePath, toolName.toLowerCase());
293
+ if (dcResult && dcResult.action === 'block') {
372
294
  coreResult = {
373
- ...coreResult,
374
- ...componentResult,
375
- // Preserve task gating allowance unless component check blocks
376
- allowed: !componentResult.blocked,
377
- blocked: componentResult.blocked
295
+ allowed: false,
296
+ blocked: true,
297
+ reason: `Damage control: ${dcResult.reason || 'file access blocked'}`,
298
+ message: `\u26d4 BLOCKED by damage control: ${dcResult.message || 'This file is protected.'}`
378
299
  };
300
+ const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
301
+ return { __raw: true, ...output };
379
302
  }
380
-
381
- // Strict adherence: File naming check (for Write)
382
- // v5.0: Block AI from creating files with wrong naming convention
383
- // v5.1: Fixed to pass basename instead of full path
384
- if (!coreResult.blocked) {
385
- const strictAdherence = getStrictAdherence();
386
- if (strictAdherence.isEnabled()) {
387
- // Determine file type from path (more precise matching)
388
- // Only match if path contains /components/, /ui/, /api/, /routes/ directories
389
- const isComponent = /\/(components?|ui)\//i.test(filePath) && /\.(tsx|jsx)$/i.test(filePath);
390
- const isApi = /\/(api|routes)\//i.test(filePath);
391
- const fileType = isComponent ? 'component' : isApi ? 'api' : 'generic';
392
-
393
- // Extract basename for validation (validateFileName expects just the filename)
394
- const fileName = path.basename(filePath);
395
- const fileResult = strictAdherence.validateFileName(fileName, fileType);
396
- if (fileResult.blocked) {
397
- coreResult = {
398
- allowed: false,
399
- blocked: true,
400
- reason: `Strict adherence: ${fileResult.reason}`,
401
- message: `⚠️ BLOCKED: ${fileResult.reason}\n\n💡 ${fileResult.suggestion || 'Please use the correct naming convention.'}`
402
- };
403
- }
404
- }
303
+ } catch (err) {
304
+ if (process.env.DEBUG) {
305
+ console.error(`[Hook] Damage control file check error (fail-open): ${err.message}`);
405
306
  }
406
307
  }
308
+ }
407
309
 
408
- // Transform to Claude Code format
409
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
410
-
411
- // Output JSON
412
- console.log(JSON.stringify(output));
413
- process.exit(0);
414
- } catch (err) {
415
- // Fail-closed: deny the tool use on hook errors to prevent untracked edits
416
- // Users installed WogiFlow to enforce task tracking - failing open would bypass that
417
- if (process.env.DEBUG) {
418
- console.error(`[Wogi Flow Hook Error] ${err.message}`);
419
- } else {
420
- console.error('[Wogi Flow Hook] Validation error occurred');
310
+ // Component reuse check (for Write only)
311
+ if (toolName === 'Write' && filePath) {
312
+ const componentResult = checkComponentReuse({
313
+ filePath,
314
+ content: toolInput.content
315
+ }, config);
316
+
317
+ if (componentResult.blocked || componentResult.warning) {
318
+ coreResult = {
319
+ ...coreResult,
320
+ ...componentResult,
321
+ allowed: !componentResult.blocked,
322
+ blocked: componentResult.blocked
323
+ };
421
324
  }
422
- console.log(JSON.stringify({
423
- continue: true,
424
- hookSpecificOutput: {
425
- hookEventName: 'PreToolUse',
426
- permissionDecision: 'deny',
427
- permissionDecisionReason: 'WogiFlow validation error. Please check your setup or use /wogi-start.'
325
+
326
+ // Strict adherence: File naming check (for Write)
327
+ if (!coreResult.blocked) {
328
+ const strictAdherence = getStrictAdherence();
329
+ if (strictAdherence.isEnabled()) {
330
+ const isComponent = /\/(components?|ui)\//i.test(filePath) && /\.(tsx|jsx)$/i.test(filePath);
331
+ const isApi = /\/(api|routes)\//i.test(filePath);
332
+ const fileType = isComponent ? 'component' : isApi ? 'api' : 'generic';
333
+
334
+ const fileName = path.basename(filePath);
335
+ const fileResult = strictAdherence.validateFileName(fileName, fileType);
336
+ if (fileResult.blocked) {
337
+ coreResult = {
338
+ allowed: false,
339
+ blocked: true,
340
+ reason: `Strict adherence: ${fileResult.reason}`,
341
+ message: `⚠️ BLOCKED: ${fileResult.reason}\n\n💡 ${fileResult.suggestion || 'Please use the correct naming convention.'}`
342
+ };
343
+ }
428
344
  }
429
- }));
430
- process.exit(0);
345
+ }
431
346
  }
432
- }
433
-
434
- // Handle stdin properly
435
- process.stdin.setEncoding('utf8');
436
347
 
437
- // Must await async main() to prevent race conditions
438
- (async () => {
439
- try {
440
- await main();
441
- } catch (err) {
442
- // Fail-closed: deny on unexpected errors
443
- if (process.env.DEBUG) {
444
- console.error(`[Wogi Flow Hook] Unexpected error: ${err.message}`);
445
- }
446
- console.log(JSON.stringify({
447
- continue: true,
448
- hookSpecificOutput: {
449
- hookEventName: 'PreToolUse',
450
- permissionDecision: 'deny',
451
- permissionDecisionReason: 'WogiFlow hook error. Use /wogi-start to route your request.'
452
- }
453
- }));
454
- process.exit(0);
348
+ // Benchmark: log hook latency when DEBUG is enabled
349
+ if (process.env.DEBUG) {
350
+ const elapsed = Number(process.hrtime.bigint() - hookStart) / 1e6;
351
+ console.error(`[Hook] PreToolUse latency: ${elapsed.toFixed(1)}ms`);
455
352
  }
456
- })();
353
+
354
+ return coreResult;
355
+ }, { failMode: 'block' });