wogiflow 2.17.0 → 2.18.0

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 (111) hide show
  1. package/.claude/commands/wogi-audit.md +212 -17
  2. package/.claude/commands/wogi-research.md +37 -0
  3. package/.claude/commands/wogi-review.md +200 -22
  4. package/.claude/commands/wogi-start.md +45 -0
  5. package/.claude/docs/claude-code-compatibility.md +46 -1
  6. package/.claude/docs/intent-grounded-review.md +209 -0
  7. package/.claude/settings.json +34 -1
  8. package/.workflow/agents/logic-adversary.md +8 -0
  9. package/.workflow/templates/claude-md.hbs +18 -0
  10. package/lib/installer.js +22 -0
  11. package/lib/utils.js +29 -3
  12. package/lib/workspace-changelog.js +2 -1
  13. package/lib/workspace-channel-server.js +4 -6
  14. package/lib/workspace-contracts.js +5 -4
  15. package/lib/workspace-events.js +8 -7
  16. package/lib/workspace-gates.js +4 -3
  17. package/lib/workspace-integration-tests.js +2 -1
  18. package/lib/workspace-intelligence.js +3 -2
  19. package/lib/workspace-locks.js +2 -1
  20. package/lib/workspace-messages.js +7 -6
  21. package/lib/workspace-routing.js +14 -26
  22. package/lib/workspace-session.js +7 -6
  23. package/lib/workspace-sync.js +9 -8
  24. package/package.json +4 -2
  25. package/scripts/base-workflow-step.js +1 -1
  26. package/scripts/flow +19 -0
  27. package/scripts/flow-adaptive-learning.js +1 -1
  28. package/scripts/flow-aggregate.js +2 -1
  29. package/scripts/flow-architect-pass.js +3 -3
  30. package/scripts/flow-archive-runs.js +372 -0
  31. package/scripts/flow-ask.js +1 -1
  32. package/scripts/flow-ast-grep.js +216 -0
  33. package/scripts/flow-audit-gates.js +1 -1
  34. package/scripts/flow-auto-learn.js +8 -11
  35. package/scripts/flow-bug.js +2 -2
  36. package/scripts/flow-capture-gate.js +644 -0
  37. package/scripts/flow-capture.js +4 -3
  38. package/scripts/flow-cli-flags.js +95 -0
  39. package/scripts/flow-community-sync.js +2 -1
  40. package/scripts/flow-community.js +6 -6
  41. package/scripts/flow-conclusion-classifier.js +310 -0
  42. package/scripts/flow-config-defaults.js +3 -3
  43. package/scripts/flow-constants.js +8 -11
  44. package/scripts/flow-context-scoring.js +1 -0
  45. package/scripts/flow-correction-detector.js +344 -3
  46. package/scripts/flow-damage-control.js +1 -1
  47. package/scripts/flow-decisions-merge.js +1 -0
  48. package/scripts/flow-done-gates.js +20 -0
  49. package/scripts/flow-done-report.js +2 -2
  50. package/scripts/flow-done.js +4 -4
  51. package/scripts/flow-epics.js +5 -11
  52. package/scripts/flow-health.js +145 -1
  53. package/scripts/flow-id.js +92 -0
  54. package/scripts/flow-io.js +15 -5
  55. package/scripts/flow-knowledge-router.js +2 -1
  56. package/scripts/flow-links.js +1 -1
  57. package/scripts/flow-log-manager.js +2 -1
  58. package/scripts/flow-logic-adversary.js +4 -4
  59. package/scripts/flow-long-input-cli.js +6 -0
  60. package/scripts/flow-long-input-stories.js +1 -1
  61. package/scripts/flow-loop-retry-learning.js +1 -1
  62. package/scripts/flow-mcp-capabilities.js +2 -3
  63. package/scripts/flow-mcp-docs.js +2 -1
  64. package/scripts/flow-memory-blocks.js +2 -1
  65. package/scripts/flow-memory-sync.js +1 -1
  66. package/scripts/flow-memory.js +767 -0
  67. package/scripts/flow-migrate-igr.js +1 -1
  68. package/scripts/flow-migrate.js +2 -1
  69. package/scripts/flow-model-adapter.js +1 -1
  70. package/scripts/flow-model-config.js +5 -1
  71. package/scripts/flow-model-profile.js +2 -1
  72. package/scripts/flow-orchestrate.js +3 -3
  73. package/scripts/flow-output.js +29 -0
  74. package/scripts/flow-parallel.js +10 -9
  75. package/scripts/flow-pattern-enforcer.js +2 -1
  76. package/scripts/flow-permissions-audit.js +124 -0
  77. package/scripts/flow-plugin-registry.js +2 -2
  78. package/scripts/flow-progress.js +5 -1
  79. package/scripts/flow-project-analyzer.js +1 -1
  80. package/scripts/flow-promote.js +510 -0
  81. package/scripts/flow-registries.js +86 -0
  82. package/scripts/flow-request-log.js +133 -0
  83. package/scripts/flow-research-protocol.js +0 -1
  84. package/scripts/flow-revision-tracker.js +2 -1
  85. package/scripts/flow-roadmap.js +2 -1
  86. package/scripts/flow-rules-sync.js +3 -7
  87. package/scripts/flow-session-end.js +3 -1
  88. package/scripts/flow-session-learning.js +6 -13
  89. package/scripts/flow-session-state.js +2 -2
  90. package/scripts/flow-setup-hooks.js +2 -1
  91. package/scripts/flow-skill-create.js +1 -1
  92. package/scripts/flow-skill-freshness.js +6 -7
  93. package/scripts/flow-skill-learn.js +1 -1
  94. package/scripts/flow-step-coverage.js +1 -1
  95. package/scripts/flow-step-security.js +1 -1
  96. package/scripts/flow-story.js +58 -10
  97. package/scripts/flow-sys.js +204 -0
  98. package/scripts/flow-task-hierarchy.js +88 -0
  99. package/scripts/flow-tech-debt.js +2 -1
  100. package/scripts/flow-test-api.js +1 -1
  101. package/scripts/flow-utils.js +60 -890
  102. package/scripts/hooks/core/bugfix-scope-gate.js +5 -4
  103. package/scripts/hooks/core/deploy-gate.js +1 -1
  104. package/scripts/hooks/core/pre-tool-helpers.js +72 -0
  105. package/scripts/hooks/core/pre-tool-orchestrator.js +442 -0
  106. package/scripts/hooks/core/routing-gate.js +8 -0
  107. package/scripts/hooks/core/session-end.js +28 -0
  108. package/scripts/hooks/entry/claude-code/pre-tool-use.js +48 -492
  109. package/scripts/hooks/entry/shared/hook-runner.js +1 -1
  110. package/scripts/registries/schema-registry.js +1 -1
  111. package/scripts/registries/service-registry.js +1 -1
