wogiflow 2.4.1 → 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.
Files changed (198) hide show
  1. package/.claude/docs/claude-code-compatibility.md +27 -0
  2. package/.claude/settings.json +1 -1
  3. package/.workflow/models/registry.json +1 -1
  4. package/package.json +4 -4
  5. package/scripts/MEMORY-ARCHITECTURE.md +1 -1
  6. package/scripts/base-workflow-step.js +136 -0
  7. package/scripts/flow-adaptive-learning.js +8 -9
  8. package/scripts/flow-aggregate.js +11 -6
  9. package/scripts/flow-api-index.js +4 -6
  10. package/scripts/flow-assumption-detector.js +0 -2
  11. package/scripts/flow-audit.js +15 -2
  12. package/scripts/flow-auto-context.js +8 -12
  13. package/scripts/flow-auto-learn.js +49 -49
  14. package/scripts/flow-background.js +5 -6
  15. package/scripts/flow-bridge-state.js +8 -10
  16. package/scripts/flow-bulk-loop.js +1 -3
  17. package/scripts/flow-bulk-orchestrator.js +1 -3
  18. package/scripts/flow-cascade-completion.js +0 -2
  19. package/scripts/flow-cascade.js +4 -4
  20. package/scripts/flow-checkpoint.js +10 -13
  21. package/scripts/flow-code-intelligence.js +10 -12
  22. package/scripts/flow-community-sync.js +4 -4
  23. package/scripts/flow-community.js +12 -20
  24. package/scripts/flow-config-defaults.js +0 -2
  25. package/scripts/flow-config-interactive.js +9 -5
  26. package/scripts/flow-config-loader.js +49 -92
  27. package/scripts/flow-config-substitution.js +0 -2
  28. package/scripts/flow-context-estimator.js +4 -4
  29. package/scripts/flow-context-init.js +10 -12
  30. package/scripts/flow-context-manager.js +0 -2
  31. package/scripts/flow-context-scoring.js +2 -2
  32. package/scripts/flow-contract-scan.js +6 -9
  33. package/scripts/flow-correct.js +29 -27
  34. package/scripts/flow-correction-detector.js +5 -1
  35. package/scripts/flow-damage-control.js +47 -54
  36. package/scripts/flow-decisions-merge.js +4 -14
  37. package/scripts/flow-diff.js +5 -8
  38. package/scripts/flow-done-gates.js +786 -0
  39. package/scripts/flow-done-report.js +123 -0
  40. package/scripts/flow-done.js +71 -717
  41. package/scripts/flow-entropy-monitor.js +1 -3
  42. package/scripts/flow-eval.js +5 -5
  43. package/scripts/flow-extraction-review.js +1 -0
  44. package/scripts/flow-failure-categories.js +0 -2
  45. package/scripts/flow-figma-confirm.js +5 -9
  46. package/scripts/flow-figma-generate.js +8 -10
  47. package/scripts/flow-figma-index.js +8 -10
  48. package/scripts/flow-figma-match.js +3 -5
  49. package/scripts/flow-figma-mcp-server.js +2 -4
  50. package/scripts/flow-figma-orchestrator.js +2 -3
  51. package/scripts/flow-figma-registry.js +2 -3
  52. package/scripts/flow-framework-resolver.js +0 -2
  53. package/scripts/flow-function-index.js +4 -6
  54. package/scripts/flow-gate-confidence.js +2 -2
  55. package/scripts/flow-gitignore.js +0 -2
  56. package/scripts/flow-guided-edit.js +5 -6
  57. package/scripts/flow-health.js +5 -6
  58. package/scripts/flow-hook-errors.js +6 -0
  59. package/scripts/flow-hook-status.js +263 -0
  60. package/scripts/flow-hooks.js +17 -29
  61. package/scripts/flow-http-client.js +9 -8
  62. package/scripts/flow-hybrid-interactive.js +7 -12
  63. package/scripts/flow-hybrid-test.js +12 -13
  64. package/scripts/flow-instruction-richness.js +1 -1
  65. package/scripts/flow-io.js +21 -4
  66. package/scripts/flow-knowledge-router.js +9 -3
  67. package/scripts/flow-learning-orchestrator.js +318 -13
  68. package/scripts/flow-links.js +5 -7
  69. package/scripts/flow-long-input-association.js +275 -0
  70. package/scripts/flow-long-input-chunking.js +1 -0
  71. package/scripts/flow-long-input-cli.js +0 -2
  72. package/scripts/flow-long-input-complexity.js +0 -2
  73. package/scripts/flow-long-input-constants.js +0 -2
  74. package/scripts/flow-long-input-contradictions.js +351 -0
  75. package/scripts/flow-long-input-detection.js +0 -2
  76. package/scripts/flow-long-input-passes.js +885 -0
  77. package/scripts/flow-long-input-stories.js +1 -1
  78. package/scripts/flow-long-input-voice.js +0 -2
  79. package/scripts/flow-long-input.js +425 -3005
  80. package/scripts/flow-loop-retry-learning.js +2 -3
  81. package/scripts/flow-lsp.js +3 -3
  82. package/scripts/flow-mcp-docs.js +3 -4
  83. package/scripts/flow-memory-db.js +6 -8
  84. package/scripts/flow-memory-sync.js +18 -11
  85. package/scripts/flow-metrics.js +1 -2
  86. package/scripts/flow-model-adapter.js +2 -3
  87. package/scripts/flow-model-config.js +72 -104
  88. package/scripts/flow-model-router.js +2 -2
  89. package/scripts/flow-model-types.js +0 -2
  90. package/scripts/flow-multi-approach.js +5 -6
  91. package/scripts/flow-orchestrate-context.js +3 -7
  92. package/scripts/flow-orchestrate-rollback.js +3 -8
  93. package/scripts/flow-orchestrate-state.js +8 -14
  94. package/scripts/flow-orchestrate-templates.js +2 -6
  95. package/scripts/flow-orchestrate-validator.js +5 -9
  96. package/scripts/flow-orchestrate.js +126 -103
  97. package/scripts/flow-output.js +0 -2
  98. package/scripts/flow-parallel.js +1 -1
  99. package/scripts/flow-paths.js +23 -2
  100. package/scripts/flow-pattern-enforcer.js +30 -28
  101. package/scripts/flow-pattern-extractor.js +3 -4
  102. package/scripts/flow-pending.js +0 -2
  103. package/scripts/flow-permissions.js +2 -3
  104. package/scripts/flow-plugin-registry.js +10 -12
  105. package/scripts/flow-prd-manager.js +1 -1
  106. package/scripts/flow-progress.js +7 -9
  107. package/scripts/flow-prompt-composer.js +3 -3
  108. package/scripts/flow-prompt-template.js +2 -2
  109. package/scripts/flow-providers.js +7 -4
  110. package/scripts/flow-registry-manager.js +7 -12
  111. package/scripts/flow-regression.js +9 -11
  112. package/scripts/flow-roadmap.js +2 -2
  113. package/scripts/flow-run-trace.js +16 -15
  114. package/scripts/flow-safety.js +2 -5
  115. package/scripts/flow-scanner-base.js +5 -7
  116. package/scripts/flow-scenario-engine.js +1 -5
  117. package/scripts/flow-security.js +29 -0
  118. package/scripts/flow-session-end.js +32 -41
  119. package/scripts/flow-session-learning.js +53 -49
  120. package/scripts/flow-setup-hooks.js +2 -3
  121. package/scripts/flow-skill-create.js +7 -12
  122. package/scripts/flow-skill-generator.js +12 -16
  123. package/scripts/flow-skill-learn.js +17 -8
  124. package/scripts/flow-skill-matcher.js +1 -2
  125. package/scripts/flow-spec-generator.js +2 -4
  126. package/scripts/flow-stack-wizard.js +5 -7
  127. package/scripts/flow-standards-learner.js +35 -16
  128. package/scripts/flow-start.js +2 -0
  129. package/scripts/flow-stats-collector.js +2 -2
  130. package/scripts/flow-status.js +10 -10
  131. package/scripts/flow-statusline-setup.js +2 -2
  132. package/scripts/flow-step-changelog.js +2 -3
  133. package/scripts/flow-step-comments.js +66 -81
  134. package/scripts/flow-step-complexity.js +50 -70
  135. package/scripts/flow-step-coverage.js +3 -5
  136. package/scripts/flow-step-knowledge.js +2 -3
  137. package/scripts/flow-step-pr-tests.js +64 -74
  138. package/scripts/flow-step-regression.js +3 -5
  139. package/scripts/flow-step-review.js +86 -103
  140. package/scripts/flow-step-security.js +111 -121
  141. package/scripts/flow-step-silent-failures.js +56 -83
  142. package/scripts/flow-step-simplifier.js +52 -70
  143. package/scripts/flow-story.js +4 -7
  144. package/scripts/flow-strict-adherence.js +3 -4
  145. package/scripts/flow-task-checkpoint.js +36 -5
  146. package/scripts/flow-task-enforcer.js +2 -24
  147. package/scripts/flow-tech-debt.js +1 -1
  148. package/scripts/flow-template-extractor.js +1 -0
  149. package/scripts/flow-templates.js +11 -13
  150. package/scripts/flow-test-api.js +9 -13
  151. package/scripts/flow-test-discovery.js +1 -1
  152. package/scripts/flow-test-generate.js +5 -9
  153. package/scripts/flow-test-integrity.js +3 -7
  154. package/scripts/flow-test-ui.js +5 -9
  155. package/scripts/flow-testing-deps.js +1 -3
  156. package/scripts/flow-tiered-learning.js +4 -4
  157. package/scripts/flow-todowrite-sync.js +1 -1
  158. package/scripts/flow-tokens.js +0 -2
  159. package/scripts/flow-verification-profile.js +6 -10
  160. package/scripts/flow-verify.js +12 -16
  161. package/scripts/flow-version-check.js +4 -12
  162. package/scripts/flow-webmcp-generator.js +3 -5
  163. package/scripts/flow-workflow-steps.js +0 -2
  164. package/scripts/flow-workflow.js +9 -11
  165. package/scripts/hooks/adapters/claude-code.js +2 -0
  166. package/scripts/hooks/core/commit-log-gate.js +21 -10
  167. package/scripts/hooks/core/config-change.js +1 -0
  168. package/scripts/hooks/core/extension-registry.js +0 -2
  169. package/scripts/hooks/core/instructions-loaded.js +1 -1
  170. package/scripts/hooks/core/observation-capture.js +5 -5
  171. package/scripts/hooks/core/phase-gate.js +5 -0
  172. package/scripts/hooks/core/post-compact.js +1 -12
  173. package/scripts/hooks/core/research-gate.js +2 -12
  174. package/scripts/hooks/core/routing-gate.js +6 -0
  175. package/scripts/hooks/core/task-completed.js +12 -0
  176. package/scripts/hooks/core/task-gate.js +27 -0
  177. package/scripts/hooks/core/worktree-lifecycle.js +1 -1
  178. package/scripts/hooks/entry/claude-code/config-change.js +6 -29
  179. package/scripts/hooks/entry/claude-code/instructions-loaded.js +5 -30
  180. package/scripts/hooks/entry/claude-code/post-compact.js +4 -31
  181. package/scripts/hooks/entry/claude-code/post-tool-use.js +121 -172
  182. package/scripts/hooks/entry/claude-code/pre-tool-use.js +260 -361
  183. package/scripts/hooks/entry/claude-code/session-end.js +4 -28
  184. package/scripts/hooks/entry/claude-code/session-start.js +205 -243
  185. package/scripts/hooks/entry/claude-code/setup.js +8 -49
  186. package/scripts/hooks/entry/claude-code/stop.js +40 -72
  187. package/scripts/hooks/entry/claude-code/task-completed.js +4 -28
  188. package/scripts/hooks/entry/claude-code/user-prompt-submit.js +113 -195
  189. package/scripts/hooks/entry/claude-code/worktree-create.js +6 -25
  190. package/scripts/hooks/entry/claude-code/worktree-remove.js +6 -25
  191. package/scripts/hooks/entry/shared/hook-runner.js +99 -0
  192. package/scripts/hooks/entry/shared/read-stdin.js +0 -2
  193. package/scripts/registries/api-registry.js +0 -2
  194. package/scripts/registries/component-registry.js +5 -9
  195. package/scripts/registries/contract-scanner.js +2 -9
  196. package/scripts/registries/function-registry.js +0 -2
  197. package/scripts/registries/schema-registry.js +14 -18
  198. package/scripts/registries/service-registry.js +23 -27
