wogiflow 2.29.2 → 2.29.3
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/docs/intent-grounded-reasoning.md +1 -1
- package/.workflow/templates/partials/methodology-rules.hbs +30 -1
- package/lib/commands/team-connection.js +5 -28
- package/lib/utils.js +12 -26
- package/lib/wogi-claude +40 -1
- package/lib/workspace.js +6 -13
- package/package.json +2 -2
- package/scripts/flow +4 -0
- package/scripts/flow-autonomous-detector.js +29 -4
- package/scripts/flow-autonomous-mode.js +27 -7
- package/scripts/flow-completion-summary.js +2 -16
- package/scripts/flow-id.js +31 -0
- package/scripts/flow-io.js +78 -0
- package/scripts/flow-long-input-pending.js +110 -0
- package/scripts/flow-long-input-stories.js +8 -0
- package/scripts/flow-orchestrate.js +16 -10
- package/scripts/flow-question-queue.js +73 -7
- package/scripts/flow-scanner-base.js +77 -1
- package/scripts/flow-session-state.js +47 -0
- package/scripts/flow-source-fidelity.js +279 -0
- package/scripts/flow-time-format.js +42 -0
- package/scripts/flow-utils.js +3 -16
- package/scripts/flow-worker-mcp-strip.js +12 -11
- package/scripts/flow-workspace-summary.js +38 -19
- package/scripts/hooks/adapters/claude-code.js +7 -4
- package/scripts/hooks/core/long-input-enforcement.js +311 -0
- package/scripts/hooks/core/pre-tool-deps.js +185 -0
- package/scripts/hooks/core/pre-tool-orchestrator.js +22 -0
- package/scripts/hooks/core/session-context.js +26 -0
- package/scripts/hooks/core/task-boundary-reset.js +13 -0
- package/scripts/hooks/core/worker-boundary-gate.js +67 -16
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +21 -95
- package/scripts/hooks/entry/claude-code/user-prompt-submit.js +33 -0
|
@@ -120,6 +120,49 @@ function isWorkspaceWorker() {
|
|
|
120
120
|
* @param {Object} toolInput - { file_path } for Edit/Write
|
|
121
121
|
* @returns {{ blocked: boolean, reason?: string, message?: string }}
|
|
122
122
|
*/
|
|
123
|
+
// SEC-002 fix (2026-04-26): manager-side path discipline now derives member
|
|
124
|
+
// state dirs from the actual workspace registry, not a hardcoded /members?/
|
|
125
|
+
// regex. Workspaces with flat-sibling layouts (member dirs directly under
|
|
126
|
+
// workspace root, no /members/ prefix) are now correctly enforced.
|
|
127
|
+
//
|
|
128
|
+
// Discovery is cached per-process for the WOGI_WORKSPACE_ROOT lifetime —
|
|
129
|
+
// PreToolUse fires often, fs.readdir on every call would add latency.
|
|
130
|
+
let _memberDirsCache = null;
|
|
131
|
+
let _memberDirsCacheRoot = null;
|
|
132
|
+
|
|
133
|
+
function discoverMemberStateDirs(root) {
|
|
134
|
+
if (_memberDirsCache && _memberDirsCacheRoot === root) return _memberDirsCache;
|
|
135
|
+
const fs = require('node:fs');
|
|
136
|
+
const path = require('node:path');
|
|
137
|
+
const out = [];
|
|
138
|
+
try {
|
|
139
|
+
const entries = fs.readdirSync(root, { withFileTypes: true });
|
|
140
|
+
for (const entry of entries) {
|
|
141
|
+
if (!entry.isDirectory()) continue;
|
|
142
|
+
// Skip hidden dirs (.workspace, .git) and node_modules (mirrors
|
|
143
|
+
// discoverMembers() in lib/workspace.js).
|
|
144
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
|
|
145
|
+
const stateDir = path.join(root, entry.name, '.workflow', 'state');
|
|
146
|
+
// Only include paths that actually have a .workflow/state/ subtree —
|
|
147
|
+
// these are the member repos. Other directories (e.g. shared/, docs/,
|
|
148
|
+
// dist/) are not workspace members.
|
|
149
|
+
try {
|
|
150
|
+
if (fs.existsSync(stateDir) && fs.statSync(stateDir).isDirectory()) {
|
|
151
|
+
out.push(stateDir + path.sep);
|
|
152
|
+
}
|
|
153
|
+
} catch (_e) { /* skip unreadable */ }
|
|
154
|
+
}
|
|
155
|
+
} catch (_err) { /* unreadable workspace root → empty list */ }
|
|
156
|
+
_memberDirsCache = out;
|
|
157
|
+
_memberDirsCacheRoot = root;
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function _resetPathDisciplineCache() {
|
|
162
|
+
_memberDirsCache = null;
|
|
163
|
+
_memberDirsCacheRoot = null;
|
|
164
|
+
}
|
|
165
|
+
|
|
123
166
|
function checkPathDiscipline(toolName, toolInput) {
|
|
124
167
|
if (!process.env.WOGI_WORKSPACE_ROOT) return { blocked: false };
|
|
125
168
|
const writeTools = new Set(['Edit', 'Write', 'NotebookEdit']);
|
|
@@ -128,9 +171,8 @@ function checkPathDiscipline(toolName, toolInput) {
|
|
|
128
171
|
if (typeof filePath !== 'string' || !filePath) return { blocked: false };
|
|
129
172
|
|
|
130
173
|
const repo = process.env.WOGI_REPO_NAME || '';
|
|
131
|
-
const root = process.env.WOGI_WORKSPACE_ROOT;
|
|
132
|
-
const managerStateDir = `${root
|
|
133
|
-
const memberStateDirRegex = /\/members?\/[^/]+\/\.workflow\/state\//;
|
|
174
|
+
const root = process.env.WOGI_WORKSPACE_ROOT.replace(/\/+$/, '');
|
|
175
|
+
const managerStateDir = `${root}/.workspace/`;
|
|
134
176
|
|
|
135
177
|
if (repo && repo !== 'manager') {
|
|
136
178
|
if (filePath.startsWith(managerStateDir)) {
|
|
@@ -151,19 +193,27 @@ function checkPathDiscipline(toolName, toolInput) {
|
|
|
151
193
|
}
|
|
152
194
|
}
|
|
153
195
|
|
|
154
|
-
if (repo === 'manager'
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
196
|
+
if (repo === 'manager') {
|
|
197
|
+
// Match against EVERY discovered member's .workflow/state/ path —
|
|
198
|
+
// layout-independent. (SEC-002 fix; was hardcoded /members?/ regex)
|
|
199
|
+
const memberStateDirs = discoverMemberStateDirs(root);
|
|
200
|
+
for (const memberStateDir of memberStateDirs) {
|
|
201
|
+
if (filePath.startsWith(memberStateDir)) {
|
|
202
|
+
return {
|
|
203
|
+
blocked: true,
|
|
204
|
+
reason: 'path-discipline-manager',
|
|
205
|
+
message: [
|
|
206
|
+
`PATH DISCIPLINE: manager MUST NOT write to worker member-repo state.`,
|
|
207
|
+
``,
|
|
208
|
+
`Blocked: ${filePath}`,
|
|
209
|
+
`Member: ${memberStateDir}`,
|
|
210
|
+
``,
|
|
211
|
+
`Worker member-repos own their own .workflow/state/. Send a channel`,
|
|
212
|
+
`dispatch to the worker if state changes are needed there.`
|
|
213
|
+
].join('\n')
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
167
217
|
}
|
|
168
218
|
|
|
169
219
|
return { blocked: false };
|
|
@@ -172,5 +222,6 @@ function checkPathDiscipline(toolName, toolInput) {
|
|
|
172
222
|
module.exports = {
|
|
173
223
|
checkWorkerBoundary,
|
|
174
224
|
checkPathDiscipline,
|
|
225
|
+
_resetPathDisciplineCache,
|
|
175
226
|
isWorkspaceWorker
|
|
176
227
|
};
|
|
@@ -3,116 +3,38 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Wogi Flow - Claude Code PreToolUse Hook (Entry)
|
|
5
5
|
*
|
|
6
|
-
* Thin CLI-specific entry point. Parses input, wires dependencies, and
|
|
7
|
-
* the shared pre-tool-orchestrator
|
|
6
|
+
* Thin CLI-specific entry point. Parses input, wires dependencies, and
|
|
7
|
+
* dispatches to the shared pre-tool-orchestrator.
|
|
8
8
|
*
|
|
9
|
-
* Extraction history
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
9
|
+
* Extraction history:
|
|
10
|
+
* v7.0 — Original 470-LOC inline gate cascade extracted to
|
|
11
|
+
* scripts/hooks/core/pre-tool-orchestrator.js.
|
|
12
|
+
* v8.0 — Audit Story 9 (wf-5e94e2c0, 2026-04-26): the lazy-loader
|
|
13
|
+
* try/catch boilerplate (12 gate modules) extracted to
|
|
14
|
+
* scripts/hooks/core/pre-tool-deps.js. Entry brought below the
|
|
15
|
+
* ≤120 LOC three-layer rule.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
'use strict';
|
|
19
19
|
|
|
20
|
-
const { checkScopeGate } = require('../../core/scope-gate');
|
|
21
|
-
const { checkComponentReuse } = require('../../core/component-check');
|
|
22
|
-
const { checkTodoWriteGate } = require('../../core/todowrite-gate');
|
|
23
|
-
const { checkRoutingGate, clearRoutingPending, hasActiveTask } = require('../../core/routing-gate');
|
|
24
|
-
const { checkPhaseGate } = require('../../core/phase-gate');
|
|
25
|
-
const { checkCommitLogGate } = require('../../core/commit-log-gate');
|
|
26
20
|
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.
|
|
30
|
-
let recordPhaseRead = () => {}, checkPhaseReadGate = () => ({ blocked: false }), clearPhaseReads = () => {};
|
|
31
|
-
try {
|
|
32
|
-
const prg = require('../../core/phase-read-gate');
|
|
33
|
-
recordPhaseRead = prg.recordPhaseRead;
|
|
34
|
-
checkPhaseReadGate = prg.checkPhaseReadGate;
|
|
35
|
-
clearPhaseReads = prg.clearPhaseReads;
|
|
36
|
-
} catch (_err) { if (process.env.DEBUG) console.error(`[Hook] Phase-read gate not loaded: ${_err.message}`); }
|
|
37
|
-
|
|
38
|
-
let recordEvidenceRead = () => {}, checkSpecWriteGate = () => ({ blocked: false }), clearResearchEvidence = () => {};
|
|
39
|
-
try {
|
|
40
|
-
const reg = require('../../core/research-evidence-gate');
|
|
41
|
-
recordEvidenceRead = reg.recordEvidenceRead;
|
|
42
|
-
checkSpecWriteGate = reg.checkSpecWriteGate;
|
|
43
|
-
clearResearchEvidence = reg.clearResearchEvidence;
|
|
44
|
-
} catch (err) {
|
|
45
|
-
// CL-004: load failure for a gate file that SHOULD be present is a
|
|
46
|
-
// deployment issue worth surfacing even without DEBUG set. Silently
|
|
47
|
-
// shimming masks broken installs. Preserve fail-open (shims above)
|
|
48
|
-
// so the hook pipeline still works, but log to stderr so operators see it.
|
|
49
|
-
console.error(`[Hook] WARNING: Research-evidence gate failed to load — gate is disabled. ${err.message}`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const _noop = () => ({ allowed: true, blocked: false });
|
|
53
|
-
let checkDeployGate = _noop, checkWriteBlock = _noop;
|
|
54
|
-
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}`); }
|
|
55
|
-
let checkStrikeGate = _noop;
|
|
56
|
-
try { checkStrikeGate = require('../../core/strike-gate').checkStrikeGate; } catch (_err) { if (process.env.DEBUG) console.error(`[Hook] Strike gate not loaded: ${_err.message}`); }
|
|
57
|
-
let checkBugfixScope = _noop;
|
|
58
|
-
try { checkBugfixScope = require('../../core/bugfix-scope-gate').checkBugfixScope; } catch (_err) { if (process.env.DEBUG) console.error(`[Hook] Bugfix scope gate not loaded: ${_err.message}`); }
|
|
59
|
-
let checkScopeMutation = _noop;
|
|
60
|
-
try { checkScopeMutation = require('../../core/scope-mutation-gate').checkScopeMutation; } catch (_err) { if (process.env.DEBUG) console.error(`[Hook] Scope mutation gate not loaded: ${_err.message}`); }
|
|
61
|
-
let checkGitSafety = _noop;
|
|
62
|
-
try { checkGitSafety = require('../../core/git-safety-gate').checkGitSafety; } catch (_err) { if (process.env.DEBUG) console.error(`[Hook] Git safety gate not loaded: ${_err.message}`); }
|
|
63
|
-
let checkManagerBoundary = _noop;
|
|
64
|
-
try { checkManagerBoundary = require('../../core/manager-boundary-gate').checkManagerBoundary; } catch (_err) { if (process.env.DEBUG) console.error(`[Hook] Manager boundary gate not loaded: ${_err.message}`); }
|
|
65
|
-
let checkWorkerBoundary = _noop;
|
|
66
|
-
let checkPathDiscipline = _noop;
|
|
67
|
-
try {
|
|
68
|
-
const wbg = require('../../core/worker-boundary-gate');
|
|
69
|
-
checkWorkerBoundary = wbg.checkWorkerBoundary;
|
|
70
|
-
checkPathDiscipline = wbg.checkPathDiscipline;
|
|
71
|
-
} catch (_err) { if (process.env.DEBUG) console.error(`[Hook] Worker boundary gate not loaded: ${_err.message}`); }
|
|
72
|
-
|
|
21
|
+
const { loadGateDeps } = require('../../core/pre-tool-deps');
|
|
73
22
|
const { claudeCodeAdapter } = require('../../adapters/claude-code');
|
|
74
|
-
const { markSkillPending } = require('../../../flow-durable-session');
|
|
75
|
-
const { getConfig } = require('../../../flow-utils');
|
|
76
|
-
const { readHookStatus } = require('../../../flow-hook-status');
|
|
77
23
|
const { runHook } = require('../shared/hook-runner');
|
|
78
24
|
|
|
79
|
-
// Lazy-load strict adherence (avoids circular deps + startup cost).
|
|
80
|
-
let _strictAdherence = null;
|
|
81
|
-
function getStrictAdherence() {
|
|
82
|
-
if (!_strictAdherence) {
|
|
83
|
-
try {
|
|
84
|
-
_strictAdherence = require('../../../flow-strict-adherence');
|
|
85
|
-
} catch (_err) {
|
|
86
|
-
_strictAdherence = { isEnabled: () => false, validateCommand: () => ({ valid: true }), validateFileName: () => ({ valid: true }) };
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return _strictAdherence;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
25
|
runHook('PreToolUse', async ({ input, parsedInput }) => {
|
|
93
26
|
const hookStart = process.hrtime.bigint();
|
|
94
27
|
|
|
95
28
|
// Empty input — allow through
|
|
96
29
|
if (!input || Object.keys(input).length === 0) {
|
|
97
|
-
return {
|
|
30
|
+
return {
|
|
31
|
+
__raw: true,
|
|
32
|
+
continue: true,
|
|
33
|
+
hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow' }
|
|
34
|
+
};
|
|
98
35
|
}
|
|
99
36
|
|
|
100
|
-
const deps =
|
|
101
|
-
// Gates
|
|
102
|
-
checkScopeGate, checkComponentReuse, checkTodoWriteGate,
|
|
103
|
-
checkRoutingGate, clearRoutingPending, hasActiveTask,
|
|
104
|
-
checkPhaseGate, checkCommitLogGate,
|
|
105
|
-
recordPhaseRead, checkPhaseReadGate, clearPhaseReads,
|
|
106
|
-
recordEvidenceRead, checkSpecWriteGate, clearResearchEvidence,
|
|
107
|
-
checkDeployGate, checkWriteBlock,
|
|
108
|
-
checkStrikeGate, checkBugfixScope, checkScopeMutation,
|
|
109
|
-
checkGitSafety, checkManagerBoundary, checkWorkerBoundary, checkPathDiscipline,
|
|
110
|
-
// Side-effect helpers
|
|
111
|
-
markSkillPending,
|
|
112
|
-
// Config + runtime
|
|
113
|
-
getConfig, readHookStatus, getStrictAdherence,
|
|
114
|
-
};
|
|
115
|
-
|
|
37
|
+
const deps = loadGateDeps();
|
|
116
38
|
const coreResult = runPreToolGates({ input, parsedInput }, deps);
|
|
117
39
|
|
|
118
40
|
// Fast path (no transform needed — short-circuit to allow)
|
|
@@ -121,7 +43,11 @@ runHook('PreToolUse', async ({ input, parsedInput }) => {
|
|
|
121
43
|
const elapsed = Number(process.hrtime.bigint() - hookStart) / 1e6;
|
|
122
44
|
console.error(`[Hook] PreToolUse fast-path: ${elapsed.toFixed(1)}ms`);
|
|
123
45
|
}
|
|
124
|
-
return {
|
|
46
|
+
return {
|
|
47
|
+
__raw: true,
|
|
48
|
+
continue: true,
|
|
49
|
+
hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow' }
|
|
50
|
+
};
|
|
125
51
|
}
|
|
126
52
|
|
|
127
53
|
if (coreResult.blocked) {
|
|
@@ -14,6 +14,11 @@ const { setRoutingPending, clearRoutingPending, ROUTING_CLEARED_PATH } = require
|
|
|
14
14
|
const { getPhaseContextPrompt } = require('../../core/phase-gate');
|
|
15
15
|
const { buildOverdueContext } = require('../../core/overdue-dispatches');
|
|
16
16
|
const { getDossierInjection } = require('../../core/feature-dossier-gate');
|
|
17
|
+
const {
|
|
18
|
+
shouldForceExtractReview,
|
|
19
|
+
buildEnforcementMessage,
|
|
20
|
+
markLongInputPending
|
|
21
|
+
} = require('../../core/long-input-enforcement');
|
|
17
22
|
const { markSkillPending, loadDurableSession } = require('../../../flow-durable-session');
|
|
18
23
|
const { captureCurrentPrompt } = require('../../../flow-prompt-capture');
|
|
19
24
|
const { spawnBackgroundDetection } = require('../../../flow-correction-detector');
|
|
@@ -207,6 +212,34 @@ runHook('UserPromptSubmit', async ({ input, parsedInput }) => {
|
|
|
207
212
|
}
|
|
208
213
|
}
|
|
209
214
|
|
|
215
|
+
// P11.5 mechanical enforcement (2026-04-27): long-form prompts without
|
|
216
|
+
// source-link are forced through /wogi-extract-review. This is the
|
|
217
|
+
// mechanical layer that complements the methodology rule. Applies in
|
|
218
|
+
// worker mode (channel-dispatch with no source-link — wogi-hub failure
|
|
219
|
+
// shape) AND in any session that receives a long task-creating prompt
|
|
220
|
+
// without preserved source.
|
|
221
|
+
try {
|
|
222
|
+
const enforce = shouldForceExtractReview({ text: prompt, source });
|
|
223
|
+
if (enforce.forced) {
|
|
224
|
+
const msg = buildEnforcementMessage(enforce.reason, enforce.level);
|
|
225
|
+
coreResult = {
|
|
226
|
+
...coreResult,
|
|
227
|
+
longInputEnforcement: msg
|
|
228
|
+
};
|
|
229
|
+
markLongInputPending({
|
|
230
|
+
level: enforce.level,
|
|
231
|
+
reason: enforce.reason,
|
|
232
|
+
promptPreview: typeof prompt === 'string' ? prompt.slice(0, 200) : '(non-string)',
|
|
233
|
+
source: source || null,
|
|
234
|
+
repoName: process.env.WOGI_REPO_NAME || null
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
} catch (err) {
|
|
238
|
+
if (process.env.DEBUG) {
|
|
239
|
+
console.error(`[Hook] Long-input enforcement check failed: ${err.message}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
210
243
|
return coreResult;
|
|
211
244
|
}, {
|
|
212
245
|
failMode: 'block',
|