wogiflow 2.4.2 → 2.4.4
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-start.md +124 -0
- package/.claude/docs/claude-code-compatibility.md +51 -0
- package/.claude/docs/explore-agents.md +11 -0
- package/.claude/settings.json +12 -1
- package/.workflow/models/registry.json +1 -1
- package/bin/flow +11 -1
- package/lib/workspace-contracts.js +599 -0
- package/lib/workspace-intelligence.js +600 -0
- package/lib/workspace-messages.js +441 -0
- package/lib/workspace-routing.js +485 -0
- package/lib/workspace-sync.js +339 -0
- package/lib/workspace.js +1073 -0
- 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 +28 -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-calibration.js +257 -0
- package/scripts/flow-eval-judge.js +10 -1
- package/scripts/flow-eval.js +14 -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 +31 -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/task-created.js +83 -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/task-created.js +15 -0
- 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/postinstall.js +2 -0
- 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
|
@@ -430,9 +430,7 @@ async function orchestrateBulk(taskIds, options = {}) {
|
|
|
430
430
|
* @param {number} ms - Milliseconds to sleep
|
|
431
431
|
* @returns {Promise}
|
|
432
432
|
*/
|
|
433
|
-
|
|
434
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
435
|
-
}
|
|
433
|
+
const { setTimeout: sleep } = require('node:timers/promises');
|
|
436
434
|
|
|
437
435
|
/**
|
|
438
436
|
* Get ready tasks from ready.json
|
package/scripts/flow-cascade.js
CHANGED
|
@@ -28,9 +28,10 @@ const {
|
|
|
28
28
|
getConfig,
|
|
29
29
|
fileExists,
|
|
30
30
|
writeJson,
|
|
31
|
+
safeJsonParse,
|
|
31
32
|
printHeader,
|
|
32
33
|
printSection,
|
|
33
|
-
showHelp: showHelpGeneric
|
|
34
|
+
showHelp: showHelpGeneric, PATHS
|
|
34
35
|
} = require('./flow-utils');
|
|
35
36
|
|
|
36
37
|
// ============================================================
|
|
@@ -92,7 +93,7 @@ const CATEGORY_PATTERNS = [
|
|
|
92
93
|
// State Management
|
|
93
94
|
// ============================================================
|
|
94
95
|
|
|
95
|
-
const STATE_PATH = path.join(
|
|
96
|
+
const STATE_PATH = path.join(PATHS.state, 'cascade-state.json');
|
|
96
97
|
|
|
97
98
|
/**
|
|
98
99
|
* In-memory failure tracker.
|
|
@@ -113,8 +114,7 @@ function loadState() {
|
|
|
113
114
|
}
|
|
114
115
|
|
|
115
116
|
try {
|
|
116
|
-
const
|
|
117
|
-
const state = JSON.parse(content);
|
|
117
|
+
const state = safeJsonParse(STATE_PATH, {});
|
|
118
118
|
|
|
119
119
|
// Clean up expired entries
|
|
120
120
|
const now = Date.now();
|
|
@@ -23,12 +23,10 @@
|
|
|
23
23
|
const fs = require('node:fs');
|
|
24
24
|
const path = require('node:path');
|
|
25
25
|
const { execSync, spawnSync } = require('node:child_process');
|
|
26
|
-
const { getProjectRoot, getConfig, colors: c } = require('./flow-utils');
|
|
26
|
+
const { getProjectRoot, getConfig, colors: c, PATHS } = require('./flow-utils');
|
|
27
27
|
const { success: printSuccess, warn: printWarn } = require('./flow-output');
|
|
28
28
|
|
|
29
|
-
const
|
|
30
|
-
const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
|
|
31
|
-
const CHECKPOINTS_DIR = path.join(WORKFLOW_DIR, 'checkpoints');
|
|
29
|
+
const CHECKPOINTS_DIR = PATHS.checkpoints;
|
|
32
30
|
const CHECKPOINT_LOG = path.join(CHECKPOINTS_DIR, 'checkpoint-log.json');
|
|
33
31
|
|
|
34
32
|
// Alias getConfig as loadConfig for minimal code changes
|
|
@@ -182,7 +180,7 @@ class Checkpoint {
|
|
|
182
180
|
const snapshots = {};
|
|
183
181
|
|
|
184
182
|
for (const relPath of stateFiles) {
|
|
185
|
-
const srcPath = path.join(
|
|
183
|
+
const srcPath = path.join(PATHS.workflow, relPath);
|
|
186
184
|
try {
|
|
187
185
|
const content = fs.readFileSync(srcPath, 'utf-8');
|
|
188
186
|
const destPath = path.join(snapshotDir, relPath.replace(/\//g, '_'));
|
|
@@ -205,7 +203,7 @@ class Checkpoint {
|
|
|
205
203
|
hasGitChanges() {
|
|
206
204
|
try {
|
|
207
205
|
const result = spawnSync('git', ['status', '--porcelain'], {
|
|
208
|
-
cwd:
|
|
206
|
+
cwd: PATHS.root,
|
|
209
207
|
encoding: 'utf-8'
|
|
210
208
|
});
|
|
211
209
|
return result.stdout && result.stdout.trim().length > 0;
|
|
@@ -220,19 +218,19 @@ class Checkpoint {
|
|
|
220
218
|
createGitCommit(message) {
|
|
221
219
|
try {
|
|
222
220
|
// Stage all changes
|
|
223
|
-
spawnSync('git', ['add', '-A'], { cwd:
|
|
221
|
+
spawnSync('git', ['add', '-A'], { cwd: PATHS.root });
|
|
224
222
|
|
|
225
223
|
// Create commit
|
|
226
224
|
const commitMessage = `${this.config.commitPrefix} ${message}`;
|
|
227
225
|
const result = spawnSync('git', ['commit', '-m', commitMessage], {
|
|
228
|
-
cwd:
|
|
226
|
+
cwd: PATHS.root,
|
|
229
227
|
encoding: 'utf-8'
|
|
230
228
|
});
|
|
231
229
|
|
|
232
230
|
if (result.status === 0) {
|
|
233
231
|
// Get commit hash
|
|
234
232
|
const hashResult = spawnSync('git', ['rev-parse', '--short', 'HEAD'], {
|
|
235
|
-
cwd:
|
|
233
|
+
cwd: PATHS.root,
|
|
236
234
|
encoding: 'utf-8'
|
|
237
235
|
});
|
|
238
236
|
return hashResult.stdout.trim();
|
|
@@ -264,7 +262,7 @@ class Checkpoint {
|
|
|
264
262
|
for (const [relPath, snapshotPath] of Object.entries(checkpoint.stateSnapshot)) {
|
|
265
263
|
try {
|
|
266
264
|
const content = fs.readFileSync(snapshotPath, 'utf-8');
|
|
267
|
-
const destPath = path.join(
|
|
265
|
+
const destPath = path.join(PATHS.workflow, relPath);
|
|
268
266
|
fs.writeFileSync(destPath, content);
|
|
269
267
|
} catch (err) {
|
|
270
268
|
if (err.code !== 'ENOENT') {
|
|
@@ -284,14 +282,14 @@ class Checkpoint {
|
|
|
284
282
|
// Find commits since checkpoint
|
|
285
283
|
const logResult = spawnSync('git', [
|
|
286
284
|
'log', '--oneline', `${checkpoint.gitCommit}..HEAD`
|
|
287
|
-
], { cwd:
|
|
285
|
+
], { cwd: PATHS.root, encoding: 'utf-8' });
|
|
288
286
|
|
|
289
287
|
const commitsSince = logResult.stdout.trim().split('\n').filter(l => l).length;
|
|
290
288
|
|
|
291
289
|
if (commitsSince > 0) {
|
|
292
290
|
// Soft reset to checkpoint
|
|
293
291
|
spawnSync('git', ['reset', '--soft', checkpoint.gitCommit], {
|
|
294
|
-
cwd:
|
|
292
|
+
cwd: PATHS.root
|
|
295
293
|
});
|
|
296
294
|
results.gitRestored = true;
|
|
297
295
|
}
|
|
@@ -354,7 +352,6 @@ class Checkpoint {
|
|
|
354
352
|
}
|
|
355
353
|
}
|
|
356
354
|
|
|
357
|
-
|
|
358
355
|
/**
|
|
359
356
|
* Format checkpoint list for display
|
|
360
357
|
*/
|
|
@@ -22,15 +22,13 @@ const { getProjectRoot, getConfig, PATHS, colors, readJson } = require('./flow-u
|
|
|
22
22
|
const { success, error: errorMsg } = require('./flow-output');
|
|
23
23
|
const { safeGrep, safeFind, escapeRegex } = require('./flow-security');
|
|
24
24
|
|
|
25
|
-
const PROJECT_ROOT = getProjectRoot();
|
|
26
|
-
|
|
27
25
|
/**
|
|
28
26
|
* Resolve a path alias (e.g., '@/*') from tsconfig.json compilerOptions.paths.
|
|
29
27
|
* Returns the base directory (e.g., 'src') or null if not found.
|
|
30
28
|
*/
|
|
31
29
|
function resolvePathAlias(alias) {
|
|
32
30
|
try {
|
|
33
|
-
const tsconfigPath = path.join(
|
|
31
|
+
const tsconfigPath = path.join(PATHS.root, 'tsconfig.json');
|
|
34
32
|
if (!fs.existsSync(tsconfigPath)) return null;
|
|
35
33
|
const content = fs.readFileSync(tsconfigPath, 'utf-8');
|
|
36
34
|
// Strip comments then use safeJsonParseString for prototype-pollution protection
|
|
@@ -51,7 +49,7 @@ function resolvePathAlias(alias) {
|
|
|
51
49
|
*/
|
|
52
50
|
function detectSourceRoot() {
|
|
53
51
|
for (const dir of ['src', 'app', 'lib']) {
|
|
54
|
-
if (fs.existsSync(path.join(
|
|
52
|
+
if (fs.existsSync(path.join(PATHS.root, dir))) return dir;
|
|
55
53
|
}
|
|
56
54
|
return '.'; // fallback to project root
|
|
57
55
|
}
|
|
@@ -69,14 +67,14 @@ function detectSourceRoot() {
|
|
|
69
67
|
function analyzeRelationships(filePath) {
|
|
70
68
|
const fullPath = path.isAbsolute(filePath)
|
|
71
69
|
? filePath
|
|
72
|
-
: path.join(
|
|
70
|
+
: path.join(PATHS.root, filePath);
|
|
73
71
|
|
|
74
72
|
if (!fs.existsSync(fullPath)) {
|
|
75
73
|
return { error: 'File not found' };
|
|
76
74
|
}
|
|
77
75
|
|
|
78
76
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
79
|
-
const relPath = path.relative(
|
|
77
|
+
const relPath = path.relative(PATHS.root, fullPath);
|
|
80
78
|
|
|
81
79
|
const relationships = {
|
|
82
80
|
file: relPath,
|
|
@@ -371,10 +369,10 @@ function resolveImportPath(importSource, fromDir) {
|
|
|
371
369
|
for (const ext of extensions) {
|
|
372
370
|
const candidate = path.resolve(fromDir, importSource + ext);
|
|
373
371
|
// Path containment check — reject paths that escape project root
|
|
374
|
-
if (!candidate.startsWith(
|
|
375
|
-
const relPath = path.relative(
|
|
372
|
+
if (!candidate.startsWith(PATHS.root + path.sep) && candidate !== PATHS.root) continue;
|
|
373
|
+
const relPath = path.relative(PATHS.root, candidate);
|
|
376
374
|
|
|
377
|
-
if (fs.existsSync(path.join(
|
|
375
|
+
if (fs.existsSync(path.join(PATHS.root, relPath))) {
|
|
378
376
|
return relPath;
|
|
379
377
|
}
|
|
380
378
|
}
|
|
@@ -430,7 +428,7 @@ async function findFilesImporting(filePath) {
|
|
|
430
428
|
// Use safe grep with escaped pattern to prevent injection
|
|
431
429
|
const pattern = `from.*${escapeRegex(basename)}`;
|
|
432
430
|
return safeGrep(pattern, {
|
|
433
|
-
cwd:
|
|
431
|
+
cwd: PATHS.root,
|
|
434
432
|
searchDir: 'src/',
|
|
435
433
|
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
|
436
434
|
maxResults: 20
|
|
@@ -443,7 +441,7 @@ async function findFilesImporting(filePath) {
|
|
|
443
441
|
async function searchCodebase(keyword, maxResults = 10) {
|
|
444
442
|
// Use safe grep with escaped pattern to prevent injection
|
|
445
443
|
return safeGrep(keyword, {
|
|
446
|
-
cwd:
|
|
444
|
+
cwd: PATHS.root,
|
|
447
445
|
searchDir: 'src/',
|
|
448
446
|
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
|
449
447
|
maxResults
|
|
@@ -678,7 +676,7 @@ async function main() {
|
|
|
678
676
|
|
|
679
677
|
// Use safe find to prevent command injection
|
|
680
678
|
const files = safeFind(dir, {
|
|
681
|
-
cwd:
|
|
679
|
+
cwd: PATHS.root,
|
|
682
680
|
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
|
683
681
|
maxResults: 100
|
|
684
682
|
});
|
|
@@ -31,9 +31,9 @@ const { loadStats } = require('./flow-stats-collector');
|
|
|
31
31
|
// Constants
|
|
32
32
|
// ============================================================
|
|
33
33
|
|
|
34
|
-
const COMMUNITY_SCORES_PATH =
|
|
35
|
-
const COMMUNITY_ROUTING_PATH =
|
|
36
|
-
const SYNC_QUEUE_PATH = path.join(PATHS.
|
|
34
|
+
const COMMUNITY_SCORES_PATH = PATHS.communityScores;
|
|
35
|
+
const COMMUNITY_ROUTING_PATH = PATHS.communityRouting;
|
|
36
|
+
const SYNC_QUEUE_PATH = path.join(PATHS.state, 'sync-queue.json');
|
|
37
37
|
|
|
38
38
|
const DEFAULT_SYNC_CONFIG = {
|
|
39
39
|
enabled: false,
|
|
@@ -78,7 +78,7 @@ function isSyncEnabled() {
|
|
|
78
78
|
if (!config.enabled) return false;
|
|
79
79
|
|
|
80
80
|
// Check for auth token (set by `wogi login`)
|
|
81
|
-
const authPath = path.join(PATHS.
|
|
81
|
+
const authPath = path.join(PATHS.state, 'auth.json');
|
|
82
82
|
try {
|
|
83
83
|
if (!fs.existsSync(authPath)) return false;
|
|
84
84
|
const auth = readJson(authPath, {});
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Wogi Flow - Community Knowledge Module
|
|
5
3
|
*
|
|
@@ -1092,25 +1090,19 @@ function mergePatterns(items) {
|
|
|
1092
1090
|
}
|
|
1093
1091
|
|
|
1094
1092
|
if (newRows.length > 0) {
|
|
1095
|
-
//
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
if (process.env.DEBUG) {
|
|
1103
|
-
console.error(`[flow-community] Failed to write feedback-patterns.md: ${err.message}`);
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
} else {
|
|
1107
|
-
// Append at end
|
|
1108
|
-
try {
|
|
1109
|
-
fs.writeFileSync(filePath, content.trimEnd() + '\n' + newRows.join('\n') + '\n', 'utf-8');
|
|
1110
|
-
} catch (err) {
|
|
1111
|
-
if (process.env.DEBUG) {
|
|
1112
|
-
console.error(`[flow-community] Failed to write feedback-patterns.md: ${err.message}`);
|
|
1093
|
+
// Route through orchestrator for locking and dedup
|
|
1094
|
+
try {
|
|
1095
|
+
const { modifyFeedbackPatterns } = require('./flow-learning-orchestrator');
|
|
1096
|
+
modifyFeedbackPatterns((curContent) => {
|
|
1097
|
+
const tableEnd = curContent.indexOf('\n\n### ');
|
|
1098
|
+
if (tableEnd !== -1) {
|
|
1099
|
+
return { content: curContent.slice(0, tableEnd) + '\n' + newRows.join('\n') + curContent.slice(tableEnd) };
|
|
1113
1100
|
}
|
|
1101
|
+
return { content: curContent.trimEnd() + '\n' + newRows.join('\n') + '\n' };
|
|
1102
|
+
}, { caller: 'flow-community/mergePatterns', skipDedup: true });
|
|
1103
|
+
} catch (err) {
|
|
1104
|
+
if (process.env.DEBUG) {
|
|
1105
|
+
console.error(`[flow-community] Failed to write feedback-patterns.md: ${err.message}`);
|
|
1114
1106
|
}
|
|
1115
1107
|
}
|
|
1116
1108
|
}
|
|
@@ -27,8 +27,6 @@
|
|
|
27
27
|
* bulkLoop, corrections, agents (top-level list)
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
-
'use strict';
|
|
31
|
-
|
|
32
30
|
// ============================================================
|
|
33
31
|
// Static Data Constants (large objects extracted for readability)
|
|
34
32
|
// ============================================================
|
|
@@ -575,6 +573,34 @@ const CONFIG_DEFAULTS = {
|
|
|
575
573
|
failureThresholdForFallback: 3
|
|
576
574
|
},
|
|
577
575
|
|
|
576
|
+
// --- Skeptical Evaluator (Anthropic harness design pattern) ---
|
|
577
|
+
// Spawns a separate sub-agent to evaluate task output before quality gates.
|
|
578
|
+
// Addresses "confident praise bias" where the implementer always thinks it did well.
|
|
579
|
+
skepticalEvaluator: {
|
|
580
|
+
enabled: true,
|
|
581
|
+
_comment_enabled: 'Spawn a separate evaluator agent between Step 3.5 and Step 4',
|
|
582
|
+
maxIterations: 3,
|
|
583
|
+
_comment_maxIterations: 'Max eval→fix cycles before proceeding anyway',
|
|
584
|
+
model: 'sonnet',
|
|
585
|
+
_comment_model: 'Use a different model than the implementer for diversity',
|
|
586
|
+
calibration: true,
|
|
587
|
+
_comment_calibration: 'Inject few-shot calibration examples into evaluator prompt',
|
|
588
|
+
skipForL3: true,
|
|
589
|
+
_comment_skipForL3: 'Skip for trivial L3 subtasks'
|
|
590
|
+
},
|
|
591
|
+
|
|
592
|
+
// --- Sprint-Based Context Reset (Anthropic harness design pattern) ---
|
|
593
|
+
// For large tasks (5+ criteria), commit and reset context every N criteria.
|
|
594
|
+
// Fresh context per sprint prevents quality degradation on later criteria.
|
|
595
|
+
sprintReset: {
|
|
596
|
+
enabled: true,
|
|
597
|
+
_comment_enabled: 'Enable sprint-based context resets for large tasks',
|
|
598
|
+
criteriaPerSprint: 3,
|
|
599
|
+
_comment_criteriaPerSprint: 'Number of criteria to complete before a context reset',
|
|
600
|
+
minTaskCriteria: 5,
|
|
601
|
+
_comment_minTaskCriteria: 'Only activate for tasks with this many or more criteria'
|
|
602
|
+
},
|
|
603
|
+
|
|
578
604
|
// --- Session Features ---
|
|
579
605
|
morningBriefing: { enabled: false },
|
|
580
606
|
techDebt: {
|
|
@@ -25,7 +25,8 @@ const {
|
|
|
25
25
|
warn,
|
|
26
26
|
error,
|
|
27
27
|
parseFlags,
|
|
28
|
-
outputJson
|
|
28
|
+
outputJson,
|
|
29
|
+
safeJsonParse
|
|
29
30
|
} = require('./flow-utils');
|
|
30
31
|
|
|
31
32
|
// ============================================================
|
|
@@ -220,11 +221,14 @@ function exportConfig() {
|
|
|
220
221
|
}
|
|
221
222
|
|
|
222
223
|
function resetConfig() {
|
|
223
|
-
const configPath =
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
224
|
+
const configPath = PATHS.config;
|
|
225
|
+
const config = safeJsonParse(configPath, null);
|
|
226
|
+
if (!config) {
|
|
227
|
+
error('Failed to read config.json');
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
227
230
|
|
|
231
|
+
try {
|
|
228
232
|
// Keep only structural keys, remove user overrides
|
|
229
233
|
const minimal = {
|
|
230
234
|
$schema: config.$schema,
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Wogi Flow - Config Loading and Management
|
|
5
3
|
*
|
|
@@ -13,7 +11,7 @@
|
|
|
13
11
|
const fs = require('node:fs');
|
|
14
12
|
const path = require('node:path');
|
|
15
13
|
const { PATHS, PROJECT_ROOT, isPathWithinProject } = require('./flow-paths');
|
|
16
|
-
const { checkForDangerousKeys, readJson, writeJson, acquireLock } = require('./flow-io');
|
|
14
|
+
const { checkForDangerousKeys, readJson, writeJson, acquireLock, safeJsonParse, safeJsonParseString } = require('./flow-io');
|
|
17
15
|
const { warn } = require('./flow-output');
|
|
18
16
|
|
|
19
17
|
// Late-loaded to avoid circular dependency
|
|
@@ -296,23 +294,12 @@ function getConfig() {
|
|
|
296
294
|
}
|
|
297
295
|
|
|
298
296
|
const configContent = fs.readFileSync(configPath, 'utf-8');
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
} catch (err) {
|
|
303
|
-
console.warn(`Warning: Invalid JSON in config.json: ${err.message}`);
|
|
297
|
+
const rawConfig = safeJsonParseString(configContent, null);
|
|
298
|
+
if (!rawConfig || typeof rawConfig !== 'object') {
|
|
299
|
+
console.warn('Warning: Invalid or dangerous JSON in config.json');
|
|
304
300
|
return {};
|
|
305
301
|
}
|
|
306
302
|
|
|
307
|
-
// Prototype pollution check on config
|
|
308
|
-
if (rawConfig && typeof rawConfig === 'object') {
|
|
309
|
-
const dangerousKeyError = checkForDangerousKeys(rawConfig);
|
|
310
|
-
if (dangerousKeyError) {
|
|
311
|
-
console.warn(`Warning: Dangerous keys in config.json: ${dangerousKeyError}`);
|
|
312
|
-
return {};
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
303
|
// Validate on first load (DEBUG mode or explicit request)
|
|
317
304
|
if (process.env.DEBUG || process.env.VALIDATE_CONFIG) {
|
|
318
305
|
validateConfig(rawConfig);
|
|
@@ -366,27 +353,7 @@ function getConfig() {
|
|
|
366
353
|
* Use this when you need to read/modify config without resolving variables
|
|
367
354
|
*/
|
|
368
355
|
function getRawConfig() {
|
|
369
|
-
|
|
370
|
-
if (!fs.existsSync(configPath)) return {};
|
|
371
|
-
|
|
372
|
-
try {
|
|
373
|
-
const content = fs.readFileSync(configPath, 'utf-8');
|
|
374
|
-
const parsed = JSON.parse(content);
|
|
375
|
-
|
|
376
|
-
// Prototype pollution check
|
|
377
|
-
if (parsed && typeof parsed === 'object') {
|
|
378
|
-
const dangerousKeyError = checkForDangerousKeys(parsed);
|
|
379
|
-
if (dangerousKeyError) {
|
|
380
|
-
console.warn(`Warning: Dangerous keys in config.json: ${dangerousKeyError}`);
|
|
381
|
-
return {};
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
return parsed;
|
|
386
|
-
} catch (err) {
|
|
387
|
-
console.warn(`Warning: Could not parse config.json: ${err.message}`);
|
|
388
|
-
return {};
|
|
389
|
-
}
|
|
356
|
+
return safeJsonParse(PATHS.config, {});
|
|
390
357
|
}
|
|
391
358
|
|
|
392
359
|
/**
|
|
@@ -439,6 +406,48 @@ function getConfigValue(configPath, defaultValue = null) {
|
|
|
439
406
|
return value;
|
|
440
407
|
}
|
|
441
408
|
|
|
409
|
+
/**
|
|
410
|
+
* Apply a value at a dot-notation path on a config object.
|
|
411
|
+
* Shared logic between setConfigValue (async, locked) and setConfigValueSync.
|
|
412
|
+
*
|
|
413
|
+
* @param {string} configPath - Dot-notation path (e.g., 'parallel.enabled')
|
|
414
|
+
* @param {*} newValue - New value to set
|
|
415
|
+
* @param {object} config - Config object to mutate
|
|
416
|
+
*/
|
|
417
|
+
function _applyConfigPath(configPath, newValue, config) {
|
|
418
|
+
const parts = configPath.split('.');
|
|
419
|
+
let obj = config;
|
|
420
|
+
|
|
421
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
422
|
+
const part = parts[i];
|
|
423
|
+
if (DANGEROUS_CONFIG_PROPS.has(part)) {
|
|
424
|
+
throw new Error(`Dangerous config key: ${part}`);
|
|
425
|
+
}
|
|
426
|
+
if (!Object.hasOwn(obj, part)) {
|
|
427
|
+
obj[part] = {};
|
|
428
|
+
}
|
|
429
|
+
obj = obj[part];
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const lastPart = parts[parts.length - 1];
|
|
433
|
+
if (DANGEROUS_CONFIG_PROPS.has(lastPart)) {
|
|
434
|
+
throw new Error(`Dangerous config key: ${lastPart}`);
|
|
435
|
+
}
|
|
436
|
+
obj[lastPart] = newValue;
|
|
437
|
+
writeJson(PATHS.config, config);
|
|
438
|
+
invalidateConfigCache();
|
|
439
|
+
|
|
440
|
+
// Auto-sync .gitignore only when config keys affect gitignore mappings
|
|
441
|
+
if (configPath.startsWith('testing.') || configPath.startsWith('webmcp.')) {
|
|
442
|
+
try {
|
|
443
|
+
const { syncGitignore } = require('./flow-gitignore');
|
|
444
|
+
syncGitignore(config);
|
|
445
|
+
} catch (_err) {
|
|
446
|
+
// Non-blocking — gitignore sync should never fail config writes
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
442
451
|
/**
|
|
443
452
|
* Update config value (uses locking to prevent race conditions)
|
|
444
453
|
* SECURITY: Always acquires lock before writing to prevent data corruption
|
|
@@ -448,51 +457,23 @@ function getConfigValue(configPath, defaultValue = null) {
|
|
|
448
457
|
* @throws {Error} If lock cannot be acquired after retries
|
|
449
458
|
*/
|
|
450
459
|
async function setConfigValue(configPath, newValue) {
|
|
451
|
-
// Validate path to prevent prototype pollution
|
|
452
460
|
if (!isValidConfigPath(configPath)) {
|
|
453
461
|
throw new Error(`Invalid config path: ${configPath}`);
|
|
454
462
|
}
|
|
455
463
|
|
|
456
|
-
// Use file lock to prevent concurrent writes
|
|
457
464
|
const lockPath = PATHS.config;
|
|
458
465
|
let release;
|
|
459
466
|
|
|
460
467
|
try {
|
|
461
|
-
// More retries with exponential backoff for better reliability
|
|
462
468
|
release = await acquireLock(lockPath, { retries: 5, retryDelay: 100, exponentialBackoff: true });
|
|
463
469
|
} catch (err) {
|
|
464
|
-
// SECURITY: Don't fall back to non-locked write - throw instead
|
|
465
470
|
throw new Error(`Could not acquire config lock after retries: ${err.message}. Config not updated.`, { cause: err });
|
|
466
471
|
}
|
|
467
472
|
|
|
468
473
|
try {
|
|
469
|
-
// Re-read config after acquiring lock (may have changed)
|
|
470
474
|
invalidateConfigCache();
|
|
471
475
|
const config = getConfig();
|
|
472
|
-
|
|
473
|
-
let obj = config;
|
|
474
|
-
|
|
475
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
476
|
-
const part = parts[i];
|
|
477
|
-
if (!Object.hasOwn(obj, part)) {
|
|
478
|
-
obj[part] = {};
|
|
479
|
-
}
|
|
480
|
-
obj = obj[part];
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
obj[parts[parts.length - 1]] = newValue;
|
|
484
|
-
writeJson(PATHS.config, config);
|
|
485
|
-
invalidateConfigCache();
|
|
486
|
-
|
|
487
|
-
// Auto-sync .gitignore only when config keys affect gitignore mappings
|
|
488
|
-
if (configPath.startsWith('testing.') || configPath.startsWith('webmcp.')) {
|
|
489
|
-
try {
|
|
490
|
-
const { syncGitignore } = require('./flow-gitignore');
|
|
491
|
-
syncGitignore(config);
|
|
492
|
-
} catch (err) {
|
|
493
|
-
// Non-blocking — gitignore sync should never fail config writes
|
|
494
|
-
}
|
|
495
|
-
}
|
|
476
|
+
_applyConfigPath(configPath, newValue, config);
|
|
496
477
|
} finally {
|
|
497
478
|
if (release) release();
|
|
498
479
|
}
|
|
@@ -503,36 +484,12 @@ async function setConfigValue(configPath, newValue) {
|
|
|
503
484
|
* Use setConfigValue for concurrent-safe writes
|
|
504
485
|
*/
|
|
505
486
|
function setConfigValueSync(configPath, newValue) {
|
|
506
|
-
// Validate path to prevent prototype pollution
|
|
507
487
|
if (!isValidConfigPath(configPath)) {
|
|
508
488
|
throw new Error(`Invalid config path: ${configPath}`);
|
|
509
489
|
}
|
|
510
490
|
|
|
511
491
|
const config = getConfig();
|
|
512
|
-
|
|
513
|
-
let obj = config;
|
|
514
|
-
|
|
515
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
516
|
-
const part = parts[i];
|
|
517
|
-
if (!Object.hasOwn(obj, part)) {
|
|
518
|
-
obj[part] = {};
|
|
519
|
-
}
|
|
520
|
-
obj = obj[part];
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
obj[parts[parts.length - 1]] = newValue;
|
|
524
|
-
writeJson(PATHS.config, config);
|
|
525
|
-
invalidateConfigCache();
|
|
526
|
-
|
|
527
|
-
// Auto-sync .gitignore only when config keys affect gitignore mappings
|
|
528
|
-
if (configPath.startsWith('testing.') || configPath.startsWith('webmcp.')) {
|
|
529
|
-
try {
|
|
530
|
-
const { syncGitignore } = require('./flow-gitignore');
|
|
531
|
-
syncGitignore(config);
|
|
532
|
-
} catch (err) {
|
|
533
|
-
// Non-blocking — gitignore sync should never fail config writes
|
|
534
|
-
}
|
|
535
|
-
}
|
|
492
|
+
_applyConfigPath(configPath, newValue, config);
|
|
536
493
|
}
|
|
537
494
|
|
|
538
495
|
/**
|
|
@@ -111,7 +111,7 @@ function readSpecFile(taskId) {
|
|
|
111
111
|
return null;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
const specPath = path.join(PATHS.
|
|
114
|
+
const specPath = path.join(PATHS.specs, `${taskId}.md`);
|
|
115
115
|
|
|
116
116
|
// Use try-catch only, no existsSync (prevents TOCTOU race condition)
|
|
117
117
|
try {
|
|
@@ -127,7 +127,7 @@ function readSpecFile(taskId) {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
// Also check changes directory
|
|
130
|
-
const changesDir =
|
|
130
|
+
const changesDir = PATHS.changes;
|
|
131
131
|
try {
|
|
132
132
|
const entries = fs.readdirSync(changesDir, { withFileTypes: true });
|
|
133
133
|
for (const entry of entries) {
|
|
@@ -499,7 +499,7 @@ if (require.main === module) {
|
|
|
499
499
|
process.exit(1);
|
|
500
500
|
}
|
|
501
501
|
|
|
502
|
-
const readyPath =
|
|
502
|
+
const readyPath = PATHS.ready;
|
|
503
503
|
const readyData = safeJsonParse(readyPath, { ready: [], inProgress: [] });
|
|
504
504
|
|
|
505
505
|
const allTasks = [
|
|
@@ -546,7 +546,7 @@ if (require.main === module) {
|
|
|
546
546
|
|
|
547
547
|
const currentPercent = parseFloat(args[2]) / 100;
|
|
548
548
|
|
|
549
|
-
const readyPath =
|
|
549
|
+
const readyPath = PATHS.ready;
|
|
550
550
|
const readyData = safeJsonParse(readyPath, { ready: [], inProgress: [] });
|
|
551
551
|
|
|
552
552
|
const allTasks = [
|