@@ -1,27 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Wogi Flow - Claude Code PreToolUse Hook
4
+ * Wogi Flow - Claude Code PreToolUse Hook (Entry)
5
5
  *
6
- * Called before Edit/Write/TodoWrite/Skill/Bash tool execution.
7
- * Enforces task gating, scope validation, component reuse checking,
8
- * TodoWrite gating, and routing gate enforcement.
6
+ * Thin CLI-specific entry point. Parses input, wires dependencies, and calls
7
+ * the shared pre-tool-orchestrator (see scripts/hooks/core/pre-tool-orchestrator.js).
9
8
  *
10
- * v4.0: Added scope gating to validate edits are within task's declared scope
11
- * v6.0: Added routing gate blocks Bash before /wogi-* routing
9
+ * Extraction history (wf-94cc3b72 / TD-002): this file previously contained
10
+ * the full 470-line gate cascade inline. The cascade now lives in the core
11
+ * orchestrator so it can be unit-tested and reused by non-Claude-Code CLIs.
12
+ *
13
+ * v4.0: Added scope gating
14
+ * v6.0: Added routing gate
15
+ * v7.0: Orchestrator extraction (this file → ~70 LOC wiring layer)
12
16
  */
13
17
 
14
- const path = require('node:path');
18
+ 'use strict';
19
+
15
20
  const { checkScopeGate } = require('../../core/scope-gate');
16
21
  const { checkComponentReuse } = require('../../core/component-check');
17
22
  const { checkTodoWriteGate } = require('../../core/todowrite-gate');
18
23
  const { checkRoutingGate, clearRoutingPending, hasActiveTask } = require('../../core/routing-gate');
19
24
  const { checkPhaseGate } = require('../../core/phase-gate');
20
25
  const { checkCommitLogGate } = require('../../core/commit-log-gate');
