wogiflow 2.17.0 → 2.18.0

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 (111) hide show
  1. package/.claude/commands/wogi-audit.md +212 -17
  2. package/.claude/commands/wogi-research.md +37 -0
  3. package/.claude/commands/wogi-review.md +200 -22
  4. package/.claude/commands/wogi-start.md +45 -0
  5. package/.claude/docs/claude-code-compatibility.md +46 -1
  6. package/.claude/docs/intent-grounded-review.md +209 -0
  7. package/.claude/settings.json +34 -1
  8. package/.workflow/agents/logic-adversary.md +8 -0
  9. package/.workflow/templates/claude-md.hbs +18 -0
  10. package/lib/installer.js +22 -0
  11. package/lib/utils.js +29 -3
  12. package/lib/workspace-changelog.js +2 -1
  13. package/lib/workspace-channel-server.js +4 -6
  14. package/lib/workspace-contracts.js +5 -4
  15. package/lib/workspace-events.js +8 -7
  16. package/lib/workspace-gates.js +4 -3
  17. package/lib/workspace-integration-tests.js +2 -1
  18. package/lib/workspace-intelligence.js +3 -2
  19. package/lib/workspace-locks.js +2 -1
  20. package/lib/workspace-messages.js +7 -6
  21. package/lib/workspace-routing.js +14 -26
  22. package/lib/workspace-session.js +7 -6
  23. package/lib/workspace-sync.js +9 -8
  24. package/package.json +4 -2
  25. package/scripts/base-workflow-step.js +1 -1
  26. package/scripts/flow +19 -0
  27. package/scripts/flow-adaptive-learning.js +1 -1
  28. package/scripts/flow-aggregate.js +2 -1
  29. package/scripts/flow-architect-pass.js +3 -3
  30. package/scripts/flow-archive-runs.js +372 -0
  31. package/scripts/flow-ask.js +1 -1
  32. package/scripts/flow-ast-grep.js +216 -0
  33. package/scripts/flow-audit-gates.js +1 -1
  34. package/scripts/flow-auto-learn.js +8 -11
  35. package/scripts/flow-bug.js +2 -2
  36. package/scripts/flow-capture-gate.js +644 -0
  37. package/scripts/flow-capture.js +4 -3
  38. package/scripts/flow-cli-flags.js +95 -0
  39. package/scripts/flow-community-sync.js +2 -1
  40. package/scripts/flow-community.js +6 -6
  41. package/scripts/flow-conclusion-classifier.js +310 -0
  42. package/scripts/flow-config-defaults.js +3 -3
  43. package/scripts/flow-constants.js +8 -11
  44. package/scripts/flow-context-scoring.js +1 -0
  45. package/scripts/flow-correction-detector.js +344 -3
  46. package/scripts/flow-damage-control.js +1 -1
  47. package/scripts/flow-decisions-merge.js +1 -0
  48. package/scripts/flow-done-gates.js +20 -0
  49. package/scripts/flow-done-report.js +2 -2
  50. package/scripts/flow-done.js +4 -4
  51. package/scripts/flow-epics.js +5 -11
  52. package/scripts/flow-health.js +145 -1
  53. package/scripts/flow-id.js +92 -0
  54. package/scripts/flow-io.js +15 -5
  55. package/scripts/flow-knowledge-router.js +2 -1
  56. package/scripts/flow-links.js +1 -1
  57. package/scripts/flow-log-manager.js +2 -1
  58. package/scripts/flow-logic-adversary.js +4 -4
  59. package/scripts/flow-long-input-cli.js +6 -0
  60. package/scripts/flow-long-input-stories.js +1 -1
  61. package/scripts/flow-loop-retry-learning.js +1 -1
  62. package/scripts/flow-mcp-capabilities.js +2 -3
  63. package/scripts/flow-mcp-docs.js +2 -1
  64. package/scripts/flow-memory-blocks.js +2 -1
  65. package/scripts/flow-memory-sync.js +1 -1
  66. package/scripts/flow-memory.js +767 -0
  67. package/scripts/flow-migrate-igr.js +1 -1
  68. package/scripts/flow-migrate.js +2 -1
  69. package/scripts/flow-model-adapter.js +1 -1
  70. package/scripts/flow-model-config.js +5 -1
  71. package/scripts/flow-model-profile.js +2 -1
  72. package/scripts/flow-orchestrate.js +3 -3
  73. package/scripts/flow-output.js +29 -0
  74. package/scripts/flow-parallel.js +10 -9
  75. package/scripts/flow-pattern-enforcer.js +2 -1
  76. package/scripts/flow-permissions-audit.js +124 -0
  77. package/scripts/flow-plugin-registry.js +2 -2
  78. package/scripts/flow-progress.js +5 -1
  79. package/scripts/flow-project-analyzer.js +1 -1
  80. package/scripts/flow-promote.js +510 -0
  81. package/scripts/flow-registries.js +86 -0
  82. package/scripts/flow-request-log.js +133 -0
  83. package/scripts/flow-research-protocol.js +0 -1
  84. package/scripts/flow-revision-tracker.js +2 -1
  85. package/scripts/flow-roadmap.js +2 -1
  86. package/scripts/flow-rules-sync.js +3 -7
  87. package/scripts/flow-session-end.js +3 -1
  88. package/scripts/flow-session-learning.js +6 -13
  89. package/scripts/flow-session-state.js +2 -2
  90. package/scripts/flow-setup-hooks.js +2 -1
  91. package/scripts/flow-skill-create.js +1 -1
  92. package/scripts/flow-skill-freshness.js +6 -7
  93. package/scripts/flow-skill-learn.js +1 -1
  94. package/scripts/flow-step-coverage.js +1 -1
  95. package/scripts/flow-step-security.js +1 -1
  96. package/scripts/flow-story.js +58 -10
  97. package/scripts/flow-sys.js +204 -0
  98. package/scripts/flow-task-hierarchy.js +88 -0
  99. package/scripts/flow-tech-debt.js +2 -1
  100. package/scripts/flow-test-api.js +1 -1
  101. package/scripts/flow-utils.js +60 -890
  102. package/scripts/hooks/core/bugfix-scope-gate.js +5 -4
  103. package/scripts/hooks/core/deploy-gate.js +1 -1
  104. package/scripts/hooks/core/pre-tool-helpers.js +72 -0
  105. package/scripts/hooks/core/pre-tool-orchestrator.js +442 -0
  106. package/scripts/hooks/core/routing-gate.js +8 -0
  107. package/scripts/hooks/core/session-end.js +28 -0
  108. package/scripts/hooks/entry/claude-code/pre-tool-use.js +48 -492
  109. package/scripts/hooks/entry/shared/hook-runner.js +1 -1
  110. package/scripts/registries/schema-registry.js +1 -1
  111. package/scripts/registries/service-registry.js +1 -1
