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.
- package/.claude/commands/wogi-audit.md +212 -17
- package/.claude/commands/wogi-research.md +37 -0
- package/.claude/commands/wogi-review.md +200 -22
- package/.claude/commands/wogi-start.md +45 -0
- package/.claude/docs/claude-code-compatibility.md +46 -1
- package/.claude/docs/intent-grounded-review.md +209 -0
- package/.claude/settings.json +34 -1
- package/.workflow/agents/logic-adversary.md +8 -0
- package/.workflow/templates/claude-md.hbs +18 -0
- package/lib/installer.js +22 -0
- package/lib/utils.js +29 -3
- package/lib/workspace-changelog.js +2 -1
- package/lib/workspace-channel-server.js +4 -6
- package/lib/workspace-contracts.js +5 -4
- package/lib/workspace-events.js +8 -7
- package/lib/workspace-gates.js +4 -3
- package/lib/workspace-integration-tests.js +2 -1
- package/lib/workspace-intelligence.js +3 -2
- package/lib/workspace-locks.js +2 -1
- package/lib/workspace-messages.js +7 -6
- package/lib/workspace-routing.js +14 -26
- package/lib/workspace-session.js +7 -6
- package/lib/workspace-sync.js +9 -8
- package/package.json +4 -2
- package/scripts/base-workflow-step.js +1 -1
- package/scripts/flow +19 -0
- package/scripts/flow-adaptive-learning.js +1 -1
- package/scripts/flow-aggregate.js +2 -1
- package/scripts/flow-architect-pass.js +3 -3
- package/scripts/flow-archive-runs.js +372 -0
- package/scripts/flow-ask.js +1 -1
- package/scripts/flow-ast-grep.js +216 -0
- package/scripts/flow-audit-gates.js +1 -1
- package/scripts/flow-auto-learn.js +8 -11
- package/scripts/flow-bug.js +2 -2
- package/scripts/flow-capture-gate.js +644 -0
- package/scripts/flow-capture.js +4 -3
- package/scripts/flow-cli-flags.js +95 -0
- package/scripts/flow-community-sync.js +2 -1
- package/scripts/flow-community.js +6 -6
- package/scripts/flow-conclusion-classifier.js +310 -0
- package/scripts/flow-config-defaults.js +3 -3
- package/scripts/flow-constants.js +8 -11
- package/scripts/flow-context-scoring.js +1 -0
- package/scripts/flow-correction-detector.js +344 -3
- package/scripts/flow-damage-control.js +1 -1
- package/scripts/flow-decisions-merge.js +1 -0
- package/scripts/flow-done-gates.js +20 -0
- package/scripts/flow-done-report.js +2 -2
- package/scripts/flow-done.js +4 -4
- package/scripts/flow-epics.js +5 -11
- package/scripts/flow-health.js +145 -1
- package/scripts/flow-id.js +92 -0
- package/scripts/flow-io.js +15 -5
- package/scripts/flow-knowledge-router.js +2 -1
- package/scripts/flow-links.js +1 -1
- package/scripts/flow-log-manager.js +2 -1
- package/scripts/flow-logic-adversary.js +4 -4
- package/scripts/flow-long-input-cli.js +6 -0
- package/scripts/flow-long-input-stories.js +1 -1
- package/scripts/flow-loop-retry-learning.js +1 -1
- package/scripts/flow-mcp-capabilities.js +2 -3
- package/scripts/flow-mcp-docs.js +2 -1
- package/scripts/flow-memory-blocks.js +2 -1
- package/scripts/flow-memory-sync.js +1 -1
- package/scripts/flow-memory.js +767 -0
- package/scripts/flow-migrate-igr.js +1 -1
- package/scripts/flow-migrate.js +2 -1
- package/scripts/flow-model-adapter.js +1 -1
- package/scripts/flow-model-config.js +5 -1
- package/scripts/flow-model-profile.js +2 -1
- package/scripts/flow-orchestrate.js +3 -3
- package/scripts/flow-output.js +29 -0
- package/scripts/flow-parallel.js +10 -9
- package/scripts/flow-pattern-enforcer.js +2 -1
- package/scripts/flow-permissions-audit.js +124 -0
- package/scripts/flow-plugin-registry.js +2 -2
- package/scripts/flow-progress.js +5 -1
- package/scripts/flow-project-analyzer.js +1 -1
- package/scripts/flow-promote.js +510 -0
- package/scripts/flow-registries.js +86 -0
- package/scripts/flow-request-log.js +133 -0
- package/scripts/flow-research-protocol.js +0 -1
- package/scripts/flow-revision-tracker.js +2 -1
- package/scripts/flow-roadmap.js +2 -1
- package/scripts/flow-rules-sync.js +3 -7
- package/scripts/flow-session-end.js +3 -1
- package/scripts/flow-session-learning.js +6 -13
- package/scripts/flow-session-state.js +2 -2
- package/scripts/flow-setup-hooks.js +2 -1
- package/scripts/flow-skill-create.js +1 -1
- package/scripts/flow-skill-freshness.js +6 -7
- package/scripts/flow-skill-learn.js +1 -1
- package/scripts/flow-step-coverage.js +1 -1
- package/scripts/flow-step-security.js +1 -1
- package/scripts/flow-story.js +58 -10
- package/scripts/flow-sys.js +204 -0
- package/scripts/flow-task-hierarchy.js +88 -0
- package/scripts/flow-tech-debt.js +2 -1
- package/scripts/flow-test-api.js +1 -1
- package/scripts/flow-utils.js +60 -890
- package/scripts/hooks/core/bugfix-scope-gate.js +5 -4
- package/scripts/hooks/core/deploy-gate.js +1 -1
- package/scripts/hooks/core/pre-tool-helpers.js +72 -0
- package/scripts/hooks/core/pre-tool-orchestrator.js +442 -0
- package/scripts/hooks/core/routing-gate.js +8 -0
- package/scripts/hooks/core/session-end.js +28 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +48 -492
- package/scripts/hooks/entry/shared/hook-runner.js +1 -1
- package/scripts/registries/schema-registry.js +1 -1
- 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
|
-
*
|
|
7
|
-
*
|
|
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
|
-
*
|
|
11
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
//
|
|
24
|
-
//
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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
|
-
//
|
|
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
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
516
|
-
|
|
517
|
-
|
|
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 (
|
|
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');
|