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.
Files changed (210) hide show
  1. package/.claude/commands/wogi-start.md +124 -0
  2. package/.claude/docs/claude-code-compatibility.md +51 -0
  3. package/.claude/docs/explore-agents.md +11 -0
  4. package/.claude/settings.json +12 -1
  5. package/.workflow/models/registry.json +1 -1
  6. package/bin/flow +11 -1
  7. package/lib/workspace-contracts.js +599 -0
  8. package/lib/workspace-intelligence.js +600 -0
  9. package/lib/workspace-messages.js +441 -0
  10. package/lib/workspace-routing.js +485 -0
  11. package/lib/workspace-sync.js +339 -0
  12. package/lib/workspace.js +1073 -0
  13. package/package.json +4 -4
  14. package/scripts/MEMORY-ARCHITECTURE.md +1 -1
  15. package/scripts/base-workflow-step.js +136 -0
  16. package/scripts/flow-adaptive-learning.js +8 -9
  17. package/scripts/flow-aggregate.js +11 -6
  18. package/scripts/flow-api-index.js +4 -6
  19. package/scripts/flow-assumption-detector.js +0 -2
  20. package/scripts/flow-audit.js +15 -2
  21. package/scripts/flow-auto-context.js +8 -12
  22. package/scripts/flow-auto-learn.js +49 -49
  23. package/scripts/flow-background.js +5 -6
  24. package/scripts/flow-bridge-state.js +8 -10
  25. package/scripts/flow-bulk-loop.js +1 -3
  26. package/scripts/flow-bulk-orchestrator.js +1 -3
  27. package/scripts/flow-cascade-completion.js +0 -2
  28. package/scripts/flow-cascade.js +4 -4
  29. package/scripts/flow-checkpoint.js +10 -13
  30. package/scripts/flow-code-intelligence.js +10 -12
  31. package/scripts/flow-community-sync.js +4 -4
  32. package/scripts/flow-community.js +12 -20
  33. package/scripts/flow-config-defaults.js +28 -2
  34. package/scripts/flow-config-interactive.js +9 -5
  35. package/scripts/flow-config-loader.js +49 -92
  36. package/scripts/flow-config-substitution.js +0 -2
  37. package/scripts/flow-context-estimator.js +4 -4
  38. package/scripts/flow-context-init.js +10 -12
  39. package/scripts/flow-context-manager.js +0 -2
  40. package/scripts/flow-context-scoring.js +2 -2
  41. package/scripts/flow-contract-scan.js +6 -9
  42. package/scripts/flow-correct.js +29 -27
  43. package/scripts/flow-correction-detector.js +5 -1
  44. package/scripts/flow-damage-control.js +47 -54
  45. package/scripts/flow-decisions-merge.js +4 -14
  46. package/scripts/flow-diff.js +5 -8
  47. package/scripts/flow-done-gates.js +786 -0
  48. package/scripts/flow-done-report.js +123 -0
  49. package/scripts/flow-done.js +71 -717
  50. package/scripts/flow-entropy-monitor.js +1 -3
  51. package/scripts/flow-eval-calibration.js +257 -0
  52. package/scripts/flow-eval-judge.js +10 -1
  53. package/scripts/flow-eval.js +14 -5
  54. package/scripts/flow-extraction-review.js +1 -0
  55. package/scripts/flow-failure-categories.js +0 -2
  56. package/scripts/flow-figma-confirm.js +5 -9
  57. package/scripts/flow-figma-generate.js +8 -10
  58. package/scripts/flow-figma-index.js +8 -10
  59. package/scripts/flow-figma-match.js +3 -5
  60. package/scripts/flow-figma-mcp-server.js +2 -4
  61. package/scripts/flow-figma-orchestrator.js +2 -3
  62. package/scripts/flow-figma-registry.js +2 -3
  63. package/scripts/flow-framework-resolver.js +0 -2
  64. package/scripts/flow-function-index.js +4 -6
  65. package/scripts/flow-gate-confidence.js +2 -2
  66. package/scripts/flow-gitignore.js +0 -2
  67. package/scripts/flow-guided-edit.js +5 -6
  68. package/scripts/flow-health.js +5 -6
  69. package/scripts/flow-hook-errors.js +6 -0
  70. package/scripts/flow-hook-status.js +263 -0
  71. package/scripts/flow-hooks.js +17 -29
  72. package/scripts/flow-http-client.js +9 -8
  73. package/scripts/flow-hybrid-interactive.js +7 -12
  74. package/scripts/flow-hybrid-test.js +12 -13
  75. package/scripts/flow-instruction-richness.js +1 -1
  76. package/scripts/flow-io.js +21 -4
  77. package/scripts/flow-knowledge-router.js +9 -3
  78. package/scripts/flow-learning-orchestrator.js +318 -13
  79. package/scripts/flow-links.js +5 -7
  80. package/scripts/flow-long-input-association.js +275 -0
  81. package/scripts/flow-long-input-chunking.js +1 -0
  82. package/scripts/flow-long-input-cli.js +0 -2
  83. package/scripts/flow-long-input-complexity.js +0 -2
  84. package/scripts/flow-long-input-constants.js +0 -2
  85. package/scripts/flow-long-input-contradictions.js +351 -0
  86. package/scripts/flow-long-input-detection.js +0 -2
  87. package/scripts/flow-long-input-passes.js +885 -0
  88. package/scripts/flow-long-input-stories.js +1 -1
  89. package/scripts/flow-long-input-voice.js +0 -2
  90. package/scripts/flow-long-input.js +425 -3005
  91. package/scripts/flow-loop-retry-learning.js +2 -3
  92. package/scripts/flow-lsp.js +3 -3
  93. package/scripts/flow-mcp-docs.js +3 -4
  94. package/scripts/flow-memory-db.js +6 -8
  95. package/scripts/flow-memory-sync.js +18 -11
  96. package/scripts/flow-metrics.js +1 -2
  97. package/scripts/flow-model-adapter.js +2 -3
  98. package/scripts/flow-model-config.js +72 -104
  99. package/scripts/flow-model-router.js +2 -2
  100. package/scripts/flow-model-types.js +0 -2
  101. package/scripts/flow-multi-approach.js +5 -6
  102. package/scripts/flow-orchestrate-context.js +3 -7
  103. package/scripts/flow-orchestrate-rollback.js +3 -8
  104. package/scripts/flow-orchestrate-state.js +8 -14
  105. package/scripts/flow-orchestrate-templates.js +2 -6
  106. package/scripts/flow-orchestrate-validator.js +5 -9
  107. package/scripts/flow-orchestrate.js +126 -103
  108. package/scripts/flow-output.js +0 -2
  109. package/scripts/flow-parallel.js +1 -1
  110. package/scripts/flow-paths.js +23 -2
  111. package/scripts/flow-pattern-enforcer.js +30 -28
  112. package/scripts/flow-pattern-extractor.js +3 -4
  113. package/scripts/flow-pending.js +0 -2
  114. package/scripts/flow-permissions.js +2 -3
  115. package/scripts/flow-plugin-registry.js +10 -12
  116. package/scripts/flow-prd-manager.js +1 -1
  117. package/scripts/flow-progress.js +7 -9
  118. package/scripts/flow-prompt-composer.js +3 -3
  119. package/scripts/flow-prompt-template.js +2 -2
  120. package/scripts/flow-providers.js +7 -4
  121. package/scripts/flow-registry-manager.js +7 -12
  122. package/scripts/flow-regression.js +9 -11
  123. package/scripts/flow-roadmap.js +2 -2
  124. package/scripts/flow-run-trace.js +16 -15
  125. package/scripts/flow-safety.js +2 -5
  126. package/scripts/flow-scanner-base.js +5 -7
  127. package/scripts/flow-scenario-engine.js +1 -5
  128. package/scripts/flow-security.js +29 -0
  129. package/scripts/flow-session-end.js +32 -41
  130. package/scripts/flow-session-learning.js +53 -49
  131. package/scripts/flow-setup-hooks.js +2 -3
  132. package/scripts/flow-skill-create.js +7 -12
  133. package/scripts/flow-skill-generator.js +12 -16
  134. package/scripts/flow-skill-learn.js +17 -8
  135. package/scripts/flow-skill-matcher.js +1 -2
  136. package/scripts/flow-spec-generator.js +2 -4
  137. package/scripts/flow-stack-wizard.js +5 -7
  138. package/scripts/flow-standards-learner.js +35 -16
  139. package/scripts/flow-start.js +2 -0
  140. package/scripts/flow-stats-collector.js +2 -2
  141. package/scripts/flow-status.js +10 -10
  142. package/scripts/flow-statusline-setup.js +2 -2
  143. package/scripts/flow-step-changelog.js +2 -3
  144. package/scripts/flow-step-comments.js +66 -81
  145. package/scripts/flow-step-complexity.js +50 -70
  146. package/scripts/flow-step-coverage.js +3 -5
  147. package/scripts/flow-step-knowledge.js +2 -3
  148. package/scripts/flow-step-pr-tests.js +64 -74
  149. package/scripts/flow-step-regression.js +3 -5
  150. package/scripts/flow-step-review.js +86 -103
  151. package/scripts/flow-step-security.js +111 -121
  152. package/scripts/flow-step-silent-failures.js +56 -83
  153. package/scripts/flow-step-simplifier.js +52 -70
  154. package/scripts/flow-story.js +4 -7
  155. package/scripts/flow-strict-adherence.js +3 -4
  156. package/scripts/flow-task-checkpoint.js +36 -5
  157. package/scripts/flow-task-enforcer.js +2 -24
  158. package/scripts/flow-tech-debt.js +1 -1
  159. package/scripts/flow-template-extractor.js +1 -0
  160. package/scripts/flow-templates.js +11 -13
  161. package/scripts/flow-test-api.js +9 -13
  162. package/scripts/flow-test-discovery.js +1 -1
  163. package/scripts/flow-test-generate.js +5 -9
  164. package/scripts/flow-test-integrity.js +3 -7
  165. package/scripts/flow-test-ui.js +5 -9
  166. package/scripts/flow-testing-deps.js +1 -3
  167. package/scripts/flow-tiered-learning.js +4 -4
  168. package/scripts/flow-todowrite-sync.js +1 -1
  169. package/scripts/flow-tokens.js +0 -2
  170. package/scripts/flow-verification-profile.js +6 -10
  171. package/scripts/flow-verify.js +12 -16
  172. package/scripts/flow-version-check.js +4 -12
  173. package/scripts/flow-webmcp-generator.js +3 -5
  174. package/scripts/flow-workflow-steps.js +0 -2
  175. package/scripts/flow-workflow.js +9 -11
  176. package/scripts/hooks/adapters/claude-code.js +31 -0
  177. package/scripts/hooks/core/config-change.js +1 -0
  178. package/scripts/hooks/core/extension-registry.js +0 -2
  179. package/scripts/hooks/core/instructions-loaded.js +1 -1
  180. package/scripts/hooks/core/observation-capture.js +5 -5
  181. package/scripts/hooks/core/phase-gate.js +5 -0
  182. package/scripts/hooks/core/post-compact.js +1 -12
  183. package/scripts/hooks/core/research-gate.js +2 -12
  184. package/scripts/hooks/core/routing-gate.js +6 -0
  185. package/scripts/hooks/core/task-completed.js +12 -0
  186. package/scripts/hooks/core/task-created.js +83 -0
  187. package/scripts/hooks/core/worktree-lifecycle.js +1 -1
  188. package/scripts/hooks/entry/claude-code/config-change.js +6 -29
  189. package/scripts/hooks/entry/claude-code/instructions-loaded.js +5 -30
  190. package/scripts/hooks/entry/claude-code/post-compact.js +4 -31
  191. package/scripts/hooks/entry/claude-code/post-tool-use.js +121 -172
  192. package/scripts/hooks/entry/claude-code/pre-tool-use.js +260 -361
  193. package/scripts/hooks/entry/claude-code/session-end.js +4 -28
  194. package/scripts/hooks/entry/claude-code/session-start.js +205 -243
  195. package/scripts/hooks/entry/claude-code/setup.js +8 -49
  196. package/scripts/hooks/entry/claude-code/stop.js +40 -72
  197. package/scripts/hooks/entry/claude-code/task-completed.js +4 -28
  198. package/scripts/hooks/entry/claude-code/task-created.js +15 -0
  199. package/scripts/hooks/entry/claude-code/user-prompt-submit.js +113 -195
  200. package/scripts/hooks/entry/claude-code/worktree-create.js +6 -25
  201. package/scripts/hooks/entry/claude-code/worktree-remove.js +6 -25
  202. package/scripts/hooks/entry/shared/hook-runner.js +99 -0
  203. package/scripts/hooks/entry/shared/read-stdin.js +0 -2
  204. package/scripts/postinstall.js +2 -0
  205. package/scripts/registries/api-registry.js +0 -2
  206. package/scripts/registries/component-registry.js +5 -9
  207. package/scripts/registries/contract-scanner.js +2 -9
  208. package/scripts/registries/function-registry.js +0 -2
  209. package/scripts/registries/schema-registry.js +14 -18
  210. 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