@@ -82,6 +82,123 @@ function checkClaudeCodeVersion() {
82
82
  }
83
83
  }
84
84
 
85
+ /**
86
+ * Normalize an MCP server config so two equivalent objects compare equal
87
+ * after JSON.stringify. Recursively sorts object keys and stringifies arrays
88
+ * in their original order (order is meaningful for args).
89
+ *
90
+ * @param {*} cfg - An MCP server config value (typically an object, but may be primitive)
91
+ * @returns {string} Canonical JSON representation suitable for equality comparison
92
+ */
93
+ function normalizeMcpConfig(cfg) {
94
+ const sortKeys = (v) => {
95
+ if (Array.isArray(v)) return v.map(sortKeys);
96
+ if (v && typeof v === 'object') {
97
+ const out = {};
98
+ for (const k of Object.keys(v).sort()) {
99
+ out[k] = sortKeys(v[k]);
100
+ }
101
+ return out;
102
+ }
103
+ return v;
104
+ };
105
+ return JSON.stringify(sortKeys(cfg));
106
+ }
107
+
108
+ /**
109
+ * Check MCP server definitions across the three Claude Code settings scopes:
110
+ * - user: ~/.claude/settings.json
111
+ * - project: <project>/.claude/settings.json
112
+ * - local: <project>/.claude/settings.local.json
113
+ *
114
+ * Mirrors the /doctor warning added in Claude Code 2.1.110: a server defined
115
+ * in multiple scopes with divergent config is almost always a mistake — the
116
+ * lowest-priority scope silently loses, and users debug ghost endpoints.
117
+ *
118
+ * Identical duplicate definitions are intentionally NOT flagged — some teams
119
+ * duplicate for portability. Only divergent configs surface.
120
+ *
121
+ * @param {Object} [opts]
122
+ * @param {string} [opts.userSettingsPath] - Override path to user scope (for tests)
123
+ * @param {string} [opts.projectSettingsPath] - Override path to project scope (for tests)
124
+ * @param {string} [opts.localSettingsPath] - Override path to local scope (for tests)
125
+ * @returns {{
126
+ * duplicates: Array<{name: string, scopes: string[]}>,
127
+ * uniqueServers: number,
128
+ * parseErrors: Array<{file: string, error: string}>,
129
+ * scopesChecked: number
130
+ * }}
131
+ */
132
+ function checkMcpScopes(opts = {}) {
133
+ const os = require('node:os');
134
+ const scopes = [
135
+ { file: opts.userSettingsPath || path.join(os.homedir(), '.claude', 'settings.json'), label: 'user' },
136
+ { file: opts.projectSettingsPath || path.join(PROJECT_ROOT, '.claude', 'settings.json'), label: 'project' },
137
+ { file: opts.localSettingsPath || path.join(PROJECT_ROOT, '.claude', 'settings.local.json'), label: 'local' }
138
+ ];
139
+
140
+ const parseErrors = [];
141
+ const serverByName = new Map();
142
+ let scopesChecked = 0;
143
+
144
+ // Sentinel object for safeJsonParse so we can distinguish "file failed to
145
+ // parse" (returned sentinel) from "file parsed but had no mcpServers" (returned
146
+ // object without the key). safeJsonParse is mandated by security-patterns.md §2
147
+ // (prototype-pollution guard + non-object validation) — we layer a raw-read
148
+ // fallback on top to categorize the failure cause for health output.
149
+ const PARSE_SENTINEL = Object.create(null);
150
+
151
+ for (const scope of scopes) {
152
+ if (!fileExists(scope.file)) continue;
153
+ scopesChecked++;
154
+ const json = safeJsonParse(scope.file, PARSE_SENTINEL);
155
+ if (json === PARSE_SENTINEL) {
156
+ // safeJsonParse returned the sentinel — categorize: read failure vs JSON
157
+ // parse failure vs non-object vs prototype-pollution rejection.
158
+ let raw;
159
+ try {
160
+ raw = fs.readFileSync(scope.file, 'utf-8');
161
+ } catch (err) {
162
+ parseErrors.push({ file: scope.file, error: `read failed: ${err.message}` });
163
+ continue;
164
+ }
165
+ try {
166
+ JSON.parse(raw);
167
+ // Raw parse succeeded — safeJsonParse rejected for structural reason
168
+ // (non-object top level, or dangerous __proto__/constructor keys).
169
+ parseErrors.push({ file: scope.file, error: 'rejected by safeJsonParse (non-object or prototype-pollution guard)' });
170
+ } catch (err) {
171
+ parseErrors.push({ file: scope.file, error: `invalid JSON: ${err.message}` });
172
+ }
173
+ continue;
174
+ }
175
+ const mcp = json.mcpServers;
176
+ if (!mcp || typeof mcp !== 'object' || Array.isArray(mcp)) continue;
177
+
178
+ for (const name of Object.keys(mcp)) {
179
+ if (!serverByName.has(name)) serverByName.set(name, []);
180
+ serverByName.get(name).push({ scope: scope.label, config: mcp[name] });
181
+ }
182
+ }
183
+
184
+ const duplicates = [];
185
+ for (const [name, entries] of serverByName.entries()) {
186
+ if (entries.length < 2) continue;
187
+ const canonical = normalizeMcpConfig(entries[0].config);
188
+ const allSame = entries.every(e => normalizeMcpConfig(e.config) === canonical);
189
+ if (!allSame) {
190
+ duplicates.push({ name, scopes: entries.map(e => e.scope) });
191
+ }
192
+ }
193
+
194
+ return {
195
+ duplicates,
196
+ uniqueServers: serverByName.size,
197
+ parseErrors,
198
+ scopesChecked
199
+ };
200
+ }
201
+
85
202
  function main() {
86
203
  console.log(color('cyan', 'Wogi Flow Health Check'));
87
204
  console.log('========================');
@@ -661,6 +778,29 @@ function main() {
661
778
  }
662
779
  }