@@ -39,10 +39,6 @@ const {
39
39
  // Configuration
40
40
  // ============================================================
41
41
 
42
- const PROJECT_ROOT = getProjectRoot();
43
- const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
44
- const STATE_DIR = path.join(WORKFLOW_DIR, 'state');
45
-
46
42
  // ============================================================
47
43
  // Pattern Extraction
48
44
  // ============================================================
@@ -50,7 +46,7 @@ const STATE_DIR = path.join(WORKFLOW_DIR, 'state');
50
46
  /**
51
47
  * Load all patterns from decisions.md
52
48
  */
53
- function loadDecisionPatterns(projectRoot = PROJECT_ROOT) {
49
+ function loadDecisionPatterns(projectRoot = PATHS.root) {
54
50
  const decisionsPath = path.join(projectRoot, '.workflow', 'state', 'decisions.md');
55
51
 
56
52
  // Read file directly in try-catch (no pre-check to avoid TOCTOU race condition)
@@ -93,7 +89,7 @@ function loadDecisionPatterns(projectRoot = PROJECT_ROOT) {
93
89
  /**
94
90
  * Load components from app-map.md
95
91
  */
96
- function loadAppMapComponents(projectRoot = PROJECT_ROOT) {
92
+ function loadAppMapComponents(projectRoot = PATHS.root) {
97
93
  const appMapPath = path.join(projectRoot, '.workflow', 'state', 'app-map.md');
98
94
 
99
95
  // Read file directly in try-catch (no pre-check to avoid TOCTOU race condition)
@@ -205,7 +201,7 @@ function loadSkillPatterns(projectRoot, fileExtension, taskDescription = '') {
205
201
  /**
206
202
  * Extract patterns relevant to a specific task
207
203
  */
208
- function extractRelevantPatterns(task, projectRoot = PROJECT_ROOT) {
204
+ function extractRelevantPatterns(task, projectRoot = PATHS.root) {
209
205
  const relevant = {
210
206
  decisions: [],
211
207
  components: [],
@@ -329,7 +325,7 @@ function formatPatternsForPrompt(relevantPatterns, config = {}) {
329
325
  /**
330
326
  * Inject patterns into a prompt
331
327
  */
332
- function injectPatterns(prompt, task, projectRoot = PROJECT_ROOT) {
328
+ function injectPatterns(prompt, task, projectRoot = PATHS.root) {
333
329
  const config = getConfig();
334
330
  const enforcement = config.enforcement || {};
335
331
 
@@ -483,7 +479,7 @@ function validateCitations(code, patterns) {
483
479
  /**
484
480
  * Generate session start summary showing loaded patterns
485
481
  */
486
- function generateSessionSummary(projectRoot = PROJECT_ROOT) {
482
+ function generateSessionSummary(projectRoot = PATHS.root) {
487
483
  const decisions = loadDecisionPatterns(projectRoot);
488
484
  const components = loadAppMapComponents(projectRoot);
489
485
  const config = getConfig();
@@ -601,16 +597,18 @@ ${userReason ? `**Reason**: ${userReason}` : ''}
601
597
 
602
598
  /**
603
599
  * Add a cross-session rule to decisions.md
604
- * Uses file locking to prevent race conditions with concurrent writes
600
+ * Routes through the learning orchestrator for centralized locking and dedup.
605
601
  */
606
602
  function addCrossSessionRuleToDecisions(rule, category) {
607
- const decisionsPath = path.join(PATHS.state, 'decisions.md');
608
-
609
603
  try {
610
- // Use file locking to prevent race conditions
611
- return withLockSync(decisionsPath, () => {
612
- if (!fileExists(decisionsPath)) {
613
- const template = `# Project Decisions
604
+ const { modifyDecisions } = require('./flow-learning-orchestrator');
605
+
606
+ // modifyDecisions is async; fire-and-forget for sync callers
607
+ const result = modifyDecisions((currentContent) => {
608
+ let content = currentContent;
609
+
610
+ if (!content) {
611
+ content = `# Project Decisions
614
612
 
615
613
  This document captures project-level coding rules and patterns.
616
614
 
@@ -629,16 +627,12 @@ This document captures project-level coding rules and patterns.
629
627
  ## General
630
628
 
631
629
  `;
632
- writeFile(decisionsPath, template);
633
630
  }
634
631
 
635
- let content = readFile(decisionsPath, '');
636
-
637
632
  const sectionHeader = `## ${category}`;
638
633
  const sectionIndex = content.indexOf(sectionHeader);
639
634
 
640
635
  if (sectionIndex === -1) {
641
- // Category doesn't exist - add it with the rule
642
636
  content += `\n${sectionHeader}\n\n${rule}\n`;
643
637
  } else {
644
638
  const afterSection = content.slice(sectionIndex + sectionHeader.length);
@@ -652,9 +646,17 @@ This document captures project-level coding rules and patterns.
652
646
  }
653
647
  }
654
648
 
655
- writeFile(decisionsPath, content);
656
- return { success: true };
657
- });
649
+ return { content, entryText: rule.slice(0, 100) };
650
+ }, { caller: 'flow-pattern-enforcer/addCrossSessionRule' });
651
+
652
+ // Handle async result for sync callers
653
+ if (result && result.then) {
654
+ result.catch(_err => {
655
+ if (process.env.DEBUG) console.error(`[DEBUG] addCrossSessionRuleToDecisions: ${_err.message}`);
656
+ });
657
+ }
658
+
659
+ return { success: true };
658
660
  } catch (err) {
659
661
  return { success: false, error: err.message };
660
662
  }
@@ -662,7 +664,7 @@ This document captures project-level coding rules and patterns.
662
664
 
663
665
  /**
664
666
  * Add a cross-session rule to .claude/rules/ directory
665
- * Uses PROJECT_ROOT for consistent path resolution
667
+ * Uses PATHS.root for consistent path resolution
666
668
  */
667
669
  function addCrossSessionRuleToClaudeRules(pattern, category) {
668
670
  try {
@@ -682,14 +684,14 @@ function addCrossSessionRuleToClaudeRules(pattern, category) {
682
684
  };
683
685
 
684
686
  const subdir = categoryToDir[category] || 'general';
685
- // Use PROJECT_ROOT instead of process.cwd() for consistent path resolution
686
- const rulesDir = path.join(PROJECT_ROOT, '.claude', 'rules', subdir);
687
+ // Use PATHS.root instead of process.cwd() for consistent path resolution
688
+ const rulesDir = path.join(PATHS.root, '.claude', 'rules', subdir);
687
689
 
688
690
  // Validate path is within project (defense in depth)
689
691
  // Use path.sep check to prevent prefix-matching attacks (e.g., /project vs /project-evil)
690
692
  const resolvedRulesDir = path.resolve(rulesDir);
691
- const normalizedRoot = PROJECT_ROOT.endsWith(path.sep) ? PROJECT_ROOT : PROJECT_ROOT + path.sep;
692
- if (!resolvedRulesDir.startsWith(normalizedRoot) && resolvedRulesDir !== PROJECT_ROOT) {
693
+ const normalizedRoot = PATHS.root.endsWith(path.sep) ? PATHS.root : PATHS.root + path.sep;
694
+ if (!resolvedRulesDir.startsWith(normalizedRoot) && resolvedRulesDir !== PATHS.root) {
693
695
  return { success: false, error: 'Invalid rules directory: outside project' };
694
696
  }
695
697
 
@@ -38,7 +38,7 @@ const fs = require('node:fs');
38
38
  const path = require('node:path');
39
39
  const { execSync, execFileSync } = require('node:child_process');
40
40
  const { resolvePatterns } = require('./flow-framework-resolver');
41
- const { getProjectRoot, generateHashId, readJson } = require('./flow-utils');
41
+ const { getProjectRoot, generateHashId, readJson, safeJsonParse, PATHS } = require('./flow-utils');
42
42
 
43
43
  // ============================================================================
44
44
  // Constants
@@ -217,9 +217,8 @@ function detectFramework(projectRoot) {
217
217
 
218
218
  if (fs.existsSync(packageJsonPath)) {
219
219
  try {
220
- const raw = fs.readFileSync(packageJsonPath, 'utf-8');
221
- const pkg = JSON.parse(raw);
222
- if (!pkg || typeof pkg !== 'object' || Array.isArray(pkg)) return null;
220
+ const pkg = safeJsonParse(packageJsonPath, null);
221
+ if (!pkg) return null;
223
222
  const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
224
223
 
225
224
  // Check for frameworks
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  /**
4
2
  * Wogi Flow - Pending Prompts Queue
5
3
  *
@@ -20,7 +20,7 @@ const fs = require('node:fs');
20
20
  const path = require('node:path');
21
21
  const {
22
22
  getProjectRoot,
23
- safeJsonParse
23
+ safeJsonParse, PATHS
24
24
  } = require('./flow-utils')
25
25
  const { color, printHeader, printSection, success } = require('./flow-output');;
26
26
 
@@ -28,8 +28,7 @@ const { color, printHeader, printSection, success } = require('./flow-output');;
28
28
  // Configuration
29
29
  // ============================================================
30
30
 
31
- const PROJECT_ROOT = getProjectRoot();
32
- const PERMISSIONS_PATH = path.join(PROJECT_ROOT, '.workflow', 'state', 'permissions.json');
31
+ const PERMISSIONS_PATH = path.join(PATHS.state, 'permissions.json');
33
32
 
34
33
  // In-memory session permissions (cleared on process exit or explicit clear)
35
34
  const sessionPermissions = new Map();
@@ -26,7 +26,7 @@ const {
26
26
  fileExists,
27
27
  isPathWithinProject,
28
28
  color,
29
- printHeader
29
+ printHeader, PATHS
30
30
  } = require('./flow-utils');
31
31
 
32
32
  // Dangerous keys that must never be used as plugin names or metadata keys
@@ -41,8 +41,6 @@ const VALID_FLOW_PHASES = new Set(['exploring', 'coding', 'validating', 'complet
41
41
  // Configuration
42
42
  // ============================================================
43
43
 
44
- const PROJECT_ROOT = getProjectRoot();
45
-
46
44
  /**
47
45
  * Get plugin config from workflow config
48
46
  * @returns {Object} Plugin configuration
@@ -68,10 +66,10 @@ function getPluginConfig() {
68
66
  function getRegistryPath() {
69
67
  const pluginConfig = getPluginConfig();
70
68
  const registryRelPath = pluginConfig.registryPath || '.workflow/state/plugin-registry.json';
71
- const resolved = path.resolve(PROJECT_ROOT, registryRelPath);
69
+ const resolved = path.resolve(PATHS.root, registryRelPath);
72
70
  if (!isPathWithinProject(resolved)) {
73
71
  console.error(`[plugin-registry] Unsafe registryPath in config: ${registryRelPath}`);
74
- return path.join(PROJECT_ROOT, '.workflow', 'state', 'plugin-registry.json');
72
+ return path.join(PATHS.state, 'plugin-registry.json');
75
73
  }
76
74
  return resolved;
77
75
  }
@@ -134,8 +132,8 @@ function discoverMcpTools(pluginName) {
134
132
 
135
133
  // Strategy 1: Check Claude Code's MCP settings for matching servers
136
134
  const settingsLocations = [
137
- path.join(PROJECT_ROOT, '.claude', 'settings.local.json'),
138
- path.join(PROJECT_ROOT, '.claude', 'settings.json')
135
+ path.join(PATHS.root, '.claude', 'settings.local.json'),
136
+ path.join(PATHS.root, '.claude', 'settings.json')
139
137
  ];
140
138
 
141
139
  for (const settingsPath of settingsLocations) {
@@ -163,7 +161,7 @@ function discoverMcpTools(pluginName) {
163
161
  }
164
162
 
165
163
  // Strategy 2: Check cached MCP tools from flow-mcp-docs
166
- const mcpCachePath = path.join(PROJECT_ROOT, '.workflow', 'state', 'mcp-tools.json');
164
+ const mcpCachePath = path.join(PATHS.state, 'mcp-tools.json');
167
165
  if (fileExists(mcpCachePath)) {
168
166
  try {
169
167
  const mcpCache = safeJsonParse(mcpCachePath, { allTools: [] });
@@ -210,8 +208,8 @@ function scanUnregisteredMcpServers() {
210
208
  const seen = new Set(); // Dedup across multiple settings files
211
209
 
212
210
  const settingsLocations = [
213
- path.join(PROJECT_ROOT, '.claude', 'settings.local.json'),
214
- path.join(PROJECT_ROOT, '.claude', 'settings.json')
211
+ path.join(PATHS.root, '.claude', 'settings.local.json'),
212
+ path.join(PATHS.root, '.claude', 'settings.json')
215
213
  ];
216
214
 
217
215
  // Internal/built-in servers that should not be auto-registered as plugins
@@ -263,8 +261,8 @@ function scanUnregisteredMcpServers() {
263
261
  function deactivateStaleMcpPlugins() {
264
262
  const registry = readRegistry();
265
263
  const settingsLocations = [
266
- path.join(PROJECT_ROOT, '.claude', 'settings.local.json'),
267
- path.join(PROJECT_ROOT, '.claude', 'settings.json')
264
+ path.join(PATHS.root, '.claude', 'settings.local.json'),
265
+ path.join(PATHS.root, '.claude', 'settings.json')
268
266
  ];
269
267
 
270
268
  const availableServers = new Set();
@@ -275,7 +275,7 @@ module.exports = {
275
275
  };
276
276
 
277
277
  if (require.main === module) {
278
- main().catch(e => {
278
+ main().catch(err => {
279
279
  error(err.message);
280
280
  process.exit(1);
281
281
  });
@@ -6,7 +6,7 @@
6
6
  * Provides visual feedback during hybrid execution.
7
7
  */
8
8
 
9
- const readline = require('node:readline');
9
+ const readline = require('node:readline/promises');
10
10
  const { colors } = require('./flow-utils');
11
11
 
12
12
  const symbols = {
@@ -230,12 +230,9 @@ async function prompt(question) {
230
230
  output: process.stdout
231
231
  });
232
232
 
233
- return new Promise(resolve => {
234
- rl.question(question, answer => {
235
- rl.close();
236
- resolve(answer.trim());
237
- });
238
- });
233
+ const answer = await rl.question(question);
234
+ rl.close();
235
+ return answer.trim();
239
236
  }
240
237
 
241
238
  function formatResults(results) {
@@ -288,14 +285,15 @@ if (require.main === module) {
288
285
 
289
286
  const spinner = new Spinner('Detecting providers...');
290
287
  spinner.start();
291
- await new Promise(r => setTimeout(r, 2000));
288
+ const { setTimeout: sleep } = require('node:timers/promises');
289
+ await sleep(2000);
292
290
  spinner.stop('Providers detected', true);
293
291
 
294
292
  console.log('\nProgress bar:');
295
293
  const bar = new ProgressBar(10);
296
294
  for (let i = 0; i <= 10; i++) {
297
295
  bar.update(i, `Step ${i}/10`);
298
- await new Promise(r => setTimeout(r, 200));
296
+ await sleep(200);
299
297
  }
300
298
  bar.complete('All steps done');
301
299
 
@@ -29,7 +29,7 @@ const {
29
29
  printHeader,
30
30
  printSection,
31
31
  isPathWithinProject,
32
- estimateTokens
32
+ estimateTokens, PATHS
33
33
  } = require('./flow-utils');
34
34
 
35
35
  // Smart Context System integration
@@ -49,8 +49,8 @@ try {
49
49
  // Constants
50
50
  // ============================================================
51
51
 
52
- const FRAGMENTS_DIR = path.join(PROJECT_ROOT, '.workflow', 'prompts', 'fragments');
53
- const COMPOSED_DIR = path.join(PROJECT_ROOT, '.workflow', 'prompts', 'composed');
52
+ const FRAGMENTS_DIR = path.join(PATHS.workflow, 'prompts', 'fragments');
53
+ const COMPOSED_DIR = path.join(PATHS.workflow, 'prompts', 'composed');
54
54
 
55
55
  // Model to CLI mapping (Claude Code only)
56
56
  const MODEL_CLI_MAP = {
@@ -25,7 +25,7 @@ const {
25
25
  // Constants
26
26
  // ============================================================
27
27
 
28
- const TEMPLATES_DIR = path.join(PATHS.root, '.workflow', 'templates', 'prompts');
28
+ const TEMPLATES_DIR = PATHS.templatesPrompts;
29
29
 
30
30
  // Model family to template file mapping
31
31
  const MODEL_TEMPLATE_MAP = {
@@ -387,7 +387,7 @@ function getAgentModel(taskType, options = {}) {
387
387
  * @returns {number} Score 0-10, or 0 if not found
388
388
  */
389
389
  function getCapabilityScore(modelFamily, taskType) {
390
- const capDir = path.join(PATHS.root, '.workflow', 'models', 'capabilities');
390
+ const capDir = PATHS.modelCapabilities;
391
391
 
392
392
  // Map model family to capability file
393
393
  const fileMap = {
@@ -24,12 +24,9 @@ const fs = require('node:fs');
24
24
  const path = require('node:path');
25
25
  const https = require('node:https');
26
26
  const http = require('node:http');
27
- const { getProjectRoot, getConfig, colors: c, estimateTokens } = require('./flow-utils');
27
+ const { getProjectRoot, getConfig, colors: c, estimateTokens, PATHS } = require('./flow-utils');
28
28
  const { success: printSuccess, error: printError } = require('./flow-output');
29
29
 
30
- const PROJECT_ROOT = getProjectRoot();
31
- const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
32
-
33
30
  /**
34
31
  * Provider types
35
32
  */
@@ -918,6 +915,12 @@ async function detectProviders() {
918
915
  // Not available
919
916
  }
920
917
 
918
+ // NOTE: When CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1 (Claude Code 2.1.83+), cloud provider
919
+ // API keys are stripped from subprocess environments. Since hybrid mode invokes this
920
+ // script via the Bash tool (a subprocess), no cloud providers will be detected.
921
+ // Workaround: pass API keys via config.json hybrid.providers[].apiKey instead of env vars.
922
+ // Local providers (Ollama, LM Studio) are unaffected (they use localhost HTTP, no keys).
923
+
921
924
  // Check Anthropic (cloud - if key present)
922
925
  if (process.env.ANTHROPIC_API_KEY) {
923
926
  available.push({
@@ -1,7 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- 'use strict';
4
-
5
3
  /**
6
4
  * Wogi Flow - Registry Manager
7
5
  *
@@ -18,12 +16,9 @@
18
16
 
19
17
  const fs = require('node:fs');
20
18
  const path = require('node:path');
21
- const { getProjectRoot, getConfig, safeJsonParse, color, success, warn, error, info } = require('./flow-utils');
19
+ const { getProjectRoot, getConfig, safeJsonParse, color, success, warn, error, info, PATHS } = require('./flow-utils');
22
20
 
23
- const PROJECT_ROOT = getProjectRoot();
24
- const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
25
- const STATE_DIR = path.join(WORKFLOW_DIR, 'state');
26
- const MANIFEST_PATH = path.join(STATE_DIR, 'registry-manifest.json');
21
+ const MANIFEST_PATH = path.join(PATHS.state, 'registry-manifest.json');
27
22
  const REGISTRIES_DIR = path.join(__dirname, 'registries');
28
23
 
29
24
  // ============================================================
@@ -268,7 +263,7 @@ class RegistryManager {
268
263
  // Detect stack
269
264
  try {
270
265
  const { detectStack } = require('./flow-context-init');
271
- this.stack = detectStack(PROJECT_ROOT);
266
+ this.stack = detectStack(PATHS.root);
272
267
  } catch (err) {
273
268
  this.stack = null;
274
269
  }
@@ -355,7 +350,7 @@ class RegistryManager {
355
350
  };
356
351
 
357
352
  try {
358
- fs.mkdirSync(STATE_DIR, { recursive: true });
353
+ fs.mkdirSync(PATHS.state, { recursive: true });
359
354
  fs.writeFileSync(MANIFEST_PATH, JSON.stringify(manifest, null, 2));
360
355
  } catch (err) {
361
356
  warn(`Failed to write manifest: ${err.message}`);
@@ -440,7 +435,7 @@ function printStatus(manager) {
440
435
  if (fs.existsSync(MANIFEST_PATH)) {
441
436
  try {
442
437
  const manifest = safeJsonParse(MANIFEST_PATH, {});
443
- console.log(` Manifest: ${path.relative(PROJECT_ROOT, MANIFEST_PATH)}`);
438
+ console.log(` Manifest: ${path.relative(PATHS.root, MANIFEST_PATH)}`);
444
439
  console.log(` Generated: ${manifest.generatedAt || 'unknown'}`);
445
440
  console.log(` Version: ${manifest.version || 'unknown'}`);
446
441
  console.log(` Registries: ${(manifest.registries || []).length}`);
@@ -482,7 +477,7 @@ async function main() {
482
477
  }
483
478
 
484
479
  console.log('');
485
- success(`Manifest saved to ${path.relative(PROJECT_ROOT, MANIFEST_PATH)}`);
480
+ success(`Manifest saved to ${path.relative(PATHS.root, MANIFEST_PATH)}`);
486
481
  console.log('');
487
482
  break;
488
483
  }
@@ -493,7 +488,7 @@ async function main() {
493
488
 
494
489
  case 'manifest':
495
490
  manager.generateManifest();
496
- success(`Manifest saved to ${path.relative(PROJECT_ROOT, MANIFEST_PATH)}`);
491
+ success(`Manifest saved to ${path.relative(PATHS.root, MANIFEST_PATH)}`);
497
492
  break;
498
493
 
499
494
  case 'status':
@@ -17,12 +17,10 @@
17
17
  const fs = require('node:fs');
18
18
  const path = require('node:path');
19
19
  const { execFileSync } = require('node:child_process');
20
- const { getProjectRoot, colors, getConfig, safeJsonParse } = require('./flow-utils');
20
+ const { getProjectRoot, colors, getConfig, safeJsonParse, PATHS } = require('./flow-utils');
21
21
  const { getExecParts, getCommand } = require('./flow-script-resolver');
22
22
 
23
- const PROJECT_ROOT = getProjectRoot();
24
- const STATE_DIR = path.join(PROJECT_ROOT, '.workflow', 'state');
25
- const READY_PATH = path.join(STATE_DIR, 'ready.json');
23
+ const READY_PATH = PATHS.ready;
26
24
  const SAFE_PATH = /^[a-zA-Z0-9_.\-/]+$/;
27
25
 
28
26
  function log(color, ...args) {
@@ -56,8 +54,8 @@ function findTestFiles(taskId, taskData) {
56
54
  if (taskData?.testFiles) {
57
55
  for (const tf of taskData.testFiles) {
58
56
  if (typeof tf === 'string' && SAFE_PATH.test(tf) && !tf.includes('..')) {
59
- const resolved = path.resolve(PROJECT_ROOT, tf);
60
- if (resolved.startsWith(PROJECT_ROOT + path.sep) || resolved === PROJECT_ROOT) {
57
+ const resolved = path.resolve(PATHS.root, tf);
58
+ if (resolved.startsWith(PATHS.root + path.sep) || resolved === PATHS.root) {
61
59
  testFiles.push(tf);
62
60
  }
63
61
  }
@@ -80,14 +78,14 @@ function findTestFiles(taskId, taskData) {
80
78
  ];
81
79
 
82
80
  for (const pattern of testPatterns) {
83
- if (fs.existsSync(path.join(PROJECT_ROOT, pattern))) {
81
+ if (fs.existsSync(path.join(PATHS.root, pattern))) {
84
82
  testFiles.push(pattern);
85
83
  }
86
84
  }
87
85
  }
88
86
 
89
87
  // Also look in request-log for files changed
90
- const logPath = path.join(STATE_DIR, 'request-log.md');
88
+ const logPath = PATHS.requestLog;
91
89
  if (fs.existsSync(logPath)) {
92
90
  let logContent;
93
91
  try {
@@ -111,7 +109,7 @@ function findTestFiles(taskId, taskData) {
111
109
  `${base}.spec${ext}`,
112
110
  ];
113
111
  for (const pattern of testPatterns) {
114
- if (fs.existsSync(path.join(PROJECT_ROOT, pattern))) {
112
+ if (fs.existsSync(path.join(PATHS.root, pattern))) {
115
113
  testFiles.push(pattern);
116
114
  }
117
115
  }
@@ -147,7 +145,7 @@ function runTaskTests(taskId, taskData) {
147
145
  }
148
146
  const { cmd, args } = runner;
149
147
  execFileSync(cmd, args, {
150
- cwd: PROJECT_ROOT,
148
+ cwd: PATHS.root,
151
149
  stdio: 'pipe',
152
150
  timeout: 60000 // 1 minute timeout per task
153
151
  });
@@ -173,7 +171,7 @@ function runTaskTests(taskId, taskData) {
173
171
  * @returns {{ cmd: string, args: string[] }}
174
172
  */
175
173
  function detectTestRunner(testFiles) {
176
- const packageJson = path.join(PROJECT_ROOT, 'package.json');
174
+ const packageJson = path.join(PATHS.root, 'package.json');
177
175
 
178
176
  if (fs.existsSync(packageJson)) {
179
177
  try {
@@ -46,7 +46,7 @@ const MIN_ITEM_TITLE_LENGTH = 2; // Minimum length for a valid item title
46
46
  // Paths
47
47
  // Note: .workflow/roadmap.md is for USER project roadmaps managed by this module.
48
48
  // WogiFlow's own internal roadmap is at .workflow/roadmap/roadmap.md (separate file).
49
- const ROADMAP_PATH = path.join(PROJECT_ROOT, '.workflow', 'roadmap.md');
49
+ const ROADMAP_PATH = path.join(PATHS.workflow, 'roadmap.md');
50
50
  const TEMPLATE_PATH = path.join(__dirname, '..', 'templates', 'roadmap.md');
51
51
 
52
52
  // ============================================================
@@ -487,7 +487,7 @@ function validateItem(item) {
487
487
 
488
488
  // Check if dependency is in ready.json as completed
489
489
  let inReadyCompleted = false;
490
- const readyPath = path.join(PROJECT_ROOT, '.workflow', 'state', 'ready.json');
490
+ const readyPath = PATHS.ready;
491
491
  if (fileExists(readyPath)) {
492
492
  const ready = safeJsonParse(readyPath, {});
493
493
  const completed = ready.recentlyCompleted || [];
@@ -18,14 +18,12 @@
18
18
 
19
19
  const fs = require('node:fs');
20
20
  const path = require('node:path');
21
- const { readJson } = require('./flow-io');
21
+ const { readJson, safeJsonParse, safeJsonParseString } = require('./flow-io');
22
22
  const crypto = require('node:crypto');
23
- const { getProjectRoot, colors: c } = require('./flow-utils');
23
+ const { getProjectRoot, colors: c, PATHS } = require('./flow-utils');
24
24
  const { success: printSuccess } = require('./flow-output');
25
25
 
26
- const PROJECT_ROOT = getProjectRoot();
27
- const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
28
- const RUNS_DIR = path.join(WORKFLOW_DIR, 'runs');
26
+ const RUNS_DIR = PATHS.runs;
29
27
 
30
28
  // Event types
31
29
  const EVENT_TYPES = {
@@ -282,16 +280,17 @@ function endRun(runId, status = 'completed') {
282
280
  */
283
281
  function generateSummary(runId) {
284
282
  const runDir = path.join(RUNS_DIR, runId);
285
- const manifest = JSON.parse(
286
- fs.readFileSync(path.join(runDir, 'manifest.json'), 'utf-8')
287
- );
283
+ const manifest = safeJsonParse(path.join(runDir, 'manifest.json'), null);
284
+ if (!manifest) return;
285
+
288
286
  const tracePath = path.join(runDir, 'trace.jsonl');
289
287
 
290
288
  const events = fs.existsSync(tracePath)
291
289
  ? fs.readFileSync(tracePath, 'utf-8')
292
290
  .split('\n')
293
291
  .filter(line => line.trim())
294
- .map(line => JSON.parse(line))
292
+ .map(line => safeJsonParseString(line, null))
293
+ .filter(Boolean)
295
294
  : [];
296
295
 
297
296
  const durationSec = manifest.durationMs
@@ -421,7 +420,7 @@ function updateIndex(runId, manifest) {
421
420
  });
422
421
 
423
422
  // Load config for retention settings
424
- const configPath = path.join(WORKFLOW_DIR, 'config.json');
423
+ const configPath = path.join(PATHS.workflow, 'config.json');
425
424
  const config = readJson(configPath, {});
426
425
  const maxRuns = config.traces?.runs?.maxRuns || 100;
427
426
 
@@ -453,16 +452,18 @@ function inspectRun(runId) {
453
452
  throw new Error(`Run not found: ${runId}`);
454
453
  }
455
454
 
456
- const manifest = JSON.parse(
457
- fs.readFileSync(path.join(runDir, 'manifest.json'), 'utf-8')
458
- );
455
+ const manifest = safeJsonParse(path.join(runDir, 'manifest.json'), null);
456
+ if (!manifest) {
457
+ throw new Error(`Could not read manifest for run: ${runId}`);
458
+ }
459
459
 
460
460
  const tracePath = path.join(runDir, 'trace.jsonl');
461
461
  const events = fs.existsSync(tracePath)
462
462
  ? fs.readFileSync(tracePath, 'utf-8')
463
463
  .split('\n')
464
464
  .filter(line => line.trim())
465
- .map(line => JSON.parse(line))
465
+ .map(line => safeJsonParseString(line, null))
466
+ .filter(Boolean)
466
467
  : [];
467
468
 
468
469
  const summaryPath = path.join(runDir, 'summary.md');
@@ -477,7 +478,7 @@ function inspectRun(runId) {
477
478
  * Cleanup old runs based on retention policy
478
479
  */
479
480
  function cleanupRuns() {
480
- const configPath = path.join(WORKFLOW_DIR, 'config.json');
481
+ const configPath = path.join(PATHS.workflow, 'config.json');
481
482
  const cleanupConfig = readJson(configPath, {});
482
483
  const retentionDays = cleanupConfig.traces?.runs?.retentionDays || 30;
483
484
  const maxRuns = cleanupConfig.traces?.runs?.maxRuns || 100;
@@ -19,12 +19,9 @@
19
19
 
20
20
  const fs = require('node:fs');
21
21
  const path = require('node:path');
22
- const { getProjectRoot, getConfig, colors: c } = require('./flow-utils');
22
+ const { getProjectRoot, getConfig, colors: c, PATHS } = require('./flow-utils');
23
23
  const { success: printSuccess, error: printError } = require('./flow-output');
24
24
 
25
- const PROJECT_ROOT = getProjectRoot();
26
- const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
27
-
28
25
  /**
29
26
  * Custom error for safety violations
30
27
  */
@@ -213,7 +210,7 @@ class SafetyGuard {
213
210
 
214
211
  // Normalize path
215
212
  const normalizedPath = filePath.startsWith('/')
216
- ? path.relative(PROJECT_ROOT, filePath)
213
+ ? path.relative(PATHS.root, filePath)
217
214
  : filePath;
218
215
 
219
216
  // Check deny list first (takes precedence)