wogiflow 2.4.2 → 2.4.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/claude-code-compatibility.md +27 -0
- package/.claude/settings.json +1 -1
- package/.workflow/models/registry.json +1 -1
- 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 +0 -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.js +5 -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 +2 -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/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/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/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
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wogi Flow - Hook Status Aggregator
|
|
3
|
+
*
|
|
4
|
+
* Pre-computes a single hook-status.json file containing all state
|
|
5
|
+
* that PreToolUse/PostToolUse hooks need. Hooks read 1 file instead
|
|
6
|
+
* of 6-8 separate reads per invocation.
|
|
7
|
+
*
|
|
8
|
+
* Writers: flow-start.js, task-completed.js, routing-gate.js, phase-gate.js
|
|
9
|
+
* Reader: pre-tool-use.js, post-tool-use.js
|
|
10
|
+
*
|
|
11
|
+
* IMPORTANT: This module must NOT require flow-utils at load time (circular dep risk).
|
|
12
|
+
* flow-utils does NOT require this module — keep it that way.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('node:fs');
|
|
16
|
+
const path = require('node:path');
|
|
17
|
+
|
|
18
|
+
// Lazy-load to avoid circular deps at module level
|
|
19
|
+
let _PATHS = null;
|
|
20
|
+
function getPATHS() {
|
|
21
|
+
if (!_PATHS) {
|
|
22
|
+
_PATHS = require('./flow-paths').PATHS;
|
|
23
|
+
}
|
|
24
|
+
return _PATHS;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Lazy-load safeJsonParseString to avoid circular deps
|
|
28
|
+
let _safeJsonParseString = null;
|
|
29
|
+
function getSafeJsonParseString() {
|
|
30
|
+
if (!_safeJsonParseString) {
|
|
31
|
+
_safeJsonParseString = require('./flow-io').safeJsonParseString;
|
|
32
|
+
}
|
|
33
|
+
return _safeJsonParseString;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const HOOK_STATUS_FILENAME = 'hook-status.json';
|
|
37
|
+
|
|
38
|
+
function getHookStatusPath() {
|
|
39
|
+
return path.join(getPATHS().state, HOOK_STATUS_FILENAME);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Read the current hook status (single file read).
|
|
44
|
+
* Uses safeJsonParseString for prototype pollution protection.
|
|
45
|
+
* Returns null if file doesn't exist, is corrupt, or stale vs config.json.
|
|
46
|
+
* @returns {Object|null}
|
|
47
|
+
*/
|
|
48
|
+
function readHookStatus() {
|
|
49
|
+
try {
|
|
50
|
+
const statusPath = getHookStatusPath();
|
|
51
|
+
const content = fs.readFileSync(statusPath, 'utf-8');
|
|
52
|
+
const parsed = getSafeJsonParseString()(content, null);
|
|
53
|
+
if (!parsed) return null;
|
|
54
|
+
|
|
55
|
+
// Staleness check: if config.json is newer than hook-status.json, invalidate
|
|
56
|
+
try {
|
|
57
|
+
const configPath = path.join(getPATHS().workflow, 'config.json');
|
|
58
|
+
const configMtime = fs.statSync(configPath).mtimeMs;
|
|
59
|
+
const statusMtime = fs.statSync(statusPath).mtimeMs;
|
|
60
|
+
if (configMtime > statusMtime) {
|
|
61
|
+
// Config changed after hook-status was written — refresh needed
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
} catch (_err) { /* config missing or stat failed — use cached status */ }
|
|
65
|
+
|
|
66
|
+
return parsed;
|
|
67
|
+
} catch (_err) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Write the hook status file atomically with PID-scoped temp file.
|
|
74
|
+
* @param {Object} status - The full status object
|
|
75
|
+
*/
|
|
76
|
+
function writeHookStatus(status) {
|
|
77
|
+
status.updatedAt = new Date().toISOString();
|
|
78
|
+
const statusPath = getHookStatusPath();
|
|
79
|
+
const tmpPath = statusPath + '.tmp.' + process.pid;
|
|
80
|
+
try {
|
|
81
|
+
fs.writeFileSync(tmpPath, JSON.stringify(status, null, 2));
|
|
82
|
+
fs.renameSync(tmpPath, statusPath);
|
|
83
|
+
} catch (_err) {
|
|
84
|
+
// Non-blocking — hooks fall back to individual reads
|
|
85
|
+
try { fs.unlinkSync(tmpPath); } catch (_e) { /* ignore */ }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Build enforcement block from config (shared by createDefaultStatus + refreshHookStatus).
|
|
91
|
+
* @param {Object} config - Parsed config object
|
|
92
|
+
* @returns {Object} enforcement flags + componentReuse + phaseGate
|
|
93
|
+
*/
|
|
94
|
+
function buildEnforcementFromConfig(config) {
|
|
95
|
+
return {
|
|
96
|
+
enforcement: {
|
|
97
|
+
taskGating: config.enforcement?.taskGating?.enabled === true,
|
|
98
|
+
scopeGating: config.enforcement?.scopeGating?.enabled === true,
|
|
99
|
+
routingGate: config.enforcement?.routingGate?.enabled === true,
|
|
100
|
+
commitLogGate: config.enforcement?.commitLogGate?.enabled === true,
|
|
101
|
+
todoWriteGate: config.enforcement?.todoWriteGate?.enabled === true,
|
|
102
|
+
loopEnforcement: config.enforcement?.loopEnforcement?.enabled === true
|
|
103
|
+
},
|
|
104
|
+
componentReuse: config.componentReuse?.enabled === true,
|
|
105
|
+
// Correct path: hooks.rules.phaseGate.enabled (matches phase-gate.js:84)
|
|
106
|
+
phaseGate: config.hooks?.rules?.phaseGate?.enabled === true
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Update specific fields in hook status (read-modify-write).
|
|
112
|
+
* Creates the file if it doesn't exist.
|
|
113
|
+
* Deep-merges known nested keys (enforcement, routing) to prevent clobbering.
|
|
114
|
+
* @param {Object} updates - Fields to merge into current status
|
|
115
|
+
*/
|
|
116
|
+
function updateHookStatus(updates) {
|
|
117
|
+
const current = readHookStatus() || createDefaultStatus();
|
|
118
|
+
// Deep-merge known nested keys to prevent shallow-assign clobbering
|
|
119
|
+
if (updates.enforcement) {
|
|
120
|
+
updates.enforcement = { ...current.enforcement, ...updates.enforcement };
|
|
121
|
+
}
|
|
122
|
+
if (updates.routing) {
|
|
123
|
+
updates.routing = { ...current.routing, ...updates.routing };
|
|
124
|
+
}
|
|
125
|
+
Object.assign(current, updates);
|
|
126
|
+
writeHookStatus(current);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Create default hook status from current config + state.
|
|
131
|
+
* Called when hook-status.json doesn't exist yet.
|
|
132
|
+
* @returns {Object}
|
|
133
|
+
*/
|
|
134
|
+
function createDefaultStatus() {
|
|
135
|
+
let config = {};
|
|
136
|
+
try {
|
|
137
|
+
const { getConfig } = require('./flow-utils');
|
|
138
|
+
config = getConfig() || {};
|
|
139
|
+
} catch (_err) { /* ignore */ }
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
...buildEnforcementFromConfig(config),
|
|
143
|
+
activeTask: null,
|
|
144
|
+
phase: 'idle',
|
|
145
|
+
routing: {
|
|
146
|
+
pending: false,
|
|
147
|
+
cleared: false,
|
|
148
|
+
clearedAt: null
|
|
149
|
+
},
|
|
150
|
+
changedFiles: [],
|
|
151
|
+
updatedAt: new Date().toISOString()
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Refresh hook status from current config and state files.
|
|
157
|
+
* Called at session start and after config changes.
|
|
158
|
+
*/
|
|
159
|
+
function refreshHookStatus() {
|
|
160
|
+
let config = {};
|
|
161
|
+
try {
|
|
162
|
+
const { getConfig } = require('./flow-utils');
|
|
163
|
+
config = getConfig() || {};
|
|
164
|
+
} catch (_err) { /* ignore */ }
|
|
165
|
+
|
|
166
|
+
let activeTask = null;
|
|
167
|
+
try {
|
|
168
|
+
const { getReadyData } = require('./flow-utils');
|
|
169
|
+
const readyData = getReadyData();
|
|
170
|
+
if (readyData.inProgress && readyData.inProgress.length > 0) {
|
|
171
|
+
const task = readyData.inProgress[0];
|
|
172
|
+
activeTask = { id: task.id, title: task.title, routedAt: task.routedAt || null };
|
|
173
|
+
}
|
|
174
|
+
} catch (_err) { /* ignore */ }
|
|
175
|
+
|
|
176
|
+
let phase = 'idle';
|
|
177
|
+
try {
|
|
178
|
+
const { safeJsonParse } = require('./flow-utils');
|
|
179
|
+
const phasePath = path.join(getPATHS().state, 'workflow-phase.json');
|
|
180
|
+
const phaseData = safeJsonParse(phasePath, null);
|
|
181
|
+
if (phaseData) phase = phaseData.phase || 'idle';
|
|
182
|
+
} catch (_err) { /* ignore */ }
|
|
183
|
+
|
|
184
|
+
const status = {
|
|
185
|
+
...buildEnforcementFromConfig(config),
|
|
186
|
+
activeTask,
|
|
187
|
+
phase,
|
|
188
|
+
routing: {
|
|
189
|
+
pending: false,
|
|
190
|
+
cleared: false,
|
|
191
|
+
clearedAt: null
|
|
192
|
+
},
|
|
193
|
+
changedFiles: []
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
writeHookStatus(status);
|
|
197
|
+
return status;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Update just the active task field.
|
|
202
|
+
* @param {Object|null} task - { id, title, routedAt } or null
|
|
203
|
+
*/
|
|
204
|
+
function setActiveTask(task) {
|
|
205
|
+
updateHookStatus({ activeTask: task });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Update just the phase field.
|
|
210
|
+
* @param {string} phase
|
|
211
|
+
*/
|
|
212
|
+
function setPhase(phase) {
|
|
213
|
+
updateHookStatus({ phase });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Update routing state.
|
|
218
|
+
* @param {Object} routing - { pending, cleared, clearedAt }
|
|
219
|
+
*/
|
|
220
|
+
function setRouting(routing) {
|
|
221
|
+
updateHookStatus({ routing });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Add a file to the changed files list (deduped).
|
|
226
|
+
* @param {string} filePath
|
|
227
|
+
*/
|
|
228
|
+
function trackFile(filePath) {
|
|
229
|
+
const current = readHookStatus();
|
|
230
|
+
if (!current) return;
|
|
231
|
+
if (!current.changedFiles) current.changedFiles = [];
|
|
232
|
+
if (!current.changedFiles.includes(filePath)) {
|
|
233
|
+
current.changedFiles.push(filePath);
|
|
234
|
+
writeHookStatus(current);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Clear hook status on task completion.
|
|
240
|
+
*/
|
|
241
|
+
function clearOnTaskComplete() {
|
|
242
|
+
const current = readHookStatus() || createDefaultStatus();
|
|
243
|
+
current.activeTask = null;
|
|
244
|
+
current.phase = 'idle';
|
|
245
|
+
current.routing = { pending: false, cleared: false, clearedAt: null };
|
|
246
|
+
current.changedFiles = [];
|
|
247
|
+
writeHookStatus(current);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
module.exports = {
|
|
251
|
+
readHookStatus,
|
|
252
|
+
writeHookStatus,
|
|
253
|
+
updateHookStatus,
|
|
254
|
+
refreshHookStatus,
|
|
255
|
+
createDefaultStatus,
|
|
256
|
+
setActiveTask,
|
|
257
|
+
setPhase,
|
|
258
|
+
setRouting,
|
|
259
|
+
trackFile,
|
|
260
|
+
clearOnTaskComplete,
|
|
261
|
+
getHookStatusPath,
|
|
262
|
+
HOOK_STATUS_FILENAME
|
|
263
|
+
};
|
package/scripts/flow-hooks.js
CHANGED
|
@@ -18,28 +18,26 @@ const fs = require('node:fs');
|
|
|
18
18
|
const path = require('node:path');
|
|
19
19
|
const {
|
|
20
20
|
getProjectRoot,
|
|
21
|
-
getConfig
|
|
21
|
+
getConfig, PATHS
|
|
22
22
|
} = require('./flow-utils')
|
|
23
23
|
const { color, success, warn, error } = require('./flow-output');;
|
|
24
24
|
|
|
25
25
|
const { getAdapter, getAllAdapters, getAvailableAdapters } = require('./hooks/adapters');
|
|
26
|
-
const { readJson } = require('./flow-io');
|
|
27
|
-
|
|
28
|
-
const PROJECT_ROOT = getProjectRoot();
|
|
26
|
+
const { readJson, safeJsonParse } = require('./flow-io');
|
|
29
27
|
|
|
30
28
|
/**
|
|
31
29
|
* Get installed WogiFlow version from package.json (canonical source)
|
|
32
30
|
* @returns {string} Version string or 'unknown'
|
|
33
31
|
*/
|
|
34
32
|
function getInstalledVersion() {
|
|
35
|
-
const pkgPath = path.join(
|
|
33
|
+
const pkgPath = path.join(PATHS.root, 'node_modules', 'wogiflow', 'package.json');
|
|
36
34
|
const pkg = readJson(pkgPath, null);
|
|
37
35
|
if (pkg) {
|
|
38
36
|
return pkg.version || 'unknown';
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
// Fallback: try settings.json (legacy)
|
|
42
|
-
const settingsPath = path.join(
|
|
40
|
+
const settingsPath = path.join(PATHS.root, '.claude', 'settings.json');
|
|
43
41
|
const settings = readJson(settingsPath, null);
|
|
44
42
|
if (settings) {
|
|
45
43
|
return settings._wogiFlowVersion || 'unknown';
|
|
@@ -113,7 +111,7 @@ function installForTarget(targetName) {
|
|
|
113
111
|
headers: config.httpHeaders || {},
|
|
114
112
|
allowedEnvVars: config.httpAllowedEnvVars || []
|
|
115
113
|
} : undefined;
|
|
116
|
-
const hooksConfig = adapter.generateConfig(config.rules,
|
|
114
|
+
const hooksConfig = adapter.generateConfig(config.rules, PATHS.root, transportConfig);
|
|
117
115
|
|
|
118
116
|
// For Claude Code, we need to merge into settings.local.json
|
|
119
117
|
if (targetName === 'claude-code') {
|
|
@@ -138,15 +136,7 @@ function installClaudeCodeHooks(adapter, hooksConfig) {
|
|
|
138
136
|
}
|
|
139
137
|
|
|
140
138
|
// Read existing config
|
|
141
|
-
let existingConfig = {};
|
|
142
|
-
if (fs.existsSync(configPath)) {
|
|
143
|
-
try {
|
|
144
|
-
const content = fs.readFileSync(configPath, 'utf-8');
|
|
145
|
-
existingConfig = JSON.parse(content);
|
|
146
|
-
} catch (err) {
|
|
147
|
-
warn(` Could not parse existing config, will create new`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
139
|
+
let existingConfig = safeJsonParse(configPath, {});
|
|
150
140
|
|
|
151
141
|
// Check if we're overwriting non-Wogi hooks
|
|
152
142
|
if (existingConfig.hooks && !existingConfig._wogiFlowManaged) {
|
|
@@ -166,7 +156,7 @@ function installClaudeCodeHooks(adapter, hooksConfig) {
|
|
|
166
156
|
|
|
167
157
|
// Write config
|
|
168
158
|
fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2));
|
|
169
|
-
success(`Hooks written to ${path.relative(
|
|
159
|
+
success(`Hooks written to ${path.relative(PATHS.root, configPath)}`);
|
|
170
160
|
|
|
171
161
|
return true;
|
|
172
162
|
}
|
|
@@ -250,8 +240,12 @@ function removeClaudeCodeHooks(adapter) {
|
|
|
250
240
|
}
|
|
251
241
|
|
|
252
242
|
try {
|
|
253
|
-
const
|
|
254
|
-
|
|
243
|
+
const config = safeJsonParse(configPath, null);
|
|
244
|
+
|
|
245
|
+
if (!config) {
|
|
246
|
+
warn(` Config file is corrupt or unreadable (skipping)`);
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
255
249
|
|
|
256
250
|
if (!config._wogiFlowManaged) {
|
|
257
251
|
warn(` Config not managed by Wogi Flow (skipping)`);
|
|
@@ -275,8 +269,7 @@ function removeClaudeCodeHooks(adapter) {
|
|
|
275
269
|
// Restore backup if exists
|
|
276
270
|
const backupPath = configPath + '.backup';
|
|
277
271
|
if (fs.existsSync(backupPath)) {
|
|
278
|
-
const
|
|
279
|
-
const backupConfig = JSON.parse(backupContent);
|
|
272
|
+
const backupConfig = safeJsonParse(backupPath, {});
|
|
280
273
|
if (backupConfig.hooks) {
|
|
281
274
|
const finalConfig = { ...config, hooks: backupConfig.hooks };
|
|
282
275
|
fs.writeFileSync(configPath, JSON.stringify(finalConfig, null, 2));
|
|
@@ -375,13 +368,8 @@ function checkIfInstalled(adapter) {
|
|
|
375
368
|
if (!fs.existsSync(configPath)) {
|
|
376
369
|
return false;
|
|
377
370
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
const config = JSON.parse(content);
|
|
381
|
-
return config._wogiFlowManaged === true;
|
|
382
|
-
} catch (_err) {
|
|
383
|
-
return false;
|
|
384
|
-
}
|
|
371
|
+
const config = safeJsonParse(configPath, {});
|
|
372
|
+
return config._wogiFlowManaged === true;
|
|
385
373
|
}
|
|
386
374
|
return false;
|
|
387
375
|
}
|
|
@@ -434,7 +422,7 @@ async function testHook(hookName) {
|
|
|
434
422
|
|
|
435
423
|
const { spawn } = require('node:child_process');
|
|
436
424
|
const proc = spawn('node', [hookPath], {
|
|
437
|
-
cwd:
|
|
425
|
+
cwd: PATHS.root,
|
|
438
426
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
439
427
|
});
|
|
440
428
|
|
|
@@ -22,9 +22,9 @@ const { TIMEOUTS, LIMITS, BACKOFF } = require('./flow-constants');
|
|
|
22
22
|
class HttpClient {
|
|
23
23
|
constructor(baseUrl, options = {}) {
|
|
24
24
|
this.baseUrl = baseUrl;
|
|
25
|
-
this.defaultHeaders = options.headers
|
|
26
|
-
this.timeout = options.timeout
|
|
27
|
-
this.maxRetries = options.maxRetries
|
|
25
|
+
this.defaultHeaders = options.headers ?? {};
|
|
26
|
+
this.timeout = options.timeout ?? TIMEOUTS.HTTP_DEFAULT;
|
|
27
|
+
this.maxRetries = options.maxRetries ?? LIMITS.HTTP_MAX_RETRIES;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/**
|
|
@@ -55,10 +55,10 @@ class HttpClient {
|
|
|
55
55
|
port: url.port || (isHttps ? 443 : 80),
|
|
56
56
|
path: url.pathname + url.search,
|
|
57
57
|
headers,
|
|
58
|
-
timeout: options.timeout
|
|
58
|
+
timeout: options.timeout ?? this.timeout,
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
-
return this._executeWithRetry(lib, requestOptions, body, options.retries
|
|
61
|
+
return this._executeWithRetry(lib, requestOptions, body, options.retries ?? 0);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/**
|
|
@@ -147,7 +147,8 @@ class HttpClient {
|
|
|
147
147
|
* Sleep helper
|
|
148
148
|
*/
|
|
149
149
|
_sleep(ms) {
|
|
150
|
-
|
|
150
|
+
const { setTimeout: sleep } = require('node:timers/promises');
|
|
151
|
+
return sleep(ms);
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
// Convenience methods
|
|
@@ -222,7 +223,7 @@ class HttpClient {
|
|
|
222
223
|
* Simple fetch JSON helper (for one-off requests)
|
|
223
224
|
*/
|
|
224
225
|
async function fetchJson(url, options = {}) {
|
|
225
|
-
const client = new HttpClient(url, { timeout: options.timeout
|
|
226
|
+
const client = new HttpClient(url, { timeout: options.timeout ?? TIMEOUTS.HTTP_DEFAULT });
|
|
226
227
|
const parsedUrl = new URL(url);
|
|
227
228
|
const response = await client.get(parsedUrl.pathname + parsedUrl.search, {
|
|
228
229
|
headers: options.headers,
|
|
@@ -234,7 +235,7 @@ async function fetchJson(url, options = {}) {
|
|
|
234
235
|
* Simple post JSON helper (for one-off requests)
|
|
235
236
|
*/
|
|
236
237
|
async function postJson(url, body, options = {}) {
|
|
237
|
-
const client = new HttpClient(url, { timeout: options.timeout
|
|
238
|
+
const client = new HttpClient(url, { timeout: options.timeout ?? TIMEOUTS.HTTP_DEFAULT });
|
|
238
239
|
const parsedUrl = new URL(url);
|
|
239
240
|
const response = await client.post(parsedUrl.pathname + parsedUrl.search, body, {
|
|
240
241
|
headers: options.headers,
|
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
|
|
11
11
|
const fs = require('node:fs');
|
|
12
12
|
const path = require('node:path');
|
|
13
|
-
const readline = require('node:readline');
|
|
13
|
+
const readline = require('node:readline/promises');
|
|
14
14
|
const http = require('node:http');
|
|
15
15
|
const { HttpClient } = require('./flow-http-client');
|
|
16
16
|
const { URL, URLSearchParams } = require('node:url');
|
|
17
|
-
const { getProjectRoot, colors, safeJsonParse } = require('./flow-utils');
|
|
17
|
+
const { getProjectRoot, colors, safeJsonParse, PATHS } = require('./flow-utils');
|
|
18
18
|
const { error: errorMsg } = require('./flow-output');
|
|
19
19
|
|
|
20
20
|
// Import model registry for smart model selection
|
|
@@ -26,9 +26,7 @@ try {
|
|
|
26
26
|
// Registry not available, will use hardcoded models
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const
|
|
30
|
-
const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
|
|
31
|
-
const CONFIG_PATH = path.join(WORKFLOW_DIR, 'config.json');
|
|
29
|
+
const CONFIG_PATH = path.join(PATHS.workflow, 'config.json');
|
|
32
30
|
|
|
33
31
|
const symbols = {
|
|
34
32
|
success: '✅',
|
|
@@ -96,12 +94,9 @@ async function prompt(question) {
|
|
|
96
94
|
output: process.stdout
|
|
97
95
|
});
|
|
98
96
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
resolve(answer.trim());
|
|
103
|
-
});
|
|
104
|
-
});
|
|
97
|
+
const answer = await rl.question(question);
|
|
98
|
+
rl.close();
|
|
99
|
+
return answer.trim();
|
|
105
100
|
}
|
|
106
101
|
|
|
107
102
|
class Spinner {
|
|
@@ -700,7 +695,7 @@ ${colors.cyan}╔═════════════════════
|
|
|
700
695
|
`);
|
|
701
696
|
|
|
702
697
|
// Check if workflow dir exists
|
|
703
|
-
if (!fs.existsSync(
|
|
698
|
+
if (!fs.existsSync(PATHS.workflow)) {
|
|
704
699
|
console.log(`${colors.red}${symbols.error} Wogi Flow not installed in this project.${colors.reset}`);
|
|
705
700
|
console.log(`Run /wogi-onboard first.`);
|
|
706
701
|
process.exit(1);
|
|
@@ -9,9 +9,8 @@
|
|
|
9
9
|
const fs = require('node:fs');
|
|
10
10
|
const path = require('node:path');
|
|
11
11
|
const { spawnSync } = require('node:child_process');
|
|
12
|
-
const { getProjectRoot, getConfig, error, success } = require('./flow-utils');
|
|
12
|
+
const { getProjectRoot, getConfig, error, success, PATHS } = require('./flow-utils');
|
|
13
13
|
|
|
14
|
-
const PROJECT_ROOT = getProjectRoot();
|
|
15
14
|
const TESTS = [];
|
|
16
15
|
let passed = 0;
|
|
17
16
|
let failed = 0;
|
|
@@ -45,7 +44,7 @@ async function run() {
|
|
|
45
44
|
// Tests
|
|
46
45
|
|
|
47
46
|
test('Config file exists', () => {
|
|
48
|
-
const configPath = path.join(
|
|
47
|
+
const configPath = path.join(PATHS.workflow, 'config.json');
|
|
49
48
|
if (!fs.existsSync(configPath)) {
|
|
50
49
|
throw new Error('config.json not found');
|
|
51
50
|
}
|
|
@@ -59,7 +58,7 @@ test('Config has hybrid section', () => {
|
|
|
59
58
|
});
|
|
60
59
|
|
|
61
60
|
test('Detection script exists and runs', () => {
|
|
62
|
-
const scriptPath = path.join(
|
|
61
|
+
const scriptPath = path.join(PATHS.root, 'scripts', 'flow-hybrid-detect.js');
|
|
63
62
|
if (!fs.existsSync(scriptPath)) {
|
|
64
63
|
throw new Error('flow-hybrid-detect.js not found');
|
|
65
64
|
}
|
|
@@ -77,56 +76,56 @@ test('Detection script exists and runs', () => {
|
|
|
77
76
|
});
|
|
78
77
|
|
|
79
78
|
test('Orchestrator script exists', () => {
|
|
80
|
-
const scriptPath = path.join(
|
|
79
|
+
const scriptPath = path.join(PATHS.root, 'scripts', 'flow-orchestrate.js');
|
|
81
80
|
if (!fs.existsSync(scriptPath)) {
|
|
82
81
|
throw new Error('flow-orchestrate.js not found');
|
|
83
82
|
}
|
|
84
83
|
});
|
|
85
84
|
|
|
86
85
|
test('Templates directory exists', () => {
|
|
87
|
-
const templatesDir = path.join(
|
|
86
|
+
const templatesDir = path.join(PATHS.root, 'templates', 'hybrid');
|
|
88
87
|
if (!fs.existsSync(templatesDir)) {
|
|
89
88
|
throw new Error('templates/hybrid directory not found');
|
|
90
89
|
}
|
|
91
90
|
});
|
|
92
91
|
|
|
93
92
|
test('Base template exists', () => {
|
|
94
|
-
const basePath = path.join(
|
|
93
|
+
const basePath = path.join(PATHS.root, 'templates', 'hybrid', '_base.md');
|
|
95
94
|
if (!fs.existsSync(basePath)) {
|
|
96
95
|
throw new Error('_base.md template not found');
|
|
97
96
|
}
|
|
98
97
|
});
|
|
99
98
|
|
|
100
99
|
test('Component template exists', () => {
|
|
101
|
-
const templatePath = path.join(
|
|
100
|
+
const templatePath = path.join(PATHS.root, 'templates', 'hybrid', 'create-component.md');
|
|
102
101
|
if (!fs.existsSync(templatePath)) {
|
|
103
102
|
throw new Error('create-component.md template not found');
|
|
104
103
|
}
|
|
105
104
|
});
|
|
106
105
|
|
|
107
106
|
test('State directory exists', () => {
|
|
108
|
-
const stateDir = path.join(
|
|
107
|
+
const stateDir = path.join(PATHS.workflow, 'state');
|
|
109
108
|
if (!fs.existsSync(stateDir)) {
|
|
110
109
|
throw new Error('.workflow/state directory not found');
|
|
111
110
|
}
|
|
112
111
|
});
|
|
113
112
|
|
|
114
113
|
test('Progress module exists', () => {
|
|
115
|
-
const scriptPath = path.join(
|
|
114
|
+
const scriptPath = path.join(PATHS.root, 'scripts', 'flow-progress.js');
|
|
116
115
|
if (!fs.existsSync(scriptPath)) {
|
|
117
116
|
throw new Error('flow-progress.js not found');
|
|
118
117
|
}
|
|
119
118
|
});
|
|
120
119
|
|
|
121
120
|
test('Templates module exists', () => {
|
|
122
|
-
const scriptPath = path.join(
|
|
121
|
+
const scriptPath = path.join(PATHS.root, 'scripts', 'flow-templates.js');
|
|
123
122
|
if (!fs.existsSync(scriptPath)) {
|
|
124
123
|
throw new Error('flow-templates.js not found');
|
|
125
124
|
}
|
|
126
125
|
});
|
|
127
126
|
|
|
128
127
|
test('Interactive setup exists', () => {
|
|
129
|
-
const scriptPath = path.join(
|
|
128
|
+
const scriptPath = path.join(PATHS.root, 'scripts', 'flow-hybrid-interactive.js');
|
|
130
129
|
if (!fs.existsSync(scriptPath)) {
|
|
131
130
|
throw new Error('flow-hybrid-interactive.js not found');
|
|
132
131
|
}
|
|
@@ -140,7 +139,7 @@ test('Slash command files exist', () => {
|
|
|
140
139
|
];
|
|
141
140
|
|
|
142
141
|
for (const cmd of commands) {
|
|
143
|
-
const cmdPath = path.join(
|
|
142
|
+
const cmdPath = path.join(PATHS.root, '.claude', 'commands', cmd);
|
|
144
143
|
if (!fs.existsSync(cmdPath)) {
|
|
145
144
|
throw new Error(`${cmd} not found`);
|
|
146
145
|
}
|
|
@@ -427,7 +427,7 @@ function loadRelevantTypes(projectRoot, filePath, options = {}) {
|
|
|
427
427
|
* @returns {Promise<string|null>} Formatted type information
|
|
428
428
|
*/
|
|
429
429
|
async function loadRelevantTypesWithLSP(projectRoot, filePath, options = {}) {
|
|
430
|
-
const { getConfig } = require('./flow-utils');
|
|
430
|
+
const { getConfig, PATHS } = require('./flow-utils');
|
|
431
431
|
const config = getConfig();
|
|
432
432
|
|
|
433
433
|
// Check if LSP is enabled
|
package/scripts/flow-io.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Wogi Flow - File I/O Operations
|
|
5
3
|
*
|
|
@@ -510,7 +508,7 @@ async function acquireLock(filePath, options = {}) {
|
|
|
510
508
|
if (process.env.DEBUG) {
|
|
511
509
|
console.warn(`[DEBUG] Stale lock cleanup failed: ${err.message}`);
|
|
512
510
|
}
|
|
513
|
-
await
|
|
511
|
+
await require('node:timers/promises').setTimeout(retryDelay);
|
|
514
512
|
}
|
|
515
513
|
// Try again
|
|
516
514
|
continue;
|
|
@@ -521,7 +519,7 @@ async function acquireLock(filePath, options = {}) {
|
|
|
521
519
|
const delay = exponentialBackoff
|
|
522
520
|
? retryDelay * Math.pow(2, attempt)
|
|
523
521
|
: retryDelay * (attempt + 1);
|
|
524
|
-
await
|
|
522
|
+
await require('node:timers/promises').setTimeout(delay);
|
|
525
523
|
continue;
|
|
526
524
|
}
|
|
527
525
|
}
|
|
@@ -646,6 +644,22 @@ function cleanupStaleLocks(dirPath, staleMs = CLEANUP_LOCK_STALE_MS) {
|
|
|
646
644
|
}
|
|
647
645
|
}
|
|
648
646
|
|
|
647
|
+
// ============================================================
|
|
648
|
+
// String Sanitization (for AI context injection)
|
|
649
|
+
// ============================================================
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Sanitize a string value before injecting into AI context.
|
|
653
|
+
* Strips markdown heading markers and truncates to prevent prompt manipulation.
|
|
654
|
+
*
|
|
655
|
+
* @param {string} value - Raw string from state files
|
|
656
|
+
* @param {number} [maxLen=200] - Maximum length
|
|
657
|
+
* @returns {string} Sanitized string
|
|
658
|
+
*/
|
|
659
|
+
function sanitizeForContext(value, maxLen = 200) {
|
|
660
|
+
return String(value).replace(/^#+\s/gm, '').slice(0, maxLen);
|
|
661
|
+
}
|
|
662
|
+
|
|
649
663
|
// ============================================================
|
|
650
664
|
// Exports
|
|
651
665
|
// ============================================================
|
|
@@ -689,4 +703,7 @@ module.exports = {
|
|
|
689
703
|
withLock,
|
|
690
704
|
withLockSync,
|
|
691
705
|
cleanupStaleLocks,
|
|
706
|
+
|
|
707
|
+
// String Sanitization
|
|
708
|
+
sanitizeForContext,
|
|
692
709
|
};
|
|
@@ -303,10 +303,16 @@ ${correction}
|
|
|
303
303
|
`;
|
|
304
304
|
|
|
305
305
|
content += entry;
|
|
306
|
-
fs.writeFileSync(decisionsPath, content);
|
|
307
306
|
|
|
308
|
-
//
|
|
309
|
-
|
|
307
|
+
// Route through orchestrator for locking and dedup
|
|
308
|
+
try {
|
|
309
|
+
const { writeToDecisions } = require('./flow-learning-orchestrator');
|
|
310
|
+
await writeToDecisions({ content, entryText: correction.slice(0, 100), caller: 'flow-knowledge-router', skipDedup: false, syncRules: true });
|
|
311
|
+
} catch (_err) {
|
|
312
|
+
// Fallback to direct write if orchestrator unavailable
|
|
313
|
+
fs.writeFileSync(decisionsPath, content);
|
|
314
|
+
syncDecisionsToRules();
|
|
315
|
+
}
|
|
310
316
|
|
|
311
317
|
return {
|
|
312
318
|
success: true,
|