663
780
 
781
+ // Check MCP server definitions across scopes (mirrors Claude Code 2.1.110 /doctor)
782
+ console.log('');
783
+ printSection('Checking MCP server scopes...');
784
+
785
+ const mcp = checkMcpScopes();
786
+ if (mcp.parseErrors.length > 0) {
787
+ for (const e of mcp.parseErrors) {
788
+ warn(`Could not parse ${e.file}: ${e.error}`);
789
+ }
790
+ warnings += mcp.parseErrors.length;
791
+ }
792
+ if (mcp.uniqueServers === 0) {
793
+ console.log(` ${color('dim', '○')} No MCP servers defined in user / project / local scopes`);
794
+ } else if (mcp.duplicates.length === 0) {
795
+ success(`No conflicting MCP server definitions across ${mcp.scopesChecked} scope(s)`);
796
+ } else {
797
+ for (const f of mcp.duplicates) {
798
+ warn(`MCP server "${f.name}" has divergent config in scopes: ${f.scopes.join(' + ')}`);
799
+ console.log(` ${color('dim', '→ Consolidate into a single scope; /doctor will flag this too')}`);
800
+ }
801
+ warnings += mcp.duplicates.length;
802
+ }
803
+
664
804
  // Check .gitignore sync
665
805
  console.log('');
