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.
@@ -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
- * Called when user submits a prompt (before processing).
7
- * Enforces implementation gate - blocks implementation requests without active task.
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 fs = require('node:fs');
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
- // Handle empty input gracefully
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: {