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.
- package/.claude/commands/wogi-start.md +124 -0
- package/.claude/docs/claude-code-compatibility.md +51 -0
- package/.claude/docs/explore-agents.md +11 -0
- package/.claude/settings.json +12 -1
- package/.workflow/models/registry.json +1 -1
- package/bin/flow +11 -1
- package/lib/workspace-contracts.js +599 -0
- package/lib/workspace-intelligence.js +600 -0
- package/lib/workspace-messages.js +441 -0
- package/lib/workspace-routing.js +485 -0
- package/lib/workspace-sync.js +339 -0
- package/lib/workspace.js +1073 -0
- package/package.json +4 -4
- package/scripts/MEMORY-ARCHITECTURE.md +1 -1
- package/scripts/base-workflow-step.js +136 -0
- package/scripts/flow-adaptive-learning.js +8 -9
- package/scripts/flow-aggregate.js +11 -6
- package/scripts/flow-api-index.js +4 -6
- package/scripts/flow-assumption-detector.js +0 -2
- package/scripts/flow-audit.js +15 -2
- package/scripts/flow-auto-context.js +8 -12
- package/scripts/flow-auto-learn.js +49 -49
- package/scripts/flow-background.js +5 -6
- package/scripts/flow-bridge-state.js +8 -10
- package/scripts/flow-bulk-loop.js +1 -3
- package/scripts/flow-bulk-orchestrator.js +1 -3
- package/scripts/flow-cascade-completion.js +0 -2
- package/scripts/flow-cascade.js +4 -4
- package/scripts/flow-checkpoint.js +10 -13
- package/scripts/flow-code-intelligence.js +10 -12
- package/scripts/flow-community-sync.js +4 -4
- package/scripts/flow-community.js +12 -20
- package/scripts/flow-config-defaults.js +28 -2
- package/scripts/flow-config-interactive.js +9 -5
- package/scripts/flow-config-loader.js +49 -92
- package/scripts/flow-config-substitution.js +0 -2
- package/scripts/flow-context-estimator.js +4 -4
- package/scripts/flow-context-init.js +10 -12
- package/scripts/flow-context-manager.js +0 -2
- package/scripts/flow-context-scoring.js +2 -2
- package/scripts/flow-contract-scan.js +6 -9
- package/scripts/flow-correct.js +29 -27
- package/scripts/flow-correction-detector.js +5 -1
- package/scripts/flow-damage-control.js +47 -54
- package/scripts/flow-decisions-merge.js +4 -14
- package/scripts/flow-diff.js +5 -8
- package/scripts/flow-done-gates.js +786 -0
- package/scripts/flow-done-report.js +123 -0
- package/scripts/flow-done.js +71 -717
- package/scripts/flow-entropy-monitor.js +1 -3
- package/scripts/flow-eval-calibration.js +257 -0
- package/scripts/flow-eval-judge.js +10 -1
- package/scripts/flow-eval.js +14 -5
- package/scripts/flow-extraction-review.js +1 -0
- package/scripts/flow-failure-categories.js +0 -2
- package/scripts/flow-figma-confirm.js +5 -9
- package/scripts/flow-figma-generate.js +8 -10
- package/scripts/flow-figma-index.js +8 -10
- package/scripts/flow-figma-match.js +3 -5
- package/scripts/flow-figma-mcp-server.js +2 -4
- package/scripts/flow-figma-orchestrator.js +2 -3
- package/scripts/flow-figma-registry.js +2 -3
- package/scripts/flow-framework-resolver.js +0 -2
- package/scripts/flow-function-index.js +4 -6
- package/scripts/flow-gate-confidence.js +2 -2
- package/scripts/flow-gitignore.js +0 -2
- package/scripts/flow-guided-edit.js +5 -6
- package/scripts/flow-health.js +5 -6
- package/scripts/flow-hook-errors.js +6 -0
- package/scripts/flow-hook-status.js +263 -0
- package/scripts/flow-hooks.js +17 -29
- package/scripts/flow-http-client.js +9 -8
- package/scripts/flow-hybrid-interactive.js +7 -12
- package/scripts/flow-hybrid-test.js +12 -13
- package/scripts/flow-instruction-richness.js +1 -1
- package/scripts/flow-io.js +21 -4
- package/scripts/flow-knowledge-router.js +9 -3
- package/scripts/flow-learning-orchestrator.js +318 -13
- package/scripts/flow-links.js +5 -7
- package/scripts/flow-long-input-association.js +275 -0
- package/scripts/flow-long-input-chunking.js +1 -0
- package/scripts/flow-long-input-cli.js +0 -2
- package/scripts/flow-long-input-complexity.js +0 -2
- package/scripts/flow-long-input-constants.js +0 -2
- package/scripts/flow-long-input-contradictions.js +351 -0
- package/scripts/flow-long-input-detection.js +0 -2
- package/scripts/flow-long-input-passes.js +885 -0
- package/scripts/flow-long-input-stories.js +1 -1
- package/scripts/flow-long-input-voice.js +0 -2
- package/scripts/flow-long-input.js +425 -3005
- package/scripts/flow-loop-retry-learning.js +2 -3
- package/scripts/flow-lsp.js +3 -3
- package/scripts/flow-mcp-docs.js +3 -4
- package/scripts/flow-memory-db.js +6 -8
- package/scripts/flow-memory-sync.js +18 -11
- package/scripts/flow-metrics.js +1 -2
- package/scripts/flow-model-adapter.js +2 -3
- package/scripts/flow-model-config.js +72 -104
- package/scripts/flow-model-router.js +2 -2
- package/scripts/flow-model-types.js +0 -2
- package/scripts/flow-multi-approach.js +5 -6
- package/scripts/flow-orchestrate-context.js +3 -7
- package/scripts/flow-orchestrate-rollback.js +3 -8
- package/scripts/flow-orchestrate-state.js +8 -14
- package/scripts/flow-orchestrate-templates.js +2 -6
- package/scripts/flow-orchestrate-validator.js +5 -9
- package/scripts/flow-orchestrate.js +126 -103
- package/scripts/flow-output.js +0 -2
- package/scripts/flow-parallel.js +1 -1
- package/scripts/flow-paths.js +23 -2
- package/scripts/flow-pattern-enforcer.js +30 -28
- package/scripts/flow-pattern-extractor.js +3 -4
- package/scripts/flow-pending.js +0 -2
- package/scripts/flow-permissions.js +2 -3
- package/scripts/flow-plugin-registry.js +10 -12
- package/scripts/flow-prd-manager.js +1 -1
- package/scripts/flow-progress.js +7 -9
- package/scripts/flow-prompt-composer.js +3 -3
- package/scripts/flow-prompt-template.js +2 -2
- package/scripts/flow-providers.js +7 -4
- package/scripts/flow-registry-manager.js +7 -12
- package/scripts/flow-regression.js +9 -11
- package/scripts/flow-roadmap.js +2 -2
- package/scripts/flow-run-trace.js +16 -15
- package/scripts/flow-safety.js +2 -5
- package/scripts/flow-scanner-base.js +5 -7
- package/scripts/flow-scenario-engine.js +1 -5
- package/scripts/flow-security.js +29 -0
- package/scripts/flow-session-end.js +32 -41
- package/scripts/flow-session-learning.js +53 -49
- package/scripts/flow-setup-hooks.js +2 -3
- package/scripts/flow-skill-create.js +7 -12
- package/scripts/flow-skill-generator.js +12 -16
- package/scripts/flow-skill-learn.js +17 -8
- package/scripts/flow-skill-matcher.js +1 -2
- package/scripts/flow-spec-generator.js +2 -4
- package/scripts/flow-stack-wizard.js +5 -7
- package/scripts/flow-standards-learner.js +35 -16
- package/scripts/flow-start.js +2 -0
- package/scripts/flow-stats-collector.js +2 -2
- package/scripts/flow-status.js +10 -10
- package/scripts/flow-statusline-setup.js +2 -2
- package/scripts/flow-step-changelog.js +2 -3
- package/scripts/flow-step-comments.js +66 -81
- package/scripts/flow-step-complexity.js +50 -70
- package/scripts/flow-step-coverage.js +3 -5
- package/scripts/flow-step-knowledge.js +2 -3
- package/scripts/flow-step-pr-tests.js +64 -74
- package/scripts/flow-step-regression.js +3 -5
- package/scripts/flow-step-review.js +86 -103
- package/scripts/flow-step-security.js +111 -121
- package/scripts/flow-step-silent-failures.js +56 -83
- package/scripts/flow-step-simplifier.js +52 -70
- package/scripts/flow-story.js +4 -7
- package/scripts/flow-strict-adherence.js +3 -4
- package/scripts/flow-task-checkpoint.js +36 -5
- package/scripts/flow-task-enforcer.js +2 -24
- package/scripts/flow-tech-debt.js +1 -1
- package/scripts/flow-template-extractor.js +1 -0
- package/scripts/flow-templates.js +11 -13
- package/scripts/flow-test-api.js +9 -13
- package/scripts/flow-test-discovery.js +1 -1
- package/scripts/flow-test-generate.js +5 -9
- package/scripts/flow-test-integrity.js +3 -7
- package/scripts/flow-test-ui.js +5 -9
- package/scripts/flow-testing-deps.js +1 -3
- package/scripts/flow-tiered-learning.js +4 -4
- package/scripts/flow-todowrite-sync.js +1 -1
- package/scripts/flow-tokens.js +0 -2
- package/scripts/flow-verification-profile.js +6 -10
- package/scripts/flow-verify.js +12 -16
- package/scripts/flow-version-check.js +4 -12
- package/scripts/flow-webmcp-generator.js +3 -5
- package/scripts/flow-workflow-steps.js +0 -2
- package/scripts/flow-workflow.js +9 -11
- package/scripts/hooks/adapters/claude-code.js +31 -0
- package/scripts/hooks/core/config-change.js +1 -0
- package/scripts/hooks/core/extension-registry.js +0 -2
- package/scripts/hooks/core/instructions-loaded.js +1 -1
- package/scripts/hooks/core/observation-capture.js +5 -5
- package/scripts/hooks/core/phase-gate.js +5 -0
- package/scripts/hooks/core/post-compact.js +1 -12
- package/scripts/hooks/core/research-gate.js +2 -12
- package/scripts/hooks/core/routing-gate.js +6 -0
- package/scripts/hooks/core/task-completed.js +12 -0
- package/scripts/hooks/core/task-created.js +83 -0
- package/scripts/hooks/core/worktree-lifecycle.js +1 -1
- package/scripts/hooks/entry/claude-code/config-change.js +6 -29
- package/scripts/hooks/entry/claude-code/instructions-loaded.js +5 -30
- package/scripts/hooks/entry/claude-code/post-compact.js +4 -31
- package/scripts/hooks/entry/claude-code/post-tool-use.js +121 -172
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +260 -361
- package/scripts/hooks/entry/claude-code/session-end.js +4 -28
- package/scripts/hooks/entry/claude-code/session-start.js +205 -243
- package/scripts/hooks/entry/claude-code/setup.js +8 -49
- package/scripts/hooks/entry/claude-code/stop.js +40 -72
- package/scripts/hooks/entry/claude-code/task-completed.js +4 -28
- package/scripts/hooks/entry/claude-code/task-created.js +15 -0
- package/scripts/hooks/entry/claude-code/user-prompt-submit.js +113 -195
- package/scripts/hooks/entry/claude-code/worktree-create.js +6 -25
- package/scripts/hooks/entry/claude-code/worktree-remove.js +6 -25
- package/scripts/hooks/entry/shared/hook-runner.js +99 -0
- package/scripts/hooks/entry/shared/read-stdin.js +0 -2
- package/scripts/postinstall.js +2 -0
- package/scripts/registries/api-registry.js +0 -2
- package/scripts/registries/component-registry.js +5 -9
- package/scripts/registries/contract-scanner.js +2 -9
- package/scripts/registries/function-registry.js +0 -2
- package/scripts/registries/schema-registry.js +14 -18
- 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 {
|
|
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
|
|
41
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
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
|
-
|
|
214
|
-
if (
|
|
215
|
-
|
|
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]
|
|
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
|
|
237
|
-
message:
|
|
190
|
+
reason: `Routing gate: ${routingResult.reason}`,
|
|
191
|
+
message: routingResult.message
|
|
238
192
|
};
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
process.exit(0);
|
|
242
|
-
return;
|
|
193
|
+
const output = claudeCodeAdapter.transformResult('PreToolUse', coreResult);
|
|
194
|
+
return { __raw: true, ...output };
|
|
243
195
|
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
if (
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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: `
|
|
310
|
-
message:
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
382
|
-
|
|
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
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
-
//
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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' });
|