666
806
  printSection('Checking .gitignore sync...');
@@ -1159,4 +1299,8 @@ function run() {
1159
1299
  }
1160
1300
  }
1161
1301
 
1162
- run();
1302
+ if (require.main === module) {
1303
+ run();
1304
+ }
1305
+
1306
+ module.exports = { checkMcpScopes, normalizeMcpConfig };
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Hash-Based ID Generation & Validation
5
+ *
6
+ * Generates collision-resistant 8-char hex IDs for work items:
7
+ * wf-XXXXXXXX (task), ep-XXXXXXXX (epic), ft-XXXXXXXX (feature), pl-XXXXXXXX (plan)
8
+ *
9
+ * Extracted from flow-utils.js (wf-94cc3b72 epic — flow-utils decomposition).
10
+ * Zero external deps beyond node:crypto — pure helpers.
11
+ */
12
+
13
+ 'use strict';
14
+
15
+ const crypto = require('node:crypto');
16
+
17
+ /**
18
+ * Generate a hash-based ID with a given prefix.
19
+ * Uses SHA256 of (seed + title + timestamp + randomBytes) for collision resistance.
20
+ *
21
+ * @param {string} prefix - ID prefix (e.g., 'wf', 'ep', 'ft', 'pl')
22
+ * @param {string} seed - Seed string for the hash (e.g., '', 'epic-', 'feature-')
23
+ * @param {string} title - Title to include in hash input
24
+ * @returns {string} ID in format prefix-XXXXXXXX
25
+ */
26
+ function generateHashId(prefix, seed, title) {
27
+ const randomHex = crypto.randomBytes(8).toString('hex');
28
+ const input = `${seed}${title}${Date.now()}${randomHex}`;
29
+ const hash = crypto.createHash('sha256').update(input).digest('hex').slice(0, 8);
30
+ return `${prefix}-${hash}`;
31
+ }
32
+
33
+ /**
34
+ * Generate a hash-based task ID
35
+ * Format: wf-XXXXXXXX (8-char hex hash)
36
+ * @example generateTaskId('Fix login bug') // => 'wf-a1b2c3d4'
37
+ */
38
+ function generateTaskId(title) {
39
+ return generateHashId('wf', '', title);
40
+ }
41
+
42
+ /** Generate a hash-based epic ID (ep-XXXXXXXX) */
43
+ function generateEpicId(title) {
44
+ return generateHashId('ep', 'epic-', title);
45
+ }
46
+
47
+ /** Generate a hash-based feature ID (ft-XXXXXXXX) */
48
+ function generateFeatureId(title) {
49
+ return generateHashId('ft', 'feature-', title);
50
+ }
51
+
52
+ /** Generate a hash-based plan ID (pl-XXXXXXXX) */
53
+ function generatePlanId(title) {
54
+ return generateHashId('pl', 'plan-', title);
55
+ }
56
+
57
+ /**
58
+ * Check if a string is a valid task ID (old or new format)
59
+ * @returns {{ valid: boolean, format: 'hash' | 'legacy' | null }}
60
+ */
61
+ function validateTaskId(id) {
62
+ if (!id || typeof id !== 'string') {
63
+ return { valid: false, format: null };
64
+ }
65
+
66
+ // New hash-based format: wf-XXXXXXXX
67
+ if (/^wf-[a-f0-9]{8}$/i.test(id)) {
68
+ return { valid: true, format: 'hash' };
69
+ }
70
+
71
+ // Legacy formats: TASK-XXX, BUG-XXX
72
+ if (/^(TASK|BUG)-\d{3,}$/i.test(id)) {
73
+ return { valid: true, format: 'legacy' };
74
+ }
75
+
76
+ return { valid: false, format: null };
77
+ }
78
+
79
+ /** Check if ID is in legacy format (for migration warnings) */
80
+ function isLegacyTaskId(id) {
81
+ return /^(TASK|BUG)-\d{3,}$/i.test(id);
82
+ }
83
+
84
+ module.exports = {
85
+ generateHashId,
86
+ generateTaskId,
87
+ generateEpicId,
88
+ generateFeatureId,
89
+ generatePlanId,
90
+ validateTaskId,
91
+ isLegacyTaskId,
92
+ };
@@ -16,6 +16,15 @@ const { PROJECT_ROOT } = require('./flow-paths');
16
16
  // Constants - Named values for magic numbers
