wogiflow 2.30.4 → 2.31.1
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-self-adversary.md +130 -0
- package/.claude/docs/config-schema.md +219 -0
- package/package.json +2 -2
- package/scripts/flow-defer-auth.js +41 -10
- package/scripts/flow-deferral-classifier-ai.js +3 -1
- package/scripts/flow-impl-question-classifier.js +178 -0
- package/scripts/flow-self-adversary-loop.js +422 -0
- package/scripts/flow-standards-gate.js +3 -1
- package/scripts/hooks/core/deferral-classifier.js +3 -0
- package/scripts/hooks/core/deferral-gate.js +6 -3
- package/scripts/hooks/core/gate-orchestrator.js +26 -1
- package/scripts/hooks/core/pre-tool-deps.js +11 -0
- package/scripts/hooks/core/pre-tool-orchestrator.js +21 -0
- package/scripts/hooks/core/self-adversary-gate.js +295 -0
- package/scripts/hooks/core/session-start-orchestrator.js +269 -0
- package/scripts/hooks/core/stop-orchestrator.js +123 -0
- package/scripts/hooks/core/task-boundary-restart-coordinator.js +84 -0
- package/scripts/hooks/core/user-prompt-orchestrator.js +201 -0
- package/scripts/hooks/core/workspace-stop-gates.js +133 -0
- package/scripts/hooks/core/workspace-stop-notify.js +76 -0
- package/scripts/hooks/entry/claude-code/session-start.js +19 -352
- package/scripts/hooks/entry/claude-code/stop.js +10 -485
- package/scripts/hooks/entry/claude-code/user-prompt-submit.js +9 -277
|
@@ -1,289 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Wogi Flow - Claude Code UserPromptSubmit Hook
|
|
4
|
+
* Wogi Flow - Claude Code UserPromptSubmit Hook (thin entry)
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* All UserPromptSubmit business logic lives in
|
|
7
|
+
* scripts/hooks/core/user-prompt-orchestrator.js. This entry dispatches.
|
|
8
|
+
*
|
|
9
|
+
* Per .claude/rules/architecture/hook-three-layer.md: entry files ≤ 120 LOC,
|
|
10
|
+
* ≤ 2 core/ imports, no inline business logic. wf-6e31850e A-3 extracted
|
|
11
|
+
* the prior 293-LOC body into core/user-prompt-orchestrator.js.
|
|
8
12
|
*/
|
|
9
13
|
|
|
10
|
-
const
|
|
11
|
-
const { checkImplementationGate } = require('../../core/implementation-gate');
|
|
12
|
-
const { checkResearchRequirement } = require('../../core/research-gate');
|
|
13
|
-
const { setRoutingPending, clearRoutingPending, ROUTING_CLEARED_PATH } = require('../../core/routing-gate');
|
|
14
|
-
const { getPhaseContextPrompt } = require('../../core/phase-gate');
|
|
15
|
-
const { buildOverdueContext } = require('../../core/overdue-dispatches');
|
|
16
|
-
const { getDossierInjection } = require('../../core/feature-dossier-gate');
|
|
17
|
-
const {
|
|
18
|
-
shouldForceExtractReview,
|
|
19
|
-
buildEnforcementMessage,
|
|
20
|
-
markLongInputPending
|
|
21
|
-
} = require('../../core/long-input-enforcement');
|
|
22
|
-
const { markSkillPending, loadDurableSession } = require('../../../flow-durable-session');
|
|
23
|
-
const { captureCurrentPrompt } = require('../../../flow-prompt-capture');
|
|
24
|
-
const { spawnBackgroundDetection } = require('../../../flow-correction-detector');
|
|
25
|
-
const { getConfig } = require('../../../flow-utils');
|
|
14
|
+
const { orchestrateUserPromptSubmit } = require('../../core/user-prompt-orchestrator');
|
|
26
15
|
const { runHook } = require('../shared/hook-runner');
|
|
27
16
|
|
|
28
17
|
runHook('UserPromptSubmit', async ({ input, parsedInput }) => {
|
|
29
|
-
|
|
30
|
-
if (!input || Object.keys(input).length === 0) {
|
|
31
|
-
return { __raw: true, continue: true, hookSpecificOutput: { hookEventName: 'UserPromptSubmit' } };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const prompt = parsedInput.prompt;
|
|
35
|
-
const source = parsedInput.source;
|
|
36
|
-
|
|
37
|
-
// wf-729ab5c0 follow-up — clear pending-question marker on user response.
|
|
38
|
-
// This unblocks a deferred task-boundary restart. The AI may have asked a
|
|
39
|
-
// question via `flow ask "..."` after task completion; user's response
|
|
40
|
-
// releases the deferral, and the next Stop hook will fire the restart.
|
|
41
|
-
try {
|
|
42
|
-
const { clearPendingQuestion } = require('../../../flow-ask');
|
|
43
|
-
const r = clearPendingQuestion();
|
|
44
|
-
if (r.wasPresent && process.env.DEBUG) {
|
|
45
|
-
console.error(`[UserPromptSubmit] Cleared pending-question marker — restart deferral released`);
|
|
46
|
-
}
|
|
47
|
-
} catch (_err) { /* non-fatal */ }
|
|
48
|
-
|
|
49
|
-
// v4.1: Detect skill commands that need execution tracking
|
|
50
|
-
if (typeof prompt === 'string') {
|
|
51
|
-
const skillMatch = prompt.match(/^\/(wogi-bulk|wogi-start)\b/i);
|
|
52
|
-
if (skillMatch) {
|
|
53
|
-
const skillName = skillMatch[1].toLowerCase();
|
|
54
|
-
markSkillPending(skillName, { prompt });
|
|
55
|
-
if (process.env.DEBUG) {
|
|
56
|
-
console.error(`[Hook] Marked /${skillName} as pending execution`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Load config once for feature flag checks
|
|
62
|
-
let hookConfig;
|
|
63
|
-
try {
|
|
64
|
-
hookConfig = getConfig();
|
|
65
|
-
} catch (_err) {
|
|
66
|
-
hookConfig = {};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// v5.0: Capture prompt for learning system (non-blocking)
|
|
70
|
-
if (hookConfig.hooks?.rules?.intelligence?.promptCapture?.enabled !== false) {
|
|
71
|
-
if (typeof prompt === 'string' && prompt.trim().length > 0) {
|
|
72
|
-
setImmediate(() => {
|
|
73
|
-
try {
|
|
74
|
-
captureCurrentPrompt(prompt);
|
|
75
|
-
} catch (err) {
|
|
76
|
-
if (process.env.DEBUG) {
|
|
77
|
-
console.error(`[Hook] Prompt capture failed: ${err.message}`);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// wf-5cd71b1f: Research-required classifier — detect diagnostic prompts
|
|
85
|
-
// (Tier 2/3 from CLAUDE.md routing) that require evidence-reading before
|
|
86
|
-
// the AI answers. Writes a turn-scoped marker that the Stop hook checks;
|
|
87
|
-
// if the AI answered with text-only and no Read calls against evidence
|
|
88
|
-
// paths, the Stop hook re-prompts forcing a redo. Fail-open throughout.
|
|
89
|
-
if (typeof prompt === 'string' && prompt.trim().length > 0) {
|
|
90
|
-
try {
|
|
91
|
-
const { applyClassification: applyResearchClassification } = require('../../core/research-required-classifier');
|
|
92
|
-
const r = applyResearchClassification(prompt, hookConfig);
|
|
93
|
-
if (r.applied && process.env.DEBUG) {
|
|
94
|
-
console.error(`[Hook] Research-required classifier: category=${r.category}, match="${r.match}"`);
|
|
95
|
-
}
|
|
96
|
-
} catch (err) {
|
|
97
|
-
if (process.env.DEBUG) {
|
|
98
|
-
console.error(`[Hook] Research-required classifier failed: ${err.message}`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// wf-b8839d99 (replaces wf-f9912af6 regex classifier): AI-based deferral-
|
|
104
|
-
// intent classifier. Calls Haiku to interpret the user's prompt. NEGATIVE
|
|
105
|
-
// ("fix all", "I don't like tech debt", any phrasing) writes a no-defer-pin;
|
|
106
|
-
// POSITIVE ("defer F5", "option 2", "ship as-is") writes a scoped auth
|
|
107
|
-
// marker. The marker now captures the verbatim user excerpt SEPARATELY from
|
|
108
|
-
// the AI's interpretation — ending the false-attribution failure shape.
|
|
109
|
-
// Fail-open throughout: classifier errors / missing API key → no state
|
|
110
|
-
// change (status quo holds; gate's default-restrictive behavior preserved).
|
|
111
|
-
if (typeof prompt === 'string' && prompt.trim().length > 0) {
|
|
112
|
-
try {
|
|
113
|
-
const { applyClassification } = require('../../core/deferral-classifier');
|
|
114
|
-
const r = await applyClassification(prompt, hookConfig);
|
|
115
|
-
if (r.applied && process.env.DEBUG) {
|
|
116
|
-
console.error(`[Hook] Deferral classifier (AI): intent=${r.intent}, confidence=${r.confidence}, standing=${r.standing}, scope=${JSON.stringify(r.scope)}`);
|
|
117
|
-
} else if (process.env.DEBUG && r.reason) {
|
|
118
|
-
console.error(`[Hook] Deferral classifier (AI): no-op — ${r.reason}`);
|
|
119
|
-
}
|
|
120
|
-
} catch (err) {
|
|
121
|
-
if (process.env.DEBUG) {
|
|
122
|
-
console.error(`[Hook] Deferral classifier failed: ${err.message}`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// v5.1->v7.0: Detect corrections for learning system (AI-only, non-blocking)
|
|
128
|
-
if (hookConfig.hooks?.rules?.intelligence?.correctionDetection?.enabled !== false) {
|
|
129
|
-
if (typeof prompt === 'string' && prompt.trim().length > 0) {
|
|
130
|
-
try {
|
|
131
|
-
const session = loadDurableSession();
|
|
132
|
-
spawnBackgroundDetection(prompt, session?.taskId || '');
|
|
133
|
-
} catch (err) {
|
|
134
|
-
if (process.env.DEBUG) {
|
|
135
|
-
console.error(`[Hook] Correction detection spawn failed: ${err.message}`);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// v6.0: Set routing-pending flag for routing gate enforcement
|
|
142
|
-
const isWogiCommand = typeof prompt === 'string' && /^\/wogi-[a-z0-9-]+\b/i.test(prompt.trim());
|
|
143
|
-
if (!isWogiCommand) {
|
|
144
|
-
// v8.1: Delete any stale cleared marker from previous turns.
|
|
145
|
-
try {
|
|
146
|
-
fs.unlinkSync(ROUTING_CLEARED_PATH);
|
|
147
|
-
} catch (err) {
|
|
148
|
-
if (err.code !== 'ENOENT' && process.env.DEBUG) {
|
|
149
|
-
console.error(`[Hook] Failed to delete cleared marker: ${err.message}`);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
try {
|
|
154
|
-
setRoutingPending();
|
|
155
|
-
} catch (err) {
|
|
156
|
-
if (process.env.DEBUG) {
|
|
157
|
-
console.error(`[Hook] Routing gate set failed: ${err.message}`);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
} else {
|
|
161
|
-
// v6.2: Actively CLEAR any existing routing flag when user explicitly types a /wogi-* command.
|
|
162
|
-
try {
|
|
163
|
-
clearRoutingPending();
|
|
164
|
-
if (process.env.DEBUG) {
|
|
165
|
-
console.error(`[Hook] Cleared routing flag — prompt is a /wogi-* command`);
|
|
166
|
-
}
|
|
167
|
-
} catch (err) {
|
|
168
|
-
if (process.env.DEBUG) {
|
|
169
|
-
console.error(`[Hook] Routing gate clear failed: ${err.message}`);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Phase context injection
|
|
175
|
-
let phasePrompt = null;
|
|
176
|
-
try {
|
|
177
|
-
const phaseContext = getPhaseContextPrompt();
|
|
178
|
-
if (phaseContext.inject && phaseContext.prompt) {
|
|
179
|
-
phasePrompt = phaseContext.prompt;
|
|
180
|
-
}
|
|
181
|
-
} catch (err) {
|
|
182
|
-
if (process.env.DEBUG) {
|
|
183
|
-
console.error(`[Hook] Phase context injection failed: ${err.message}`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// wf-557cf08a — Feature dossier + logic rules auto-injection.
|
|
188
|
-
// Surfaces canonical per-feature knowledge and cross-cutting logic rules
|
|
189
|
-
// into the phase prompt so Claude doesn't have to fetch them under token
|
|
190
|
-
// pressure. Fail-open: returns null on any error.
|
|
191
|
-
let dossierPrompt = null;
|
|
192
|
-
try {
|
|
193
|
-
dossierPrompt = getDossierInjection();
|
|
194
|
-
} catch (err) {
|
|
195
|
-
if (process.env.DEBUG) {
|
|
196
|
-
console.error(`[Hook] Dossier injection failed: ${err.message}`);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
if (dossierPrompt) {
|
|
200
|
-
phasePrompt = phasePrompt ? `${phasePrompt}\n\n${dossierPrompt}` : dossierPrompt;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Check research gate first (before implementation gate)
|
|
204
|
-
const researchResult = checkResearchRequirement({
|
|
205
|
-
prompt,
|
|
206
|
-
source
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
// Check implementation gate
|
|
210
|
-
let coreResult = checkImplementationGate({
|
|
211
|
-
prompt,
|
|
212
|
-
source
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
// If research protocol should be injected, add it to system reminder
|
|
216
|
-
if (researchResult.injectProtocol && researchResult.protocolSteps) {
|
|
217
|
-
coreResult = {
|
|
218
|
-
...coreResult,
|
|
219
|
-
systemReminder: researchResult.protocolSteps,
|
|
220
|
-
researchTriggered: true,
|
|
221
|
-
questionType: researchResult.questionType,
|
|
222
|
-
suggestedDepth: researchResult.suggestedDepth
|
|
223
|
-
};
|
|
224
|
-
} else if (researchResult.warning && coreResult.allowed) {
|
|
225
|
-
coreResult = {
|
|
226
|
-
...coreResult,
|
|
227
|
-
warning: true,
|
|
228
|
-
researchWarning: researchResult.message,
|
|
229
|
-
suggestedCommand: researchResult.suggestedCommand
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Inject phase-specific context prompt
|
|
234
|
-
if (phasePrompt) {
|
|
235
|
-
coreResult = {
|
|
236
|
-
...coreResult,
|
|
237
|
-
phasePrompt
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// wf-d3e67abe — surface overdue workspace dispatches (silent worker deaths)
|
|
242
|
-
// to the manager model before it processes the next prompt. Manager-only;
|
|
243
|
-
// fail-open (buildOverdueContext returns null on any error or wrong scope).
|
|
244
|
-
try {
|
|
245
|
-
const overduePrompt = buildOverdueContext();
|
|
246
|
-
if (overduePrompt) {
|
|
247
|
-
coreResult = {
|
|
248
|
-
...coreResult,
|
|
249
|
-
overduePrompt
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
} catch (err) {
|
|
253
|
-
if (process.env.DEBUG) {
|
|
254
|
-
console.error(`[Hook] Overdue dispatches check failed: ${err.message}`);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// P11.5 mechanical enforcement (2026-04-27): long-form prompts without
|
|
259
|
-
// source-link are forced through /wogi-extract-review. This is the
|
|
260
|
-
// mechanical layer that complements the methodology rule. Applies in
|
|
261
|
-
// worker mode (channel-dispatch with no source-link — wogi-hub failure
|
|
262
|
-
// shape) AND in any session that receives a long task-creating prompt
|
|
263
|
-
// without preserved source.
|
|
264
|
-
try {
|
|
265
|
-
const enforce = shouldForceExtractReview({ text: prompt, source });
|
|
266
|
-
if (enforce.forced) {
|
|
267
|
-
const msg = buildEnforcementMessage(enforce.reason, enforce.level);
|
|
268
|
-
coreResult = {
|
|
269
|
-
...coreResult,
|
|
270
|
-
longInputEnforcement: msg
|
|
271
|
-
};
|
|
272
|
-
markLongInputPending({
|
|
273
|
-
level: enforce.level,
|
|
274
|
-
reason: enforce.reason,
|
|
275
|
-
promptPreview: typeof prompt === 'string' ? prompt.slice(0, 200) : '(non-string)',
|
|
276
|
-
source: source || null,
|
|
277
|
-
repoName: process.env.WOGI_REPO_NAME || null
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
} catch (err) {
|
|
281
|
-
if (process.env.DEBUG) {
|
|
282
|
-
console.error(`[Hook] Long-input enforcement check failed: ${err.message}`);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return coreResult;
|
|
18
|
+
return await orchestrateUserPromptSubmit({ input, parsedInput });
|
|
287
19
|
}, {
|
|
288
20
|
failMode: 'block',
|
|
289
21
|
failOutput: {
|