21
- // Phase-read gate: enforce reading phase instruction files before mutation tools.
22
- // Uses defensive try/catch fallback (vs direct require like phase-gate/routing-gate):
23
- // this is newer code with fail-open semantics if the module is missing, the gate
24
- // silently degrades to no-op rather than crashing the entire PreToolUse hook.
26
+ const { runPreToolGates } = require('../../core/pre-tool-orchestrator');
27
+
28
+ // Defensive lazy-loaders for gates that may be absent in older installs.
29
+ // Fail-open (no-op shims) instead of crashing the entire PreToolUse hook.
25
30
  let recordPhaseRead = () => {}, checkPhaseReadGate = () => ({ blocked: false }), clearPhaseReads = () => {};
26
31
  try {
27
32
  const prg = require('../../core/phase-read-gate');
@@ -29,7 +34,7 @@ try {
29
34
  checkPhaseReadGate = prg.checkPhaseReadGate;
30
35
  clearPhaseReads = prg.clearPhaseReads;
31
36
  } catch (_err) { if (process.env.DEBUG) console.error(`[Hook] Phase-read gate not loaded: ${_err.message}`); }
32
- // F19: Lazy-load enforcement gates with try/catch to prevent one broken gate from crashing all hooks
37
+
33
38
  const _noop = () => ({ allowed: true, blocked: false });
34
39
  let checkDeployGate = _noop, checkWriteBlock = _noop;
35
40
  try { const dg = require('../../core/deploy-gate'); checkDeployGate = dg.checkDeployGate; checkWriteBlock = dg.checkWriteBlock; } catch (_err) { if (process.env.DEBUG) console.error(`[Hook] Deploy gate not loaded: ${_err.message}`); }
@@ -43,20 +48,21 @@ let checkGitSafety = _noop;
43
48
  try { checkGitSafety = require('../../core/git-safety-gate').checkGitSafety; } catch (_err) { if (process.env.DEBUG) console.error(`[Hook] Git safety gate not loaded: ${_err.message}`); }
44
49
  let checkManagerBoundary = _noop;
45
50
  try { checkManagerBoundary = require('../../core/manager-boundary-gate').checkManagerBoundary; } catch (_err) { if (process.env.DEBUG) console.error(`[Hook] Manager boundary gate not loaded: ${_err.message}`); }
51
+
46
52
  const { claudeCodeAdapter } = require('../../adapters/claude-code');
47
53
  const { markSkillPending } = require('../../../flow-durable-session');
48
54
  const { getConfig } = require('../../../flow-utils');
49
55
  const { readHookStatus } = require('../../../flow-hook-status');
50
56
  const { runHook } = require('../shared/hook-runner');
51
57
 
52
- // Lazy-load strict adherence to avoid circular deps and startup cost
58
+ // Lazy-load strict adherence (avoids circular deps + startup cost).
53
59
  let _strictAdherence = null;
54
60
  function getStrictAdherence() {
55
61
  if (!_strictAdherence) {
56
62
  try {
57
63
  _strictAdherence = require('../../../flow-strict-adherence');
58
- } catch (err) {
59
- _strictAdherence = { isEnabled: () => false, validateCommand: () => ({ valid: true }) };
64
+ } catch (_err) {
65
+ _strictAdherence = { isEnabled: () => false, validateCommand: () => ({ valid: true }), validateFileName: () => ({ valid: true }) };
60
66
  }
61
67
  }
62
68
  return _strictAdherence;
@@ -65,492 +71,42 @@ function getStrictAdherence() {
65
71
  runHook('PreToolUse', async ({ input, parsedInput }) => {
66
72
  const hookStart = process.hrtime.bigint();
67
73
 
68
- // Handle empty or invalid input gracefully
74
+ // Empty input allow through
69
75
  if (!input || Object.keys(input).length === 0) {
70
76
  return { __raw: true, continue: true, hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow' } };
71
77
  }
72
78
 
73
- const toolName = parsedInput.toolName;
74
- const toolInput = parsedInput.toolInput || {};
75
- const filePath = toolInput.file_path;
76
-
77
- // Agent-aware gating: detect subagent context from hook event fields
78
- const rawAgentId = input.agent_id || null;
79
- const rawAgentType = input.agent_type || null;
80
-
81
- const VALID_AGENT_TYPES = new Set([
82
- 'general-purpose', 'Explore', 'Plan', 'code-reviewer', 'bug-analyzer',
83
- 'statusline-setup', 'claude-code-guide', 'ui-sketcher'
84
- ]);
85
- const agentId = (typeof rawAgentId === 'string' && /^[a-zA-Z0-9_-]{1,128}$/.test(rawAgentId)) ? rawAgentId : null;
86
- const agentType = (typeof rawAgentType === 'string' && VALID_AGENT_TYPES.has(rawAgentType)) ? rawAgentType : null;
87
- const isSubagent = !!agentId;
88
-
89
- const readOnlyAgentTypes = new Set(['Explore', 'Plan', 'code-reviewer', 'bug-analyzer']);
90
- const subagentReadOnly = isSubagent && agentType ? readOnlyAgentTypes.has(agentType) : false;
91
-
92
- // Fast path: read pre-computed hook status
93
- const hookStatus = readHookStatus();
94
- if (hookStatus && hookStatus.enforcement) {
95
- const enf = hookStatus.enforcement;
96
- const allGatesDisabled = enf.taskGating === false && enf.scopeGating === false
97
- && enf.routingGate === false && enf.commitLogGate === false
98
- && enf.todoWriteGate === false && enf.loopEnforcement === false
99
- && enf.deployGate === false && enf.strikeEscalation === false
100
- && enf.bugfixScope === false && enf.scopeMutation === false
101
- && enf.gitSafety === false
102
- && hookStatus.componentReuse === false && hookStatus.phaseGate === false
103
- && hookStatus.phaseReadGate === false;
104
- if (allGatesDisabled) {
105
- if (process.env.DEBUG) {
106
- const elapsed = Number(process.hrtime.bigint() - hookStart) / 1e6;
107
- console.error(`[Hook] PreToolUse fast-path: ${elapsed.toFixed(1)}ms`);
108
- }
109
- return { __raw: true, continue: true, hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow' } };
110
- }
111
- }
112
-
113
- // Load config ONCE
114
- let config;
115
- try {
116
- config = getConfig();
117
- } catch (err) {
118
- if (process.env.DEBUG) console.error(`[Hook] Config load error: ${err.message}`);
119
- config = null;
120
- }
121
-
122
- let coreResult = { allowed: true, blocked: false };
123
-
124
- // Point A: Phase-read recording — track when AI reads a phase instruction file
125
- if (toolName === 'Read' && filePath) {
126
- try {
127
- recordPhaseRead(filePath);
128
- } catch (_err) { /* fail-open */ }
129
- }
130
-
131
- // Phase gate check
132
- const isReadTool = ['Read', 'Glob', 'Grep', 'WebSearch', 'WebFetch'].includes(toolName);
133
- const skipPhaseGateForSubagent = isSubagent && subagentReadOnly && isReadTool;
134
-
135
- if (!skipPhaseGateForSubagent) {
136
- try {
137
- const phaseResult = checkPhaseGate(toolName, toolInput, config);
138
- if (phaseResult.blocked) {
139
- coreResult = { allowed: false, blocked: true, reason: phaseResult.reason, message: phaseResult.message };
140
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
141
- return { __raw: true, ...output };
142
- }
143
- } catch (err) {
144
- if (process.env.DEBUG) console.error(`[Hook] Phase gate error (fail-open): ${err.message}`);
145
- }
146
- }
147
-
148
- // Point B: Phase-read gate — block Edit/Write/Bash until phase file is read
149
- if (toolName === 'Edit' || toolName === 'Write' || toolName === 'Bash') {
150
- try {
151
- const readGateResult = checkPhaseReadGate(toolName, config);
152
- if (readGateResult.blocked) {
153
- coreResult = {
154
- allowed: false,
155
- blocked: true,
156
- reason: `Phase-read gate: phase file not read`,
157
- message: readGateResult.message
158
- };
159
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
160
- return { __raw: true, ...output };
161
- }
162
- } catch (_err) {
163
- // Fail-open: phase-read gate errors should not block normal work
164
- if (process.env.DEBUG) {
165
- console.error(`[Hook] Phase-read gate error (fail-open): ${_err.message}`);
166
- }
167
- }
168
- }
169
-
170
- // Task + scope gating check (for Edit and Write)
171
- if (toolName === 'Edit' || toolName === 'Write') {
172
- coreResult = checkScopeGate({
173
- filePath,
174
- operation: toolName.toLowerCase()
175
- }, config);
176
-
177
- if (coreResult.blocked) {
178
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
179
- return { __raw: true, ...output };
180
- }
181
- }
182
-
183
- // TodoWrite gating check
184
- if (toolName === 'TodoWrite') {
185
- const todos = toolInput.todos || [];
186
- coreResult = checkTodoWriteGate({ todos }, config);
187
-
188
- if (coreResult.blocked) {
189
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
190
- return { __raw: true, ...output };
191
- }
192
- }
193
-
194
- // v4.1: Skill execution tracking
195
- if (toolName === 'Skill') {
196
- const skillName = toolInput.skill;
197
- if (typeof skillName === 'string' && /^wogi-(bulk|start)$/i.test(skillName)) {
198
- markSkillPending(skillName.toLowerCase(), { args: toolInput.args });
199
- // Clear phase reads on new task start (fresh slate for phase-read gate)
200
- try { clearPhaseReads(); } catch (_err) { /* fail-open */ }
201
- if (process.env.DEBUG) {
202
- console.error(`[Hook] Marked skill ${skillName} as pending (via Skill tool)`);
203
- }
204
- }
205
-
206
- // v6.0: Clear routing-pending flag on ANY /wogi-* skill invocation
207
- if (typeof skillName === 'string' && /^wogi-/i.test(skillName)) {
208
- try {
209
- clearRoutingPending();
210
- if (process.env.DEBUG) {
211
- console.error(`[Hook] Cleared routing-pending flag (Skill: ${skillName})`);
212
- }
213
- } catch (err) {
214
- if (process.env.DEBUG) {
215
- console.error(`[Hook] Failed to clear routing flag: ${err.message}`);
216
- }
217
- }
218
- }
219
- }
220
-
221
- // v6.0: Routing gate
222
- const skipRoutingGateForSubagent = isSubagent && hasActiveTask();
223
-
224
- let skipRoutingGateForReadOnlyGit = false;
225
- if (toolName === 'Bash' && typeof toolInput.command === 'string') {
226
- const cmd = toolInput.command.trim();
227
- const READ_ONLY_GIT_PREFIXES = [
228
- 'git status', 'git log', 'git diff', 'git branch',
229
- 'git show', 'git rev-parse', 'git remote -v', 'git tag -l',
230
- 'git ls-files', 'git describe'
231
- ];
232
- const SHELL_CHAIN_OPERATORS = /[;&|`$()\n\r\\]/;
233
- const DESTRUCTIVE_GIT_FLAGS = /\s-[dD]\b|\s--delete\b|\s--force\b|\s--hard\b|\s--prune\b/;
234
- if (
235
- READ_ONLY_GIT_PREFIXES.some(prefix => cmd.startsWith(prefix)) &&
236
- !SHELL_CHAIN_OPERATORS.test(cmd) &&
237
- !DESTRUCTIVE_GIT_FLAGS.test(cmd)
238
- ) {
239
- skipRoutingGateForReadOnlyGit = true;
240
- }
241
- }
242
-
243
- 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')) {
244
- try {
245
- const routingResult = checkRoutingGate(toolName, config);
246
- if (routingResult.blocked) {
247
- coreResult = {
248
- allowed: false,
249
- blocked: true,
250
- reason: `Routing gate: ${routingResult.reason}`,
251
- message: routingResult.message
252
- };
253
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
254
- return { __raw: true, ...output };
255
- }
256
- } catch (err) {
257
- // Fail-CLOSED for routing gate
258
- if (process.env.DEBUG) {
259
- console.error(`[Hook] Routing gate error (fail-closed): ${err.message}`);
260
- }
261
- coreResult = {
262
- allowed: false,
263
- blocked: true,
264
- reason: `Routing gate error: ${err.message}`,
265
- message: 'Routing gate check failed. Please invoke /wogi-start first. Use Skill(skill="wogi-start", args="<your request>").'
266
- };
267
- const errOutput = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
268
- return { __raw: true, ...errOutput };
269
- }
270
- }
271
-
272
- // Manager role boundary gate — blocks modifications in worker repos
273
- // Runs early: role boundaries should be enforced before other gates
274
- if (process.env.WOGI_REPO_NAME === 'manager') {
275
- try {
276
- const boundaryResult = checkManagerBoundary(toolName, toolInput);
277
- if (boundaryResult.blocked) {
278
- coreResult = {
279
- allowed: false,
280
- blocked: true,
281
- reason: boundaryResult.reason,
282
- message: boundaryResult.message
283
- };
284
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
285
- return { __raw: true, ...output };
286
- }
287
- } catch (err) {
288
- // Fail-open: manager boundary errors should not block normal work
289
- if (process.env.DEBUG) {
290
- console.error(`[Hook] Manager boundary gate error (fail-open): ${err.message}`);
291
- }
292
- }
293
- }
294
-
295
- // Commit log gate check
296
- if (toolName === 'Bash' && toolInput.command) {
297
- try {
298
- const commitLogResult = checkCommitLogGate(toolInput.command, config);
299
- if (commitLogResult.blocked) {
300
- coreResult = {
301
- allowed: false,
302
- blocked: true,
303
- reason: `Commit log gate: ${commitLogResult.reason}`,
304
- message: commitLogResult.message
305
- };
306
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
307
- return { __raw: true, ...output };
308
- }
309
- } catch (err) {
310
- if (process.env.DEBUG) {
311
- console.error(`[Hook] Commit log gate error (fail-open): ${err.message}`);
312
- }
313
- }
314
- }
315
-
316
- // Deploy gate check (for Bash commands — blocks deploy without verification artifact)
317
- if (toolName === 'Bash' && toolInput.command) {
318
- try {
319
- const deployResult = checkDeployGate(toolInput.command, config);
320
- if (deployResult.blocked) {
321
- coreResult = {
322
- allowed: false,
323
- blocked: true,
324
- reason: `Deploy gate: ${deployResult.reason}`,
325
- message: deployResult.message
326
- };
327
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
328
- return { __raw: true, ...output };
329
- }
330
- } catch (err) {
331
- // Fail-open: deploy gate errors should not block normal work
332
- if (process.env.DEBUG) {
333
- console.error(`[Hook] Deploy gate error (fail-open): ${err.message}`);
334
- }
335
- }
336
- }
337
-
338
- // Deploy gate: block Write to verification artifacts (anti-forgery)
339
- if ((toolName === 'Write' || toolName === 'Edit') && filePath) {
340
- try {
341
- const writeBlockResult = checkWriteBlock(filePath, config);
342
- if (writeBlockResult.blocked) {
343
- coreResult = {
344
- allowed: false,
345
- blocked: true,
346
- reason: `Deploy gate: ${writeBlockResult.reason}`,
347
- message: writeBlockResult.message
348
- };
349
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
350
- return { __raw: true, ...output };
351
- }
352
- } catch (err) {
353
- if (process.env.DEBUG) {
354
- console.error(`[Hook] Deploy gate write-block error (fail-open): ${err.message}`);
355
- }
356
- }
357
- }
358
-
359
- // Scope mutation guard (fix tasks creating files, deleting pre-existing files)
360
- if (toolName === 'Write' || toolName === 'Bash') {
361
- try {
362
- const scopeMutResult = checkScopeMutation(toolName, toolInput, config);
363
- if (scopeMutResult.blocked) {
364
- coreResult = {
365
- allowed: false, blocked: true,
366
- reason: `Scope mutation: ${scopeMutResult.reason}`,
367
- message: scopeMutResult.message
368
- };
369
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
370
- return { __raw: true, ...output };
371
- }
372
- } catch (err) {
373
- if (process.env.DEBUG) console.error(`[Hook] Scope mutation gate error (fail-open): ${err.message}`);
374
- }
375
- }
376
-
377
- // Git safety net (auto-backup before destructive git operations)
378
- if (toolName === 'Bash' && toolInput.command && /git\s+(reset|checkout\s+(--\s+)?[\.\-]|restore\s+.*\.|clean\s+.*-f)/.test(toolInput.command)) {
379
- try {
380
- const gitResult = checkGitSafety(toolInput.command, config);
381
- if (gitResult.blocked) {
382
- coreResult = {
383
- allowed: false, blocked: true,
384
- reason: `Git safety: ${gitResult.reason}`,
385
- message: gitResult.message
386
- };
387
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
388
- return { __raw: true, ...output };
389
- }
390
- } catch (err) {
391
- if (process.env.DEBUG) console.error(`[Hook] Git safety gate error (fail-open): ${err.message}`);
392
- }
393
- }
394
-
395
- // Bugfix scope gate (warns/blocks L3 bugfixes after 3+ unique file edits)
396
- if (toolName === 'Edit' || toolName === 'Write') {
397
- try {
398
- const scopeResult = checkBugfixScope(toolName, config);
399
- if (scopeResult.blocked) {
400
- coreResult = {
401
- allowed: false,
402
- blocked: true,
403
- reason: `Bugfix scope: ${scopeResult.reason}`,
404
- message: scopeResult.message
405
- };
406
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
407
- return { __raw: true, ...output };
408
- }
409
- } catch (err) {
410
- if (process.env.DEBUG) {
411
- console.error(`[Hook] Bugfix scope gate error (fail-open): ${err.message}`);
412
- }
413
- }
414
- }
415
-
416
- // Strike escalation gate (blocks Edit/Write/Bash after repeated verification failures)
417
- if (toolName === 'Edit' || toolName === 'Write' || toolName === 'Bash') {
418
- try {
419
- const strikeResult = checkStrikeGate(toolName, config);
420
- if (strikeResult.blocked) {
421
- coreResult = {
422
- allowed: false,
423
- blocked: true,
424
- reason: `Strike gate: ${strikeResult.reason}`,
425
- message: strikeResult.message
426
- };
427
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
428
- return { __raw: true, ...output };
429
- }
430
- } catch (err) {
431
- // Fail-open: strike gate errors should not block normal work
432
- if (process.env.DEBUG) {
433
- console.error(`[Hook] Strike gate error (fail-open): ${err.message}`);
434
- }
435
- }
436
- }
437
-
438
- // Strict adherence check (for Bash commands)
439
- if (toolName === 'Bash') {
440
- const command = toolInput.command;
441
- if (command) {
442
- const strictAdherence = getStrictAdherence();
443
- if (strictAdherence.isEnabled()) {
444
- const cmdResult = strictAdherence.validateCommand(command);
445
- if (cmdResult.blocked) {
446
- coreResult = {
447
- allowed: false,
448
- blocked: true,
449
- reason: `Strict adherence: ${cmdResult.reason}`,
450
- message: cmdResult.autoCorrect
451
- ? `⚠️ BLOCKED: ${cmdResult.reason}\n\n✅ Auto-correcting to: ${cmdResult.autoCorrect}`
452
- : `⚠️ BLOCKED: ${cmdResult.reason}\n\n💡 ${cmdResult.suggestion || 'Please use the correct pattern.'}`
453
- };
454
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
455
- return { __raw: true, ...output };
456
- }
457
- }
458
- }
459
- }
460
-
461
- // Damage control check (for Bash commands)
462
- if (toolName === 'Bash' && toolInput.command && config?.damageControl?.enabled) {
463
- try {
464
- const dc = require('../../../flow-damage-control');
465
- const dcResult = dc.checkBashEvent(toolInput.command);
466
- if (dcResult && dcResult.action === 'block') {
467
- coreResult = {
468
- allowed: false,
469
- blocked: true,
470
- reason: `Damage control: ${dcResult.reason || dcResult.message || 'blocked by rule'}`,
471
- message: `\u26d4 BLOCKED by damage control: ${dcResult.message || dcResult.reason || 'This command matches a blocked pattern.'}\n\nRule: ${dcResult.rule || 'unknown'}`
472
- };
473
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
474
- return { __raw: true, ...output };
475
- }
476
- if (dcResult && dcResult.action === 'ask') {
477
- coreResult = {
478
- allowed: true,
479
- blocked: false,
480
- reason: `Damage control warning: ${dcResult.reason || dcResult.message || 'requires confirmation'}`,
481
- message: `\u26a0\ufe0f Damage control: ${dcResult.message || dcResult.reason || 'This command matches an ask-before-execute pattern.'}`
482
- };
483
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
484
- return { __raw: true, ...output };
485
- }
486
- } catch (err) {
487
- if (process.env.DEBUG) {
488
- console.error(`[Hook] Damage control error (fail-open): ${err.message}`);
489
- }
490
- }
491
- }
492
-
493
- // Damage control check (for file operations)
494
- if ((toolName === 'Edit' || toolName === 'Write') && filePath && config?.damageControl?.enabled && config?.damageControl?.events?.file) {
495
- try {
496
- const dc = require('../../../flow-damage-control');
497
- const dcResult = dc.checkFileEvent(filePath, toolName.toLowerCase());
498
- if (dcResult && dcResult.action === 'block') {
499
- coreResult = {
500
- allowed: false,
501
- blocked: true,
502
- reason: `Damage control: ${dcResult.reason || 'file access blocked'}`,
503
- message: `\u26d4 BLOCKED by damage control: ${dcResult.message || 'This file is protected.'}`
504
- };
505
- const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
506
- return { __raw: true, ...output };
507
- }
508
- } catch (err) {
509
- if (process.env.DEBUG) {
510
- console.error(`[Hook] Damage control file check error (fail-open): ${err.message}`);
511
- }
79
+ const deps = {
80
+ // Gates
81
+ checkScopeGate, checkComponentReuse, checkTodoWriteGate,
82
+ checkRoutingGate, clearRoutingPending, hasActiveTask,
83
+ checkPhaseGate, checkCommitLogGate,
84
+ recordPhaseRead, checkPhaseReadGate, clearPhaseReads,
85
+ checkDeployGate, checkWriteBlock,
86
+ checkStrikeGate, checkBugfixScope, checkScopeMutation,
87
+ checkGitSafety, checkManagerBoundary,
88
+ // Side-effect helpers
89
+ markSkillPending,
90
+ // Config + runtime
91
+ getConfig, readHookStatus, getStrictAdherence,
92
+ };
93
+
94
+ const coreResult = runPreToolGates({ input, parsedInput }, deps);
95
+
96
+ // Fast path (no transform needed short-circuit to allow)
97
+ if (coreResult._fastPath) {
98
+ if (process.env.DEBUG) {
99
+ const elapsed = Number(process.hrtime.bigint() - hookStart) / 1e6;
100
+ console.error(`[Hook] PreToolUse fast-path: ${elapsed.toFixed(1)}ms`);
512
101
  }
102
+ return { __raw: true, continue: true, hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow' } };
513
103
  }
514
104
 
515
- // Component reuse check (for Write only)
516
- if (toolName === 'Write' && filePath) {
517
- const componentResult = checkComponentReuse({
518
- filePath,
519
- content: toolInput.content
520
- }, config);
521
-
522
- if (componentResult.blocked || componentResult.warning) {
523
- coreResult = {
524
- ...coreResult,
525
- ...componentResult,
526
- allowed: !componentResult.blocked,
527
- blocked: componentResult.blocked
528
- };
529
- }
530
-
531
- // Strict adherence: File naming check (for Write)
532
- if (!coreResult.blocked) {
533
- const strictAdherence = getStrictAdherence();
534
- if (strictAdherence.isEnabled()) {
535
- const isComponent = /\/(components?|ui)\//i.test(filePath) && /\.(tsx|jsx)$/i.test(filePath);
536
- const isApi = /\/(api|routes)\//i.test(filePath);
537
- const fileType = isComponent ? 'component' : isApi ? 'api' : 'generic';
538
-
539
- const fileName = path.basename(filePath);
540
- const fileResult = strictAdherence.validateFileName(fileName, fileType);
541
- if (fileResult.blocked) {
542
- coreResult = {
543
- allowed: false,
544
- blocked: true,
545
- reason: `Strict adherence: ${fileResult.reason}`,
546
- message: `⚠️ BLOCKED: ${fileResult.reason}\n\n💡 ${fileResult.suggestion || 'Please use the correct naming convention.'}`
547
- };
548
- }
549
- }
550
- }
105
+ if (coreResult.blocked) {
106
+ const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
107
+ return { __raw: true, ...output };
551
108
  }
552
109
 
553
- // Benchmark: log hook latency when DEBUG is enabled
554
110
  if (process.env.DEBUG) {
555
111
  const elapsed = Number(process.hrtime.bigint() - hookStart) / 1e6;
556
112
  console.error(`[Hook] PreToolUse latency: ${elapsed.toFixed(1)}ms`);
@@ -66,7 +66,7 @@ async function runHook(eventName, handler, { failMode = 'silent', useStdoutWrite
66
66
  try {
67
67
  const { logHookError } = require('../../../flow-hook-errors');
68
68
  logHookError(eventName, err, { failMode: 'open', operation: eventName.toLowerCase() });
69
- } catch (_logErr) {
69
+ } catch (_err) {
70
70
  console.error(`[WogiFlow] ${eventName} hook error: ${err.message}`);
71
71
  }
72
72
  write(JSON.stringify(errorOutput));
@@ -12,7 +12,7 @@
12
12
  const fs = require('node:fs');
13
13
  const path = require('node:path');
14
14
  const { RegistryPlugin } = require('../flow-registry-manager');
15
- const { getProjectRoot, safeJsonParse: safeJsonParseFile, PATHS } = require('../flow-utils');
15
+ const { getProjectRoot, safeJsonParse: safeJsonParseFile, PATHS, getTodayDate } = require('../flow-utils');
16
16
 
17
17
  const INDEX_PATH = path.join(PATHS.state, 'schema-index.json');
18
18
  const MAP_PATH = path.join(PATHS.state, 'schema-map.md');
@@ -13,7 +13,7 @@
13
13
  const fs = require('node:fs');
14
14
  const path = require('node:path');
15
15
  const { RegistryPlugin } = require('../flow-registry-manager');
16
- const { getProjectRoot, safeJsonParse: safeJsonParseFile, PATHS } = require('../flow-utils');
16
+ const { getProjectRoot, safeJsonParse: safeJsonParseFile, PATHS, getTodayDate } = require('../flow-utils');
17
17
 
18
18
  const INDEX_PATH = path.join(PATHS.state, 'service-index.json');
19
19
  const MAP_PATH = path.join(PATHS.state, 'service-map.md');