17
17
  // ============================================================
18
18
 
19
+ /**
20
+ * Canonical DANGEROUS_KEYS — prototype-pollution guard constant used by
21
+ * JSON parse safety checks, plugin-name validation, frontmatter parsers.
22
+ * Consolidated 2026-04-15 per audit dup-002 (wf-2f6fbb12). All scripts/
23
+ * consumers should import from here; lib/ has its own local copy to avoid
24
+ * lib→scripts dependency per dual-repo-management.md.
25
+ */
26
+ const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
27
+
19
28
  /** Default lock stale threshold (60 seconds) */
20
29
  const LOCK_STALE_THRESHOLD_MS = 60000;
21
30
 
@@ -607,9 +616,9 @@ function cleanupStaleLocks(dirPath, staleMs = CLEANUP_LOCK_STALE_MS) {
607
616
  try {
608
617
  fs.rmSync(lockDir, { recursive: true, force: true });
609
618
  cleaned++;
610
- } catch (err2) {
619
+ } catch (err) {
611
620
  if (process.env.DEBUG) {
612
- console.warn(`[DEBUG] cleanupStaleLocks: Could not force delete ${lockDir}: ${err2.message}`);
621
+ console.warn(`[DEBUG] cleanupStaleLocks: Could not force delete ${lockDir}: ${err.message}`);
613
622
  }
614
623
  }
615
624
  }
@@ -626,10 +635,10 @@ function cleanupStaleLocks(dirPath, staleMs = CLEANUP_LOCK_STALE_MS) {
626
635
  fs.rmSync(lockDir, { recursive: true, force: true });
627
636
  cleaned++;
628
637
  }