- function sleep(ms) {
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
@@ -1,6 +1,4 @@
1
1
  #!/usr/bin/env node
2
- 'use strict';
3
-
4
2
  /**
5
3
  * Wogi Flow - Cascade Completion (v3.2)
6
4
  *
@@ -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(PROJECT_ROOT, '.workflow', 'state', 'cascade-state.json');
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 content = fs.readFileSync(STATE_PATH, 'utf-8');
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 PROJECT_ROOT = getProjectRoot();
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(WORKFLOW_DIR, relPath);
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: PROJECT_ROOT,
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: PROJECT_ROOT });
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: PROJECT_ROOT,
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: PROJECT_ROOT,
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(WORKFLOW_DIR, relPath);
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: PROJECT_ROOT, encoding: 'utf-8' });
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: PROJECT_ROOT
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(PROJECT_ROOT, 'tsconfig.json');
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(PROJECT_ROOT, dir))) return dir;
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(PROJECT_ROOT, filePath);
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(PROJECT_ROOT, fullPath);
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(PROJECT_ROOT + path.sep) && candidate !== PROJECT_ROOT) continue;
375
- const relPath = path.relative(PROJECT_ROOT, candidate);
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(PROJECT_ROOT, relPath))) {
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: PROJECT_ROOT,
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: PROJECT_ROOT,
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: PROJECT_ROOT,
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 = path.join(PATHS.root, '.workflow', 'models', 'community-scores.json');
35
- const COMMUNITY_ROUTING_PATH = path.join(PATHS.root, '.workflow', 'models', 'community-routing.json');
36
- const SYNC_QUEUE_PATH = path.join(PATHS.root, '.workflow', 'state', 'sync-queue.json');
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.root, '.workflow', 'state', 'auth.json');
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
- // Find the end of the Patterns Log table to insert before pending patterns
1096
- const tableEnd = content.indexOf('\n\n### ');
1097
- if (tableEnd !== -1) {
1098
- const updated = content.slice(0, tableEnd) + '\n' + newRows.join('\n') + content.slice(tableEnd);
1099
- try {
1100
- fs.writeFileSync(filePath, updated, 'utf-8');
1101
- } catch (err) {
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 = path.join(PATHS.root, '.workflow', 'config.json');
224
- try {
225
- const content = fs.readFileSync(configPath, 'utf-8');
226
- const config = JSON.parse(content);
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
- let rawConfig;
300
- try {
301
- rawConfig = JSON.parse(configContent);
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
- const configPath = PATHS.config;
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
- const parts = configPath.split('.');
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
- const parts = configPath.split('.');
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
  /**
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  /**
4
2
  * Wogi Flow - Config Substitution
5
3
  *
@@ -111,7 +111,7 @@ function readSpecFile(taskId) {
111
111
  return null;
112
112
  }
113
113
 
114
- const specPath = path.join(PATHS.root, '.workflow', 'specs', `${taskId}.md`);
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 = path.join(PATHS.root, '.workflow', 'changes');
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 = path.join(PATHS.state, 'ready.json');
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 = path.join(PATHS.state, 'ready.json');
549
+ const readyPath = PATHS.ready;
550
550
  const readyData = safeJsonParse(readyPath, { ready: [], inProgress: [] });
551
551
 
552
552
  const allTasks = [