wogiflow 2.4.2 → 2.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/wogi-start.md +124 -0
- package/.claude/docs/claude-code-compatibility.md +51 -0
- package/.claude/docs/explore-agents.md +11 -0
- package/.claude/settings.json +12 -1
- package/.workflow/models/registry.json +1 -1
- package/bin/flow +11 -1
- package/lib/workspace-contracts.js +599 -0
- package/lib/workspace-intelligence.js +600 -0
- package/lib/workspace-messages.js +441 -0
- package/lib/workspace-routing.js +485 -0
- package/lib/workspace-sync.js +339 -0
- package/lib/workspace.js +1073 -0
- package/package.json +4 -4
- package/scripts/MEMORY-ARCHITECTURE.md +1 -1
- package/scripts/base-workflow-step.js +136 -0
- package/scripts/flow-adaptive-learning.js +8 -9
- package/scripts/flow-aggregate.js +11 -6
- package/scripts/flow-api-index.js +4 -6
- package/scripts/flow-assumption-detector.js +0 -2
- package/scripts/flow-audit.js +15 -2
- package/scripts/flow-auto-context.js +8 -12
- package/scripts/flow-auto-learn.js +49 -49
- package/scripts/flow-background.js +5 -6
- package/scripts/flow-bridge-state.js +8 -10
- package/scripts/flow-bulk-loop.js +1 -3
- package/scripts/flow-bulk-orchestrator.js +1 -3
- package/scripts/flow-cascade-completion.js +0 -2
- package/scripts/flow-cascade.js +4 -4
- package/scripts/flow-checkpoint.js +10 -13
- package/scripts/flow-code-intelligence.js +10 -12
- package/scripts/flow-community-sync.js +4 -4
- package/scripts/flow-community.js +12 -20
- package/scripts/flow-config-defaults.js +28 -2
- package/scripts/flow-config-interactive.js +9 -5
- package/scripts/flow-config-loader.js +49 -92
- package/scripts/flow-config-substitution.js +0 -2
- package/scripts/flow-context-estimator.js +4 -4
- package/scripts/flow-context-init.js +10 -12
- package/scripts/flow-context-manager.js +0 -2
- package/scripts/flow-context-scoring.js +2 -2
- package/scripts/flow-contract-scan.js +6 -9
- package/scripts/flow-correct.js +29 -27
- package/scripts/flow-correction-detector.js +5 -1
- package/scripts/flow-damage-control.js +47 -54
- package/scripts/flow-decisions-merge.js +4 -14
- package/scripts/flow-diff.js +5 -8
- package/scripts/flow-done-gates.js +786 -0
- package/scripts/flow-done-report.js +123 -0
- package/scripts/flow-done.js +71 -717
- package/scripts/flow-entropy-monitor.js +1 -3
- package/scripts/flow-eval-calibration.js +257 -0
- package/scripts/flow-eval-judge.js +10 -1
- package/scripts/flow-eval.js +14 -5
- package/scripts/flow-extraction-review.js +1 -0
- package/scripts/flow-failure-categories.js +0 -2
- package/scripts/flow-figma-confirm.js +5 -9
- package/scripts/flow-figma-generate.js +8 -10
- package/scripts/flow-figma-index.js +8 -10
- package/scripts/flow-figma-match.js +3 -5
- package/scripts/flow-figma-mcp-server.js +2 -4
- package/scripts/flow-figma-orchestrator.js +2 -3
- package/scripts/flow-figma-registry.js +2 -3
- package/scripts/flow-framework-resolver.js +0 -2
- package/scripts/flow-function-index.js +4 -6
- package/scripts/flow-gate-confidence.js +2 -2
- package/scripts/flow-gitignore.js +0 -2
- package/scripts/flow-guided-edit.js +5 -6
- package/scripts/flow-health.js +5 -6
- package/scripts/flow-hook-errors.js +6 -0
- package/scripts/flow-hook-status.js +263 -0
- package/scripts/flow-hooks.js +17 -29
- package/scripts/flow-http-client.js +9 -8
- package/scripts/flow-hybrid-interactive.js +7 -12
- package/scripts/flow-hybrid-test.js +12 -13
- package/scripts/flow-instruction-richness.js +1 -1
- package/scripts/flow-io.js +21 -4
- package/scripts/flow-knowledge-router.js +9 -3
- package/scripts/flow-learning-orchestrator.js +318 -13
- package/scripts/flow-links.js +5 -7
- package/scripts/flow-long-input-association.js +275 -0
- package/scripts/flow-long-input-chunking.js +1 -0
- package/scripts/flow-long-input-cli.js +0 -2
- package/scripts/flow-long-input-complexity.js +0 -2
- package/scripts/flow-long-input-constants.js +0 -2
- package/scripts/flow-long-input-contradictions.js +351 -0
- package/scripts/flow-long-input-detection.js +0 -2
- package/scripts/flow-long-input-passes.js +885 -0
- package/scripts/flow-long-input-stories.js +1 -1
- package/scripts/flow-long-input-voice.js +0 -2
- package/scripts/flow-long-input.js +425 -3005
- package/scripts/flow-loop-retry-learning.js +2 -3
- package/scripts/flow-lsp.js +3 -3
- package/scripts/flow-mcp-docs.js +3 -4
- package/scripts/flow-memory-db.js +6 -8
- package/scripts/flow-memory-sync.js +18 -11
- package/scripts/flow-metrics.js +1 -2
- package/scripts/flow-model-adapter.js +2 -3
- package/scripts/flow-model-config.js +72 -104
- package/scripts/flow-model-router.js +2 -2
- package/scripts/flow-model-types.js +0 -2
- package/scripts/flow-multi-approach.js +5 -6
- package/scripts/flow-orchestrate-context.js +3 -7
- package/scripts/flow-orchestrate-rollback.js +3 -8
- package/scripts/flow-orchestrate-state.js +8 -14
- package/scripts/flow-orchestrate-templates.js +2 -6
- package/scripts/flow-orchestrate-validator.js +5 -9
- package/scripts/flow-orchestrate.js +126 -103
- package/scripts/flow-output.js +0 -2
- package/scripts/flow-parallel.js +1 -1
- package/scripts/flow-paths.js +23 -2
- package/scripts/flow-pattern-enforcer.js +30 -28
- package/scripts/flow-pattern-extractor.js +3 -4
- package/scripts/flow-pending.js +0 -2
- package/scripts/flow-permissions.js +2 -3
- package/scripts/flow-plugin-registry.js +10 -12
- package/scripts/flow-prd-manager.js +1 -1
- package/scripts/flow-progress.js +7 -9
- package/scripts/flow-prompt-composer.js +3 -3
- package/scripts/flow-prompt-template.js +2 -2
- package/scripts/flow-providers.js +7 -4
- package/scripts/flow-registry-manager.js +7 -12
- package/scripts/flow-regression.js +9 -11
- package/scripts/flow-roadmap.js +2 -2
- package/scripts/flow-run-trace.js +16 -15
- package/scripts/flow-safety.js +2 -5
- package/scripts/flow-scanner-base.js +5 -7
- package/scripts/flow-scenario-engine.js +1 -5
- package/scripts/flow-security.js +29 -0
- package/scripts/flow-session-end.js +32 -41
- package/scripts/flow-session-learning.js +53 -49
- package/scripts/flow-setup-hooks.js +2 -3
- package/scripts/flow-skill-create.js +7 -12
- package/scripts/flow-skill-generator.js +12 -16
- package/scripts/flow-skill-learn.js +17 -8
- package/scripts/flow-skill-matcher.js +1 -2
- package/scripts/flow-spec-generator.js +2 -4
- package/scripts/flow-stack-wizard.js +5 -7
- package/scripts/flow-standards-learner.js +35 -16
- package/scripts/flow-start.js +2 -0
- package/scripts/flow-stats-collector.js +2 -2
- package/scripts/flow-status.js +10 -10
- package/scripts/flow-statusline-setup.js +2 -2
- package/scripts/flow-step-changelog.js +2 -3
- package/scripts/flow-step-comments.js +66 -81
- package/scripts/flow-step-complexity.js +50 -70
- package/scripts/flow-step-coverage.js +3 -5
- package/scripts/flow-step-knowledge.js +2 -3
- package/scripts/flow-step-pr-tests.js +64 -74
- package/scripts/flow-step-regression.js +3 -5
- package/scripts/flow-step-review.js +86 -103
- package/scripts/flow-step-security.js +111 -121
- package/scripts/flow-step-silent-failures.js +56 -83
- package/scripts/flow-step-simplifier.js +52 -70
- package/scripts/flow-story.js +4 -7
- package/scripts/flow-strict-adherence.js +3 -4
- package/scripts/flow-task-checkpoint.js +36 -5
- package/scripts/flow-task-enforcer.js +2 -24
- package/scripts/flow-tech-debt.js +1 -1
- package/scripts/flow-template-extractor.js +1 -0
- package/scripts/flow-templates.js +11 -13
- package/scripts/flow-test-api.js +9 -13
- package/scripts/flow-test-discovery.js +1 -1
- package/scripts/flow-test-generate.js +5 -9
- package/scripts/flow-test-integrity.js +3 -7
- package/scripts/flow-test-ui.js +5 -9
- package/scripts/flow-testing-deps.js +1 -3
- package/scripts/flow-tiered-learning.js +4 -4
- package/scripts/flow-todowrite-sync.js +1 -1
- package/scripts/flow-tokens.js +0 -2
- package/scripts/flow-verification-profile.js +6 -10
- package/scripts/flow-verify.js +12 -16
- package/scripts/flow-version-check.js +4 -12
- package/scripts/flow-webmcp-generator.js +3 -5
- package/scripts/flow-workflow-steps.js +0 -2
- package/scripts/flow-workflow.js +9 -11
- package/scripts/hooks/adapters/claude-code.js +31 -0
- package/scripts/hooks/core/config-change.js +1 -0
- package/scripts/hooks/core/extension-registry.js +0 -2
- package/scripts/hooks/core/instructions-loaded.js +1 -1
- package/scripts/hooks/core/observation-capture.js +5 -5
- package/scripts/hooks/core/phase-gate.js +5 -0
- package/scripts/hooks/core/post-compact.js +1 -12
- package/scripts/hooks/core/research-gate.js +2 -12
- package/scripts/hooks/core/routing-gate.js +6 -0
- package/scripts/hooks/core/task-completed.js +12 -0
- package/scripts/hooks/core/task-created.js +83 -0
- package/scripts/hooks/core/worktree-lifecycle.js +1 -1
- package/scripts/hooks/entry/claude-code/config-change.js +6 -29
- package/scripts/hooks/entry/claude-code/instructions-loaded.js +5 -30
- package/scripts/hooks/entry/claude-code/post-compact.js +4 -31
- package/scripts/hooks/entry/claude-code/post-tool-use.js +121 -172
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +260 -361
- package/scripts/hooks/entry/claude-code/session-end.js +4 -28
- package/scripts/hooks/entry/claude-code/session-start.js +205 -243
- package/scripts/hooks/entry/claude-code/setup.js +8 -49
- package/scripts/hooks/entry/claude-code/stop.js +40 -72
- package/scripts/hooks/entry/claude-code/task-completed.js +4 -28
- package/scripts/hooks/entry/claude-code/task-created.js +15 -0
- package/scripts/hooks/entry/claude-code/user-prompt-submit.js +113 -195
- package/scripts/hooks/entry/claude-code/worktree-create.js +6 -25
- package/scripts/hooks/entry/claude-code/worktree-remove.js +6 -25
- package/scripts/hooks/entry/shared/hook-runner.js +99 -0
- package/scripts/hooks/entry/shared/read-stdin.js +0 -2
- package/scripts/postinstall.js +2 -0
- package/scripts/registries/api-registry.js +0 -2
- package/scripts/registries/component-registry.js +5 -9
- package/scripts/registries/contract-scanner.js +2 -9
- package/scripts/registries/function-registry.js +0 -2
- package/scripts/registries/schema-registry.js +14 -18
- package/scripts/registries/service-registry.js +23 -27
package/scripts/flow-io.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* Wogi Flow - File I/O Operations
|
|
5
3
|
*
|
|
@@ -510,7 +508,7 @@ async function acquireLock(filePath, options = {}) {
|
|
|
510
508
|
if (process.env.DEBUG) {
|
|
511
509
|
console.warn(`[DEBUG] Stale lock cleanup failed: ${err.message}`);
|
|
512
510
|
}
|
|
513
|
-
await
|
|
511
|
+
await require('node:timers/promises').setTimeout(retryDelay);
|
|
514
512
|
}
|
|
515
513
|
// Try again
|
|
516
514
|
continue;
|
|
@@ -521,7 +519,7 @@ async function acquireLock(filePath, options = {}) {
|
|
|
521
519
|
const delay = exponentialBackoff
|
|
522
520
|
? retryDelay * Math.pow(2, attempt)
|
|
523
521
|
: retryDelay * (attempt + 1);
|
|
524
|
-
await
|
|
522
|
+
await require('node:timers/promises').setTimeout(delay);
|
|
525
523
|
continue;
|
|
526
524
|
}
|
|
527
525
|
}
|
|
@@ -646,6 +644,22 @@ function cleanupStaleLocks(dirPath, staleMs = CLEANUP_LOCK_STALE_MS) {
|
|
|
646
644
|
}
|
|
647
645
|
}
|
|
648
646
|
|
|
647
|
+
// ============================================================
|
|
648
|
+
// String Sanitization (for AI context injection)
|
|
649
|
+
// ============================================================
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Sanitize a string value before injecting into AI context.
|
|
653
|
+
* Strips markdown heading markers and truncates to prevent prompt manipulation.
|
|
654
|
+
*
|
|
655
|
+
* @param {string} value - Raw string from state files
|
|
656
|
+
* @param {number} [maxLen=200] - Maximum length
|
|
657
|
+
* @returns {string} Sanitized string
|
|
658
|
+
*/
|
|
659
|
+
function sanitizeForContext(value, maxLen = 200) {
|
|
660
|
+
return String(value).replace(/^#+\s/gm, '').slice(0, maxLen);
|
|
661
|
+
}
|
|
662
|
+
|
|
649
663
|
// ============================================================
|
|
650
664
|
// Exports
|
|
651
665
|
// ============================================================
|
|
@@ -689,4 +703,7 @@ module.exports = {
|
|
|
689
703
|
withLock,
|
|
690
704
|
withLockSync,
|
|
691
705
|
cleanupStaleLocks,
|
|
706
|
+
|
|
707
|
+
// String Sanitization
|
|
708
|
+
sanitizeForContext,
|
|
692
709
|
};
|
|
@@ -303,10 +303,16 @@ ${correction}
|
|
|
303
303
|
`;
|
|
304
304
|
|
|
305
305
|
content += entry;
|
|
306
|
-
fs.writeFileSync(decisionsPath, content);
|
|
307
306
|
|
|
308
|
-
//
|
|
309
|
-
|
|
307
|
+
// Route through orchestrator for locking and dedup
|
|
308
|
+
try {
|
|
309
|
+
const { writeToDecisions } = require('./flow-learning-orchestrator');
|
|
310
|
+
await writeToDecisions({ content, entryText: correction.slice(0, 100), caller: 'flow-knowledge-router', skipDedup: false, syncRules: true });
|
|
311
|
+
} catch (_err) {
|
|
312
|
+
// Fallback to direct write if orchestrator unavailable
|
|
313
|
+
fs.writeFileSync(decisionsPath, content);
|
|
314
|
+
syncDecisionsToRules();
|
|
315
|
+
}
|
|
310
316
|
|
|
311
317
|
return {
|
|
312
318
|
success: true,
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
* Learning Orchestrator
|
|
2
|
+
* Learning Orchestrator — Centralized Write Mediator
|
|
3
|
+
*
|
|
4
|
+
* ALL writes to feedback-patterns.md and decisions.md MUST go through this module.
|
|
5
|
+
* Direct fs.writeFileSync to these files from learning modules is prohibited.
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Centralized write API with dedup checking
|
|
9
|
+
* - Write locking via acquireLock to prevent race conditions
|
|
10
|
+
* - Fuzzy dedup: rejects writes that duplicate existing entries
|
|
8
11
|
*
|
|
9
12
|
* Sub-modules (lazy-loaded to avoid startup cost):
|
|
10
13
|
* - flow-skill-learn.js — Skill discovery and learning
|
|
@@ -20,6 +23,16 @@
|
|
|
20
23
|
* - flow-standards-learner.js — Standards violation learning
|
|
21
24
|
*/
|
|
22
25
|
|
|
26
|
+
const fs = require('node:fs');
|
|
27
|
+
const { PATHS } = require('./flow-paths');
|
|
28
|
+
const { acquireLock, readFile, writeFile, fileExists } = require('./flow-io');
|
|
29
|
+
|
|
30
|
+
// ============================================================
|
|
31
|
+
// Constants
|
|
32
|
+
// ============================================================
|
|
33
|
+
|
|
34
|
+
const DEDUP_SIMILARITY_THRESHOLD = 0.85; // 85% similarity = duplicate
|
|
35
|
+
|
|
23
36
|
// ============================================================
|
|
24
37
|
// Lazy Loaders
|
|
25
38
|
// ============================================================
|
|
@@ -36,6 +49,287 @@ function getFailureLearning() { return require('./flow-failure-learning'); }
|
|
|
36
49
|
function getAutoLearn() { return require('./flow-auto-learn'); }
|
|
37
50
|
function getStandardsLearner() { return require('./flow-standards-learner'); }
|
|
38
51
|
|
|
52
|
+
// ============================================================
|
|
53
|
+
// Dedup Utilities
|
|
54
|
+
// ============================================================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Normalize text for dedup comparison: lowercase, strip punctuation, collapse whitespace.
|
|
58
|
+
* @param {string} text
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
61
|
+
function normalizeForDedup(text) {
|
|
62
|
+
if (!text || typeof text !== 'string') return '';
|
|
63
|
+
return text
|
|
64
|
+
.toLowerCase()
|
|
65
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
66
|
+
.replace(/\s+/g, ' ')
|
|
67
|
+
.trim();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Compute bigram similarity between two strings (Dice coefficient).
|
|
72
|
+
* Returns 0..1 where 1 = identical.
|
|
73
|
+
* @param {string} a
|
|
74
|
+
* @param {string} b
|
|
75
|
+
* @returns {number}
|
|
76
|
+
*/
|
|
77
|
+
function bigramSimilarity(a, b) {
|
|
78
|
+
const na = normalizeForDedup(a);
|
|
79
|
+
const nb = normalizeForDedup(b);
|
|
80
|
+
|
|
81
|
+
if (na === nb) return 1.0;
|
|
82
|
+
if (na.length < 2 || nb.length < 2) return 0;
|
|
83
|
+
|
|
84
|
+
const bigramsA = new Set();
|
|
85
|
+
for (let i = 0; i < na.length - 1; i++) bigramsA.add(na.slice(i, i + 2));
|
|
86
|
+
const bigramsB = new Set();
|
|
87
|
+
for (let i = 0; i < nb.length - 1; i++) bigramsB.add(nb.slice(i, i + 2));
|
|
88
|
+
|
|
89
|
+
let intersection = 0;
|
|
90
|
+
for (const bg of bigramsA) {
|
|
91
|
+
if (bigramsB.has(bg)) intersection++;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (2 * intersection) / (bigramsA.size + bigramsB.size);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Check if an entry already exists in file content (fuzzy match).
|
|
99
|
+
* Checks both exact substring and bigram similarity against existing entries.
|
|
100
|
+
*
|
|
101
|
+
* @param {string} content - Current file content
|
|
102
|
+
* @param {string} entryText - The new entry text to check
|
|
103
|
+
* @returns {{ isDuplicate: boolean, matchedText?: string, similarity?: number }}
|
|
104
|
+
*/
|
|
105
|
+
function checkDuplicate(content, entryText) {
|
|
106
|
+
if (!content || !entryText) return { isDuplicate: false };
|
|
107
|
+
|
|
108
|
+
const normalizedEntry = normalizeForDedup(entryText);
|
|
109
|
+
if (!normalizedEntry || normalizedEntry.length < 5) return { isDuplicate: false };
|
|
110
|
+
|
|
111
|
+
// Exact substring check (case-insensitive)
|
|
112
|
+
if (content.toLowerCase().includes(normalizedEntry)) {
|
|
113
|
+
return { isDuplicate: true, matchedText: entryText, similarity: 1.0 };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Extract existing entries from tables (pipe-delimited rows) and headings
|
|
117
|
+
const lines = content.split('\n');
|
|
118
|
+
for (const line of lines) {
|
|
119
|
+
const trimmed = line.trim();
|
|
120
|
+
|
|
121
|
+
// Skip header rows and separators
|
|
122
|
+
if (!trimmed || trimmed.startsWith('|---') || trimmed.startsWith('| Date')) continue;
|
|
123
|
+
|
|
124
|
+
// Table row: extract content cells
|
|
125
|
+
if (trimmed.startsWith('|')) {
|
|
126
|
+
const cells = trimmed.split('|').slice(1, -1).map(c => c.trim());
|
|
127
|
+
// Check cells 1 and 2 (pattern/correction columns)
|
|
128
|
+
for (let i = 1; i < Math.min(cells.length, 3); i++) {
|
|
129
|
+
const sim = bigramSimilarity(cells[i], entryText);
|
|
130
|
+
if (sim >= DEDUP_SIMILARITY_THRESHOLD) {
|
|
131
|
+
return { isDuplicate: true, matchedText: cells[i], similarity: sim };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Heading: ### Pattern Name
|
|
137
|
+
if (trimmed.startsWith('###')) {
|
|
138
|
+
const heading = trimmed.replace(/^###\s*/, '').replace(/\s*\([\d-]+\)$/, '');
|
|
139
|
+
const sim = bigramSimilarity(heading, entryText);
|
|
140
|
+
if (sim >= DEDUP_SIMILARITY_THRESHOLD) {
|
|
141
|
+
return { isDuplicate: true, matchedText: heading, similarity: sim };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return { isDuplicate: false };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ============================================================
|
|
150
|
+
// Centralized Write API
|
|
151
|
+
// ============================================================
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Write to feedback-patterns.md through the orchestrator.
|
|
155
|
+
* Acquires a lock, checks for duplicates, then applies the write.
|
|
156
|
+
*
|
|
157
|
+
* @param {Object} params
|
|
158
|
+
* @param {string} params.content - Full new content to write (replaces file)
|
|
159
|
+
* @param {string} [params.entryText] - Key text of the new entry (for dedup)
|
|
160
|
+
* @param {string} params.caller - Calling module name (for logging)
|
|
161
|
+
* @param {boolean} [params.skipDedup=false] - Skip dedup check (for bulk rewrites)
|
|
162
|
+
* @returns {Promise<{ success: boolean, reason?: string }>}
|
|
163
|
+
*/
|
|
164
|
+
async function writeToFeedbackPatterns({ content, entryText, caller, skipDedup = false }) {
|
|
165
|
+
const filePath = PATHS.feedbackPatterns;
|
|
166
|
+
|
|
167
|
+
const release = await acquireLock(filePath, { retries: 5, retryDelay: 100 });
|
|
168
|
+
try {
|
|
169
|
+
// Dedup check
|
|
170
|
+
if (!skipDedup && entryText) {
|
|
171
|
+
const currentContent = fileExists(filePath) ? readFile(filePath, '') : '';
|
|
172
|
+
const dupCheck = checkDuplicate(currentContent, entryText);
|
|
173
|
+
if (dupCheck.isDuplicate) {
|
|
174
|
+
if (process.env.DEBUG) {
|
|
175
|
+
console.log(`[orchestrator] Dedup rejected write from ${caller}: "${entryText}" matches "${dupCheck.matchedText}" (${(dupCheck.similarity * 100).toFixed(0)}%)`);
|
|
176
|
+
}
|
|
177
|
+
return { success: false, reason: 'duplicate', matchedText: dupCheck.matchedText, similarity: dupCheck.similarity };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
writeFile(filePath, content);
|
|
182
|
+
if (process.env.DEBUG) {
|
|
183
|
+
console.log(`[orchestrator] ${caller} wrote to feedback-patterns.md`);
|
|
184
|
+
}
|
|
185
|
+
return { success: true };
|
|
186
|
+
} catch (err) {
|
|
187
|
+
return { success: false, reason: err.message };
|
|
188
|
+
} finally {
|
|
189
|
+
release();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Write to decisions.md through the orchestrator.
|
|
195
|
+
* Acquires a lock, checks for duplicates, then applies the write.
|
|
196
|
+
*
|
|
197
|
+
* @param {Object} params
|
|
198
|
+
* @param {string} params.content - Full new content to write (replaces file)
|
|
199
|
+
* @param {string} [params.entryText] - Key text of the new entry (for dedup)
|
|
200
|
+
* @param {string} params.caller - Calling module name (for logging)
|
|
201
|
+
* @param {boolean} [params.skipDedup=false] - Skip dedup check (for bulk rewrites)
|
|
202
|
+
* @param {boolean} [params.syncRules=false] - Trigger rules sync after write
|
|
203
|
+
* @returns {Promise<{ success: boolean, reason?: string }>}
|
|
204
|
+
*/
|
|
205
|
+
async function writeToDecisions({ content, entryText, caller, skipDedup = false, syncRules = false }) {
|
|
206
|
+
const filePath = PATHS.decisions;
|
|
207
|
+
|
|
208
|
+
const release = await acquireLock(filePath, { retries: 5, retryDelay: 100 });
|
|
209
|
+
try {
|
|
210
|
+
// Dedup check
|
|
211
|
+
if (!skipDedup && entryText) {
|
|
212
|
+
const currentContent = fileExists(filePath) ? readFile(filePath, '') : '';
|
|
213
|
+
const dupCheck = checkDuplicate(currentContent, entryText);
|
|
214
|
+
if (dupCheck.isDuplicate) {
|
|
215
|
+
if (process.env.DEBUG) {
|
|
216
|
+
console.log(`[orchestrator] Dedup rejected write from ${caller}: "${entryText}" matches "${dupCheck.matchedText}" (${(dupCheck.similarity * 100).toFixed(0)}%)`);
|
|
217
|
+
}
|
|
218
|
+
return { success: false, reason: 'duplicate', matchedText: dupCheck.matchedText, similarity: dupCheck.similarity };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
writeFile(filePath, content);
|
|
223
|
+
if (process.env.DEBUG) {
|
|
224
|
+
console.log(`[orchestrator] ${caller} wrote to decisions.md`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Optionally sync rules after write
|
|
228
|
+
if (syncRules) {
|
|
229
|
+
try {
|
|
230
|
+
require('./flow-rules-sync');
|
|
231
|
+
} catch (_err) {
|
|
232
|
+
// Non-fatal — rules sync is optional
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return { success: true };
|
|
237
|
+
} catch (err) {
|
|
238
|
+
return { success: false, reason: err.message };
|
|
239
|
+
} finally {
|
|
240
|
+
release();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Read-modify-write helper for feedback-patterns.md.
|
|
246
|
+
* Acquires lock, reads current content, calls modifier, writes result.
|
|
247
|
+
*
|
|
248
|
+
* @param {Function} modifier - (currentContent: string) => { content: string, entryText?: string }
|
|
249
|
+
* @param {Object} opts - { caller: string, skipDedup?: boolean }
|
|
250
|
+
* @returns {Promise<{ success: boolean, reason?: string }>}
|
|
251
|
+
*/
|
|
252
|
+
async function modifyFeedbackPatterns(modifier, { caller, skipDedup = false } = {}) {
|
|
253
|
+
const filePath = PATHS.feedbackPatterns;
|
|
254
|
+
|
|
255
|
+
const release = await acquireLock(filePath, { retries: 5, retryDelay: 100 });
|
|
256
|
+
try {
|
|
257
|
+
const currentContent = fileExists(filePath) ? readFile(filePath, '') : '';
|
|
258
|
+
const result = modifier(currentContent);
|
|
259
|
+
|
|
260
|
+
if (!result || !result.content) {
|
|
261
|
+
return { success: false, reason: 'modifier returned no content' };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Dedup check
|
|
265
|
+
if (!skipDedup && result.entryText) {
|
|
266
|
+
const dupCheck = checkDuplicate(currentContent, result.entryText);
|
|
267
|
+
if (dupCheck.isDuplicate) {
|
|
268
|
+
if (process.env.DEBUG) {
|
|
269
|
+
console.log(`[orchestrator] Dedup rejected modify from ${caller}: "${result.entryText}" matches "${dupCheck.matchedText}"`);
|
|
270
|
+
}
|
|
271
|
+
return { success: false, reason: 'duplicate', matchedText: dupCheck.matchedText };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
writeFile(filePath, result.content);
|
|
276
|
+
return { success: true };
|
|
277
|
+
} catch (err) {
|
|
278
|
+
return { success: false, reason: err.message };
|
|
279
|
+
} finally {
|
|
280
|
+
release();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Read-modify-write helper for decisions.md.
|
|
286
|
+
* Acquires lock, reads current content, calls modifier, writes result.
|
|
287
|
+
*
|
|
288
|
+
* @param {Function} modifier - (currentContent: string) => { content: string, entryText?: string }
|
|
289
|
+
* @param {Object} opts - { caller: string, skipDedup?: boolean, syncRules?: boolean }
|
|
290
|
+
* @returns {Promise<{ success: boolean, reason?: string }>}
|
|
291
|
+
*/
|
|
292
|
+
async function modifyDecisions(modifier, { caller, skipDedup = false, syncRules = false } = {}) {
|
|
293
|
+
const filePath = PATHS.decisions;
|
|
294
|
+
|
|
295
|
+
const release = await acquireLock(filePath, { retries: 5, retryDelay: 100 });
|
|
296
|
+
try {
|
|
297
|
+
const currentContent = fileExists(filePath) ? readFile(filePath, '') : '';
|
|
298
|
+
const result = modifier(currentContent);
|
|
299
|
+
|
|
300
|
+
if (!result || !result.content) {
|
|
301
|
+
return { success: false, reason: 'modifier returned no content' };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Dedup check
|
|
305
|
+
if (!skipDedup && result.entryText) {
|
|
306
|
+
const dupCheck = checkDuplicate(currentContent, result.entryText);
|
|
307
|
+
if (dupCheck.isDuplicate) {
|
|
308
|
+
if (process.env.DEBUG) {
|
|
309
|
+
console.log(`[orchestrator] Dedup rejected modify from ${caller}: "${result.entryText}" matches "${dupCheck.matchedText}"`);
|
|
310
|
+
}
|
|
311
|
+
return { success: false, reason: 'duplicate', matchedText: dupCheck.matchedText };
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
writeFile(filePath, result.content);
|
|
316
|
+
|
|
317
|
+
if (syncRules) {
|
|
318
|
+
try {
|
|
319
|
+
require('./flow-rules-sync');
|
|
320
|
+
} catch (_err) {
|
|
321
|
+
// Non-fatal
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return { success: true };
|
|
326
|
+
} catch (err) {
|
|
327
|
+
return { success: false, reason: err.message };
|
|
328
|
+
} finally {
|
|
329
|
+
release();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
39
333
|
// ============================================================
|
|
40
334
|
// Unified API
|
|
41
335
|
// ============================================================
|
|
@@ -114,10 +408,10 @@ async function learn(type, context = {}) {
|
|
|
114
408
|
function getStats() {
|
|
115
409
|
const stats = {};
|
|
116
410
|
|
|
117
|
-
try { stats.tiered = getTieredLearning().getLearningStats(); } catch (
|
|
118
|
-
try { stats.failure = getFailureLearning().getLearningStats(); } catch (
|
|
119
|
-
try { stats.loop = getLoopRetryLearning().getLearningStats(); } catch (
|
|
120
|
-
try { stats.auto = getAutoLearn().showStatus(); } catch (
|
|
411
|
+
try { stats.tiered = getTieredLearning().getLearningStats(); } catch (_err) { stats.tiered = null; }
|
|
412
|
+
try { stats.failure = getFailureLearning().getLearningStats(); } catch (_err) { stats.failure = null; }
|
|
413
|
+
try { stats.loop = getLoopRetryLearning().getLearningStats(); } catch (_err) { stats.loop = null; }
|
|
414
|
+
try { stats.auto = getAutoLearn().showStatus(); } catch (_err) { stats.auto = null; }
|
|
121
415
|
|
|
122
416
|
return stats;
|
|
123
417
|
}
|
|
@@ -134,19 +428,19 @@ function loadRelevantPatterns(context = {}) {
|
|
|
134
428
|
|
|
135
429
|
try {
|
|
136
430
|
patterns.decisions = getPatternEnforcer().loadDecisionPatterns();
|
|
137
|
-
} catch (
|
|
431
|
+
} catch (_err) {
|
|
138
432
|
patterns.decisions = [];
|
|
139
433
|
}
|
|
140
434
|
|
|
141
435
|
try {
|
|
142
436
|
patterns.skills = getPatternEnforcer().loadSkillPatterns(context.skills || []);
|
|
143
|
-
} catch (
|
|
437
|
+
} catch (_err) {
|
|
144
438
|
patterns.skills = [];
|
|
145
439
|
}
|
|
146
440
|
|
|
147
441
|
try {
|
|
148
442
|
patterns.tiered = getTieredLearning().getPatternsByTier(context.tier || 'all');
|
|
149
|
-
} catch (
|
|
443
|
+
} catch (_err) {
|
|
150
444
|
patterns.tiered = [];
|
|
151
445
|
}
|
|
152
446
|
|
|
@@ -158,7 +452,18 @@ function loadRelevantPatterns(context = {}) {
|
|
|
158
452
|
// ============================================================
|
|
159
453
|
|
|
160
454
|
module.exports = {
|
|
161
|
-
//
|
|
455
|
+
// Centralized write API (MUST be used for all learning file writes)
|
|
456
|
+
writeToFeedbackPatterns,
|
|
457
|
+
writeToDecisions,
|
|
458
|
+
modifyFeedbackPatterns,
|
|
459
|
+
modifyDecisions,
|
|
460
|
+
|
|
461
|
+
// Dedup utilities (exposed for testing)
|
|
462
|
+
checkDuplicate,
|
|
463
|
+
bigramSimilarity,
|
|
464
|
+
normalizeForDedup,
|
|
465
|
+
|
|
466
|
+
// Unified learning API
|
|
162
467
|
learn,
|
|
163
468
|
getStats,
|
|
164
469
|
loadRelevantPatterns,
|
package/scripts/flow-links.js
CHANGED
|
@@ -25,14 +25,12 @@ 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 } = require('./flow-utils');
|
|
28
|
+
const { getProjectRoot, colors: c, readJson, PATHS } = require('./flow-utils');
|
|
29
29
|
const { success: printSuccess } = require('./flow-output');
|
|
30
30
|
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const LINKS_JSON_PATH = path.join(WORKFLOW_DIR, 'links.json');
|
|
35
|
-
const CACHE_DIR = path.join(WORKFLOW_DIR, 'cache', 'links');
|
|
31
|
+
const LINKS_PATH = path.join(PATHS.workflow, 'links.yaml');
|
|
32
|
+
const LINKS_JSON_PATH = path.join(PATHS.workflow, 'links.json');
|
|
33
|
+
const CACHE_DIR = path.join(PATHS.workflow, 'cache', 'links');
|
|
36
34
|
|
|
37
35
|
/**
|
|
38
36
|
* Link types
|
|
@@ -357,7 +355,7 @@ async function fetchLink(name, links = null) {
|
|
|
357
355
|
const filePath = url.startsWith('file://') ? url.slice(7) : url;
|
|
358
356
|
const absPath = path.isAbsolute(filePath)
|
|
359
357
|
? filePath
|
|
360
|
-
: path.join(
|
|
358
|
+
: path.join(PATHS.root, filePath);
|
|
361
359
|
|
|
362
360
|
if (fs.existsSync(absPath)) {
|
|
363
361
|
content = fs.readFileSync(absPath, 'utf-8');
|