629
- } catch (err2) {
638
+ } catch (err) {
630
639
  // Directory gone or inaccessible - skip
631
- if (err2.code !== 'ENOENT' && process.env.DEBUG) {
632
- console.warn(`[DEBUG] cleanupStaleLocks: Could not stat ${lockDir}: ${err2.message}`);
640
+ if (err.code !== 'ENOENT' && process.env.DEBUG) {
641
+ console.warn(`[DEBUG] cleanupStaleLocks: Could not stat ${lockDir}: ${err.message}`);
633
642
  }
634
643
  }
635
644
  }
@@ -666,6 +675,7 @@ function sanitizeForContext(value, maxLen = 200) {
666
675
 
667
676
  module.exports = {
668
677
  // Constants
678
+ DANGEROUS_KEYS,
669
679
  LOCK_STALE_THRESHOLD_MS,
670
680
  CLEANUP_LOCK_STALE_MS,
671
681
  LOCK_RETRY_DELAY_MS,
@@ -33,7 +33,8 @@ const {
33
33
  readFile,
34
34
  writeFile,
35
35
  fileExists,
36
- printHeader
36
+ printHeader,
37
+ getTodayDate
37
38
  } = require('./flow-utils');
38
39
 
39
40
  // Import from skill-learn and model-adapter to avoid duplication
@@ -25,7 +25,7 @@ const path = require('node:path');
25
25
  const https = require('node:https');
26
26
  const http = require('node:http');
27
27
  const dns = require('dns');
28
- const { getProjectRoot, colors: c, readJson, PATHS } = require('./flow-utils');
28
+ const { getProjectRoot, colors: c, readJson, PATHS, getTodayDate } = require('./flow-utils');
29
29
  const { success: printSuccess } = require('./flow-output');
30
30
 
31
31
  const LINKS_PATH = path.join(PATHS.workflow, 'links.yaml');
@@ -30,7 +30,8 @@ const {
30
30
  fileExists,
31
31
  dirExists,
32
32
  countRequestLogEntries,
33
- printHeader
33
+ printHeader,
34
+ getTodayDate
34
35
  } = require('./flow-utils');
35
36
 
36
37
  // v9.0: Memory database for queryable log storage
@@ -53,7 +53,7 @@ const gateTelemetry = require('./flow-gate-telemetry');
53
53
  // ============================================================
54
54
 
55
55
  const RUBRIC_DIR = path.join(PATHS.workflow, 'rubrics');
56
- const DEFAULT_RUBRIC = 'logic-constitution-v2';
56
+ const DEFAULT_RUBRIC = 'logic-constitution-v3';
57
57
  const CALIBRATION_PATH = path.join(PATHS.state, 'adversary-calibration.json');
58
58
 
59
59
  const VALID_OVERALL_VERDICTS = new Set([
@@ -84,7 +84,7 @@ const INTENT_ARTIFACTS = {
84
84
 
85
85
  /**
86
86
  * Load the Logic Constitution rubric by version identifier.
87
- * @param {string} [version='logic-constitution-v2']
87
+ * @param {string} [version='logic-constitution-v3']
88
88
  * @returns {{ content: string, version: string, path: string }}
89
89
  */
90
90
  function loadRubric(version = DEFAULT_RUBRIC) {
@@ -149,7 +149,7 @@ function readIntentArtifact(key) {
149
149
  * @param {string} [opts.taskId]
150
150
  * @param {number} [opts.round=1] - Iteration round number (≥1).
151
151
  * @param {object} [opts.previousAdversaryOutput] - Prior round's parsed output when re-running.
152
- * @param {string} [opts.rubricVersion='logic-constitution-v2']
152
+ * @param {string} [opts.rubricVersion='logic-constitution-v3']
153
153
  * @param {number} [opts.calibrationCount=3] - How many calibration examples to inject.
154
154
  * @returns {{ systemPrompt:string, userPrompt:string, metadata:object }}
155
155
  */
@@ -317,7 +317,7 @@ All 10 principles must appear in the \`principles\` array, even if SKIPped.
317
317
  * @param {Object} ctx
318
318
  * @param {string} [ctx.taskId]
319
319
  * @param {number} [ctx.round=1]
320
- * @param {string} [ctx.rubricVersion='logic-constitution-v2']
320
+ * @param {string} [ctx.rubricVersion='logic-constitution-v3']
321
321
  * @returns {object} The validated adversary verdict.
322
322
  */
323
323
  function parseAdversaryOutput(response, ctx = {}) {
@@ -20,6 +20,12 @@ const c = {
20
20
  red: '\x1b[31m'
21
21
  };
22
22
 
23
+ // Local helper — 5 call sites referenced printWarn without defining it
24
+ // (latent no-undef bug caught by eslint upgrade; wf-5a6df88a).
25
+ function printWarn(msg) {
26
+ console.warn(`${c.yellow}⚠ ${msg}${c.reset}`);
27
+ }
28
+
23
29
  /**
24
30
  * CLI handler
25
31
  */
@@ -2150,7 +2150,7 @@ function cleanupTempFiles(digestId) {
2150
2150
  if (active && active.session?.digest_id === digestId) {
2151
2151
  try {
2152
2152
  fs.unlinkSync(activeFile);
2153
- } catch (_unlinkErr) {
2153
+ } catch (_err) {
2154
2154
  // Ignore errors unlinking active file - main cleanup succeeded
2155
2155
  }
2156
2156
  }
@@ -15,7 +15,7 @@
15
15
 
16
16
  const fs = require('node:fs');
17
17
  const path = require('node:path');
18
- const { getConfig, getProjectRoot, colors, readJson, info, success, PATHS } = require('./flow-utils');
18
+ const { getConfig, getProjectRoot, colors, readJson, info, success, PATHS, getTodayDate } = require('./flow-utils');
19
19
  const { FailureCategory, detectCategory } = require('./flow-failure-categories');
20
20
 
21
21
  const LEARNING_LOG_PATH = path.join(PATHS.state, 'adaptive-learning.json');
@@ -32,10 +32,9 @@
32
32
 
33
33
  const fs = require('node:fs');
34
34
  const path = require('node:path');
35
- const { PATHS, getConfig, safeJsonParse, readJson, writeJson, fileExists } = require('./flow-utils');
35
+ const { PATHS, getConfig, safeJsonParse, readJson, writeJson, fileExists, DANGEROUS_KEYS } = require('./flow-utils');
36
36
 
37
- // Prototype pollution protection same pattern as flow-plugin-registry.js
38
- const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
37
+ // Local DANGEROUS_KEYS consolidated to flow-io canonical (wf-2f6fbb12 / dup-002).
39
38
 
40
39
  // ============================================================
41
40
  // Constants
@@ -19,7 +19,8 @@ const path = require('node:path');
19
19
  const {
20
20
  getProjectRoot,
21
21
  safeJsonParse,
22
- fileExists
22
+ fileExists,
23
+ PATHS
23
24
  } = require('./flow-utils')
24
25
  const { color, printHeader, printSection, success } = require('./flow-output');;
25
26
 
@@ -27,7 +27,8 @@ const {
27
27
  writeFile,
28
28
  fileExists,
29
29
  printHeader,
30
- safeJsonParseString
30
+ safeJsonParseString,
31
+ getTodayDate
31
32
  } = require('./flow-utils');
32
33
 
33
34
  // ============================================================
@@ -22,6 +22,7 @@ const path = require('node:path');
22
22
  const memoryDb = require('./flow-memory-db');
23
23
  const { getConfig, PROJECT_ROOT } = require('./flow-config-loader');
24
24
  const { color } = require('./flow-output');
25
+ const { PATHS } = require('./flow-utils');
25
26
 
26
27
  // Lazy-load to avoid circular dependency
27
28
  let _syncDecisionsToRules = null;
@@ -469,7 +470,6 @@ module.exports = {
469
470
  listCandidates,
470
471
  promoteFact,
471
472
  showStatus,
472
- loadConfig
473
473
  };
474
474
 
475
475
  // Run CLI if executed directly