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.
Files changed (33) hide show
  1. package/.claude/docs/intent-grounded-reasoning.md +1 -1
  2. package/.workflow/templates/partials/methodology-rules.hbs +30 -1
  3. package/lib/commands/team-connection.js +5 -28
  4. package/lib/utils.js +12 -26
  5. package/lib/wogi-claude +40 -1
  6. package/lib/workspace.js +6 -13
  7. package/package.json +2 -2
  8. package/scripts/flow +4 -0
  9. package/scripts/flow-autonomous-detector.js +29 -4
  10. package/scripts/flow-autonomous-mode.js +27 -7
  11. package/scripts/flow-completion-summary.js +2 -16
  12. package/scripts/flow-id.js +31 -0
  13. package/scripts/flow-io.js +78 -0
  14. package/scripts/flow-long-input-pending.js +110 -0
  15. package/scripts/flow-long-input-stories.js +8 -0
  16. package/scripts/flow-orchestrate.js +16 -10
  17. package/scripts/flow-question-queue.js +73 -7
  18. package/scripts/flow-scanner-base.js +77 -1
  19. package/scripts/flow-session-state.js +47 -0
  20. package/scripts/flow-source-fidelity.js +279 -0
  21. package/scripts/flow-time-format.js +42 -0
  22. package/scripts/flow-utils.js +3 -16
  23. package/scripts/flow-worker-mcp-strip.js +12 -11
  24. package/scripts/flow-workspace-summary.js +38 -19
  25. package/scripts/hooks/adapters/claude-code.js +7 -4
  26. package/scripts/hooks/core/long-input-enforcement.js +311 -0
  27. package/scripts/hooks/core/pre-tool-deps.js +185 -0
  28. package/scripts/hooks/core/pre-tool-orchestrator.js +22 -0
  29. package/scripts/hooks/core/session-context.js +26 -0
  30. package/scripts/hooks/core/task-boundary-reset.js +13 -0
  31. package/scripts/hooks/core/worker-boundary-gate.js +67 -16
  32. package/scripts/hooks/entry/claude-code/pre-tool-use.js +21 -95
  33. 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.replace(/\/+$/, '')}/.workspace/`;
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' && memberStateDirRegex.test(filePath)) {
155
- return {
156
- blocked: true,
157
- reason: 'path-discipline-manager',
158
- message: [
159
- `PATH DISCIPLINE: manager MUST NOT write to worker member-repo state.`,
160
- ``,
161
- `Blocked: ${filePath}`,
162
- ``,
163
- `Worker member-repos own their own .workflow/state/. Send a channel`,
164
- `dispatch to the worker if state changes are needed there.`
165
- ].join('\n')
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 calls
7
- * the shared pre-tool-orchestrator (see scripts/hooks/core/pre-tool-orchestrator.js).
6
+ * Thin CLI-specific entry point. Parses input, wires dependencies, and
7
+ * dispatches to the shared pre-tool-orchestrator.
8
8
  *
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)
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 { __raw: true, continue: true, hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow' } };
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 { __raw: true, continue: true, hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow' } };
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',