moflo 4.8.35 → 4.8.38
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/scripts/hooks.mjs +15 -13
- package/.claude/scripts/index-all.mjs +127 -0
- package/README.md +3 -3
- package/bin/hooks.mjs +6 -110
- package/bin/index-all.mjs +168 -0
- package/bin/index-patterns.mjs +376 -0
- package/package.json +1 -1
- package/src/@claude-flow/cli/dist/src/commands/doctor.js +20 -11
- package/src/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +202 -18
- package/src/@claude-flow/cli/dist/src/version.js +1 -1
- package/src/@claude-flow/cli/package.json +1 -1
|
@@ -258,21 +258,23 @@ async function main() {
|
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
case 'session-start': {
|
|
261
|
-
//
|
|
262
|
-
// Start daemon quietly in background
|
|
261
|
+
// Start daemon quietly in background (no DB writes)
|
|
263
262
|
runDaemonStartBackground();
|
|
264
|
-
//
|
|
265
|
-
runIndexGuidanceBackground();
|
|
266
|
-
// Generate structural code map in background
|
|
267
|
-
runCodeMapBackground();
|
|
268
|
-
// Index test files in background
|
|
269
|
-
runTestIndexBackground();
|
|
270
|
-
// Run pretrain in background to extract patterns from repository
|
|
263
|
+
// Run pretrain in background (writes to patterns namespace, fast)
|
|
271
264
|
runBackgroundPretrain();
|
|
272
|
-
//
|
|
273
|
-
// This
|
|
274
|
-
|
|
275
|
-
|
|
265
|
+
// Run all DB-writing indexers SEQUENTIALLY in a single background process.
|
|
266
|
+
// This avoids sql.js last-write-wins concurrency (issue #78).
|
|
267
|
+
// Chain: guidance → code-map → tests → HNSW rebuild (must be last).
|
|
268
|
+
const indexAllScript = resolve(__dirname, 'index-all.mjs');
|
|
269
|
+
if (existsSync(indexAllScript)) {
|
|
270
|
+
spawnWindowless('node', [indexAllScript], 'sequential indexing chain');
|
|
271
|
+
} else {
|
|
272
|
+
// Fallback to parallel if index-all.mjs not available
|
|
273
|
+
runIndexGuidanceBackground();
|
|
274
|
+
runCodeMapBackground();
|
|
275
|
+
runTestIndexBackground();
|
|
276
|
+
runHNSWRebuildBackground();
|
|
277
|
+
}
|
|
276
278
|
break;
|
|
277
279
|
}
|
|
278
280
|
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Sequential indexer chain for session-start.
|
|
4
|
+
*
|
|
5
|
+
* Runs all DB-writing indexers one at a time to avoid sql.js last-write-wins
|
|
6
|
+
* concurrency issues, then triggers HNSW rebuild once everything is committed.
|
|
7
|
+
*
|
|
8
|
+
* Spawned as a single detached background process by hooks.mjs session-start.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, appendFileSync } from 'fs';
|
|
12
|
+
import { resolve, dirname } from 'path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
import { execFileSync } from 'child_process';
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const projectRoot = resolve(__dirname, '../..');
|
|
18
|
+
const LOG_PATH = resolve(projectRoot, '.claude/hooks.log');
|
|
19
|
+
|
|
20
|
+
function log(msg) {
|
|
21
|
+
const ts = new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
22
|
+
const line = `[${ts}] [index-all] ${msg}\n`;
|
|
23
|
+
try { appendFileSync(LOG_PATH, line); } catch { /* ignore */ }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveBin(binName, localScript) {
|
|
27
|
+
const mofloScript = resolve(projectRoot, 'node_modules/moflo/bin', localScript);
|
|
28
|
+
if (existsSync(mofloScript)) return mofloScript;
|
|
29
|
+
const npmBin = resolve(projectRoot, 'node_modules/.bin', binName);
|
|
30
|
+
if (existsSync(npmBin)) return npmBin;
|
|
31
|
+
const localPath = resolve(projectRoot, '.claude/scripts', localScript);
|
|
32
|
+
if (existsSync(localPath)) return localPath;
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getLocalCliPath() {
|
|
37
|
+
const paths = [
|
|
38
|
+
resolve(projectRoot, 'node_modules/moflo/src/@claude-flow/cli/bin/cli.js'),
|
|
39
|
+
resolve(projectRoot, 'node_modules/moflo/bin/cli.js'),
|
|
40
|
+
resolve(projectRoot, 'node_modules/.bin/flo'),
|
|
41
|
+
resolve(projectRoot, 'src/@claude-flow/cli/bin/cli.js'),
|
|
42
|
+
];
|
|
43
|
+
for (const p of paths) {
|
|
44
|
+
if (existsSync(p)) return p;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function runStep(label, cmd, args, timeoutMs = 120_000) {
|
|
50
|
+
const start = Date.now();
|
|
51
|
+
log(`START ${label}`);
|
|
52
|
+
try {
|
|
53
|
+
execFileSync(cmd, args, {
|
|
54
|
+
cwd: projectRoot,
|
|
55
|
+
timeout: timeoutMs,
|
|
56
|
+
stdio: 'ignore',
|
|
57
|
+
windowsHide: true,
|
|
58
|
+
});
|
|
59
|
+
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
|
|
60
|
+
log(`DONE ${label} (${elapsed}s)`);
|
|
61
|
+
return true;
|
|
62
|
+
} catch (err) {
|
|
63
|
+
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
|
|
64
|
+
log(`FAIL ${label} (${elapsed}s): ${err.message?.split('\n')[0] || 'unknown'}`);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function main() {
|
|
70
|
+
const startTime = Date.now();
|
|
71
|
+
log('Sequential indexing chain started');
|
|
72
|
+
|
|
73
|
+
// 1. Guidance indexer
|
|
74
|
+
const guidanceScript = resolveBin('flo-index', 'index-guidance.mjs');
|
|
75
|
+
if (guidanceScript) {
|
|
76
|
+
runStep('guidance-index', 'node', [guidanceScript]);
|
|
77
|
+
} else {
|
|
78
|
+
log('SKIP guidance-index (script not found)');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 2. Code map generator (the big one — ~22s)
|
|
82
|
+
const codeMapScript = resolveBin('flo-codemap', 'generate-code-map.mjs');
|
|
83
|
+
if (codeMapScript) {
|
|
84
|
+
runStep('code-map', 'node', [codeMapScript], 180_000);
|
|
85
|
+
} else {
|
|
86
|
+
log('SKIP code-map (script not found)');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 3. Test indexer
|
|
90
|
+
const testScript = resolveBin('flo-testmap', 'index-tests.mjs');
|
|
91
|
+
if (testScript) {
|
|
92
|
+
runStep('test-index', 'node', [testScript]);
|
|
93
|
+
} else {
|
|
94
|
+
log('SKIP test-index (script not found)');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 4. Patterns indexer
|
|
98
|
+
const patternsScript = resolveBin('flo-patterns', 'index-patterns.mjs');
|
|
99
|
+
if (patternsScript) {
|
|
100
|
+
runStep('patterns-index', 'node', [patternsScript]);
|
|
101
|
+
} else {
|
|
102
|
+
log('SKIP patterns-index (script not found)');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 5. Pretrain (extracts patterns from repository)
|
|
106
|
+
const localCli = getLocalCliPath();
|
|
107
|
+
if (localCli) {
|
|
108
|
+
runStep('pretrain', 'node', [localCli, 'hooks', 'pretrain']);
|
|
109
|
+
} else {
|
|
110
|
+
log('SKIP pretrain (CLI not found)');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 6. HNSW rebuild — MUST run last, after all writes are committed (#81)
|
|
114
|
+
if (localCli) {
|
|
115
|
+
runStep('hnsw-rebuild', 'node', [localCli, 'memory', 'rebuild', '--force']);
|
|
116
|
+
} else {
|
|
117
|
+
log('SKIP hnsw-rebuild (CLI not found)');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const totalElapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
121
|
+
log(`Sequential indexing chain complete (${totalElapsed}s)`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
main().catch(err => {
|
|
125
|
+
log(`FATAL: ${err.message}`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
});
|
package/README.md
CHANGED
|
@@ -163,7 +163,7 @@ npx flo memory code-map # Index your code structure
|
|
|
163
163
|
npx flo doctor # Verify everything works
|
|
164
164
|
```
|
|
165
165
|
|
|
166
|
-
Both indexes run automatically at session start after this, so you only need to run them manually on first setup or after major structural changes. To reindex everything at once:
|
|
166
|
+
Both indexes run automatically at session start after this, so you only need to run them manually on first setup or after major structural changes. The first index may take a minute or two on large codebases (1,000+ files) but runs in the background — you can start working immediately. Subsequent indexes are incremental and typically finish in under a second. To reindex everything at once:
|
|
167
167
|
|
|
168
168
|
```bash
|
|
169
169
|
npx flo memory refresh # Reindex all content, rebuild embeddings, cleanup, vacuum
|
|
@@ -183,8 +183,8 @@ MoFlo automatically indexes three types of content on every session start, so yo
|
|
|
183
183
|
|
|
184
184
|
### How it works
|
|
185
185
|
|
|
186
|
-
1. **Session start hook** — When your AI client starts a new session, MoFlo's `SessionStart` hook launches
|
|
187
|
-
2. **Incremental** — Each indexer tracks file modification times. Only files that changed since the last index run are re-processed. The first run
|
|
186
|
+
1. **Session start hook** — When your AI client starts a new session, MoFlo's `SessionStart` hook launches the indexers sequentially in a single background process. This runs silently — you can start working immediately.
|
|
187
|
+
2. **Incremental** — Each indexer tracks file modification times. Only files that changed since the last index run are re-processed. The first run on a large codebase may take a minute or two; subsequent runs typically finish in under a second.
|
|
188
188
|
3. **Embedding generation** — Guidance chunks are embedded using MiniLM-L6-v2 (384 dimensions, WASM). These vectors are stored in the SQLite memory database and used for semantic search.
|
|
189
189
|
4. **No blocking** — The indexers run in the background and don't block your session from starting. You can begin working immediately.
|
|
190
190
|
|
package/bin/hooks.mjs
CHANGED
|
@@ -258,24 +258,15 @@ async function main() {
|
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
case 'session-start': {
|
|
261
|
-
//
|
|
262
|
-
// Start daemon quietly in background
|
|
261
|
+
// Start daemon quietly in background (no DB writes)
|
|
263
262
|
runDaemonStartBackground();
|
|
264
263
|
// Initialize embeddings engine (must run before indexers that generate embeddings)
|
|
265
264
|
runEmbeddingsInitBackground();
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
-
//
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
runTestIndexBackground();
|
|
272
|
-
// Index code patterns into patterns namespace
|
|
273
|
-
runPatternsIndexBackground();
|
|
274
|
-
// Run pretrain in background to extract patterns from repository
|
|
275
|
-
runBackgroundPretrain();
|
|
276
|
-
// Force HNSW rebuild to ensure all processes use identical fresh index
|
|
277
|
-
// This fixes agent search result mismatches (0.61 vs 0.81 similarity)
|
|
278
|
-
runHNSWRebuildBackground();
|
|
265
|
+
// Run all DB-writing indexers SEQUENTIALLY in a single background process.
|
|
266
|
+
// This avoids sql.js last-write-wins concurrency (#78) and ensures
|
|
267
|
+
// HNSW rebuild runs after all indexers finish (#81).
|
|
268
|
+
// Chain: guidance → code-map → tests → patterns → pretrain → HNSW rebuild.
|
|
269
|
+
spawnWindowless('node', [resolve(__dirname, 'index-all.mjs')], 'sequential indexing chain');
|
|
279
270
|
// Neural patterns now loaded by moflo core routing — no external patching.
|
|
280
271
|
break;
|
|
281
272
|
}
|
|
@@ -471,55 +462,6 @@ function runIndexGuidanceBackground(specificFile = null) {
|
|
|
471
462
|
spawnWindowless('node', indexArgs, desc);
|
|
472
463
|
}
|
|
473
464
|
|
|
474
|
-
// Run structural code map generator in background (non-blocking)
|
|
475
|
-
function runCodeMapBackground() {
|
|
476
|
-
// Check auto_index.code_map flag in moflo.yaml (default: true)
|
|
477
|
-
const yamlPath = resolve(projectRoot, 'moflo.yaml');
|
|
478
|
-
if (existsSync(yamlPath)) {
|
|
479
|
-
try {
|
|
480
|
-
const content = readFileSync(yamlPath, 'utf-8');
|
|
481
|
-
const match = content.match(/auto_index:\s*\n(?:.*\n)*?\s+code_map:\s*(true|false)/);
|
|
482
|
-
if (match && match[1] === 'false') {
|
|
483
|
-
log('info', 'Code map generation disabled (auto_index.code_map: false)');
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
} catch { /* ignore, proceed with indexing */ }
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
const codeMapScript = resolveBinOrLocal('flo-codemap', 'generate-code-map.mjs');
|
|
490
|
-
|
|
491
|
-
if (!codeMapScript) {
|
|
492
|
-
log('warn', 'Code map generator not found (checked npm bin + .claude/scripts/)');
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
spawnWindowless('node', [codeMapScript], 'background code map generation');
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// Run test file indexer in background (non-blocking)
|
|
500
|
-
function runTestIndexBackground() {
|
|
501
|
-
// Check auto_index.tests flag in moflo.yaml (default: true)
|
|
502
|
-
const yamlPath = resolve(projectRoot, 'moflo.yaml');
|
|
503
|
-
if (existsSync(yamlPath)) {
|
|
504
|
-
try {
|
|
505
|
-
const content = readFileSync(yamlPath, 'utf-8');
|
|
506
|
-
const match = content.match(/auto_index:\s*\n(?:.*\n)*?\s+tests:\s*(true|false)/);
|
|
507
|
-
if (match && match[1] === 'false') {
|
|
508
|
-
log('info', 'Test indexing disabled (auto_index.tests: false)');
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
} catch { /* ignore, proceed with indexing */ }
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
const testIndexScript = resolveBinOrLocal('flo-testmap', 'index-tests.mjs');
|
|
515
|
-
|
|
516
|
-
if (!testIndexScript) {
|
|
517
|
-
log('info', 'Test indexer not found (checked npm bin + .claude/scripts/)');
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
spawnWindowless('node', [testIndexScript], 'background test indexing');
|
|
522
|
-
}
|
|
523
465
|
|
|
524
466
|
// Run ReasoningBank + MicroLoRA training + EWC++ consolidation in background (non-blocking)
|
|
525
467
|
function runBackgroundTraining() {
|
|
@@ -627,28 +569,6 @@ function runDaemonStartBackground() {
|
|
|
627
569
|
spawnWindowless('node', [localCli, 'daemon', 'start', '--quiet'], 'daemon');
|
|
628
570
|
}
|
|
629
571
|
|
|
630
|
-
// Run pretrain in background on session start (non-blocking)
|
|
631
|
-
function runBackgroundPretrain() {
|
|
632
|
-
const localCli = getLocalCliPath();
|
|
633
|
-
if (!localCli) {
|
|
634
|
-
log('warn', 'Local CLI not found, skipping background pretrain');
|
|
635
|
-
return;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
spawnWindowless('node', [localCli, 'hooks', 'pretrain'], 'background pretrain');
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// Force HNSW rebuild in background to ensure all processes use identical fresh index
|
|
642
|
-
// This fixes the issue where spawned agents return different search results than CLI/MCP
|
|
643
|
-
function runHNSWRebuildBackground() {
|
|
644
|
-
const localCli = getLocalCliPath();
|
|
645
|
-
if (!localCli) {
|
|
646
|
-
log('warn', 'Local CLI not found, skipping HNSW rebuild');
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
spawnWindowless('node', [localCli, 'memory', 'rebuild', '--force'], 'HNSW rebuild');
|
|
651
|
-
}
|
|
652
572
|
|
|
653
573
|
// Initialize embeddings ONNX engine on session start (non-blocking)
|
|
654
574
|
function runEmbeddingsInitBackground() {
|
|
@@ -661,30 +581,6 @@ function runEmbeddingsInitBackground() {
|
|
|
661
581
|
spawnWindowless('node', [localCli, 'embeddings', 'init'], 'embeddings init');
|
|
662
582
|
}
|
|
663
583
|
|
|
664
|
-
// Index code patterns into the patterns namespace (non-blocking)
|
|
665
|
-
// Extracts architectural patterns, idioms, and recurring structures from source
|
|
666
|
-
function runPatternsIndexBackground() {
|
|
667
|
-
// Check auto_index.patterns flag in moflo.yaml (default: true)
|
|
668
|
-
const yamlPath = resolve(projectRoot, 'moflo.yaml');
|
|
669
|
-
if (existsSync(yamlPath)) {
|
|
670
|
-
try {
|
|
671
|
-
const content = readFileSync(yamlPath, 'utf-8');
|
|
672
|
-
const match = content.match(/auto_index:\s*\n(?:.*\n)*?\s+patterns:\s*(true|false)/);
|
|
673
|
-
if (match && match[1] === 'false') {
|
|
674
|
-
log('info', 'Patterns indexing disabled (auto_index.patterns: false)');
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
} catch { /* ignore, proceed with indexing */ }
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
const patternsScript = resolveBinOrLocal('flo-patterns', 'index-patterns.mjs');
|
|
681
|
-
if (!patternsScript) {
|
|
682
|
-
log('warn', 'Patterns indexer not found (checked npm bin + .claude/scripts/)');
|
|
683
|
-
return;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
spawnWindowless('node', [patternsScript], 'background patterns indexing');
|
|
687
|
-
}
|
|
688
584
|
|
|
689
585
|
// Neural pattern application — now handled by moflo core routing (learned patterns
|
|
690
586
|
// loaded from routing-outcomes.json by hooks-tools.ts getSemanticRouter).
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Sequential indexer chain for session-start.
|
|
4
|
+
*
|
|
5
|
+
* Runs all DB-writing indexers one at a time to avoid sql.js last-write-wins
|
|
6
|
+
* concurrency issues (#78), then triggers HNSW rebuild once everything is
|
|
7
|
+
* committed (#81).
|
|
8
|
+
*
|
|
9
|
+
* Spawned as a single detached background process by hooks.mjs session-start.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, appendFileSync, readFileSync } from 'fs';
|
|
13
|
+
import { resolve, dirname } from 'path';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
import { execFileSync } from 'child_process';
|
|
16
|
+
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const projectRoot = resolve(__dirname, '..');
|
|
19
|
+
const LOG_PATH = resolve(projectRoot, '.swarm/hooks.log');
|
|
20
|
+
|
|
21
|
+
function log(msg) {
|
|
22
|
+
const ts = new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
23
|
+
const line = `[${ts}] [index-all] ${msg}\n`;
|
|
24
|
+
try { appendFileSync(LOG_PATH, line); } catch { /* ignore */ }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function resolveBin(binName, localScript) {
|
|
28
|
+
const mofloScript = resolve(projectRoot, 'node_modules/moflo/bin', localScript);
|
|
29
|
+
if (existsSync(mofloScript)) return mofloScript;
|
|
30
|
+
const npmBin = resolve(projectRoot, 'node_modules/.bin', binName);
|
|
31
|
+
if (existsSync(npmBin)) return npmBin;
|
|
32
|
+
const localPath = resolve(projectRoot, '.claude/scripts', localScript);
|
|
33
|
+
if (existsSync(localPath)) return localPath;
|
|
34
|
+
// Also check bin/ directory (for development use)
|
|
35
|
+
const binPath = resolve(projectRoot, 'bin', localScript);
|
|
36
|
+
if (existsSync(binPath)) return binPath;
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getLocalCliPath() {
|
|
41
|
+
const paths = [
|
|
42
|
+
resolve(projectRoot, 'node_modules/moflo/src/@claude-flow/cli/bin/cli.js'),
|
|
43
|
+
resolve(projectRoot, 'node_modules/moflo/bin/cli.js'),
|
|
44
|
+
resolve(projectRoot, 'node_modules/.bin/flo'),
|
|
45
|
+
// Development: local CLI
|
|
46
|
+
resolve(projectRoot, 'src/@claude-flow/cli/bin/cli.js'),
|
|
47
|
+
];
|
|
48
|
+
for (const p of paths) {
|
|
49
|
+
if (existsSync(p)) return p;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Read moflo.yaml once and cache auto_index flags. */
|
|
55
|
+
let _autoIndexFlags = null;
|
|
56
|
+
function isIndexEnabled(key) {
|
|
57
|
+
if (_autoIndexFlags === null) {
|
|
58
|
+
_autoIndexFlags = {};
|
|
59
|
+
const yamlPath = resolve(projectRoot, 'moflo.yaml');
|
|
60
|
+
if (existsSync(yamlPath)) {
|
|
61
|
+
try {
|
|
62
|
+
const content = readFileSync(yamlPath, 'utf-8');
|
|
63
|
+
for (const k of ['guidance', 'code_map', 'tests', 'patterns']) {
|
|
64
|
+
const re = new RegExp(`auto_index:\\s*\\n(?:.*\\n)*?\\s+${k}:\\s*(true|false)`);
|
|
65
|
+
const match = content.match(re);
|
|
66
|
+
_autoIndexFlags[k] = match ? match[1] !== 'false' : true;
|
|
67
|
+
}
|
|
68
|
+
} catch { /* ignore, all default to true */ }
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return _autoIndexFlags[key] !== false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function runStep(label, cmd, args, timeoutMs = 120_000) {
|
|
75
|
+
const start = Date.now();
|
|
76
|
+
log(`START ${label}`);
|
|
77
|
+
try {
|
|
78
|
+
execFileSync(cmd, args, {
|
|
79
|
+
cwd: projectRoot,
|
|
80
|
+
timeout: timeoutMs,
|
|
81
|
+
stdio: 'ignore',
|
|
82
|
+
windowsHide: true,
|
|
83
|
+
});
|
|
84
|
+
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
|
|
85
|
+
log(`DONE ${label} (${elapsed}s)`);
|
|
86
|
+
return true;
|
|
87
|
+
} catch (err) {
|
|
88
|
+
const elapsed = ((Date.now() - start) / 1000).toFixed(1);
|
|
89
|
+
log(`FAIL ${label} (${elapsed}s): ${err.message?.split('\n')[0] || 'unknown'}`);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function main() {
|
|
95
|
+
const startTime = Date.now();
|
|
96
|
+
log('Sequential indexing chain started');
|
|
97
|
+
|
|
98
|
+
// 1. Guidance indexer
|
|
99
|
+
if (isIndexEnabled('guidance')) {
|
|
100
|
+
const guidanceScript = resolveBin('flo-index', 'index-guidance.mjs');
|
|
101
|
+
if (guidanceScript) {
|
|
102
|
+
runStep('guidance-index', 'node', [guidanceScript]);
|
|
103
|
+
} else {
|
|
104
|
+
log('SKIP guidance-index (script not found)');
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
log('SKIP guidance-index (disabled in moflo.yaml)');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 2. Code map generator (the big one — ~22s)
|
|
111
|
+
if (isIndexEnabled('code_map')) {
|
|
112
|
+
const codeMapScript = resolveBin('flo-codemap', 'generate-code-map.mjs');
|
|
113
|
+
if (codeMapScript) {
|
|
114
|
+
runStep('code-map', 'node', [codeMapScript], 180_000);
|
|
115
|
+
} else {
|
|
116
|
+
log('SKIP code-map (script not found)');
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
log('SKIP code-map (disabled in moflo.yaml)');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 3. Test indexer
|
|
123
|
+
if (isIndexEnabled('tests')) {
|
|
124
|
+
const testScript = resolveBin('flo-testmap', 'index-tests.mjs');
|
|
125
|
+
if (testScript) {
|
|
126
|
+
runStep('test-index', 'node', [testScript]);
|
|
127
|
+
} else {
|
|
128
|
+
log('SKIP test-index (script not found)');
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
log('SKIP test-index (disabled in moflo.yaml)');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 4. Patterns indexer
|
|
135
|
+
if (isIndexEnabled('patterns')) {
|
|
136
|
+
const patternsScript = resolveBin('flo-patterns', 'index-patterns.mjs');
|
|
137
|
+
if (patternsScript) {
|
|
138
|
+
runStep('patterns-index', 'node', [patternsScript]);
|
|
139
|
+
} else {
|
|
140
|
+
log('SKIP patterns-index (script not found)');
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
log('SKIP patterns-index (disabled in moflo.yaml)');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 5. Pretrain (extracts patterns from repository)
|
|
147
|
+
const localCli = getLocalCliPath();
|
|
148
|
+
if (localCli) {
|
|
149
|
+
runStep('pretrain', 'node', [localCli, 'hooks', 'pretrain']);
|
|
150
|
+
} else {
|
|
151
|
+
log('SKIP pretrain (CLI not found)');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 6. HNSW rebuild — MUST run last, after all writes are committed (#81)
|
|
155
|
+
if (localCli) {
|
|
156
|
+
runStep('hnsw-rebuild', 'node', [localCli, 'memory', 'rebuild', '--force']);
|
|
157
|
+
} else {
|
|
158
|
+
log('SKIP hnsw-rebuild (CLI not found)');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const totalElapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
162
|
+
log(`Sequential indexing chain complete (${totalElapsed}s)`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
main().catch(err => {
|
|
166
|
+
log(`FATAL: ${err.message}`);
|
|
167
|
+
process.exit(1);
|
|
168
|
+
});
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Index code patterns into claude-flow memory under the `patterns` namespace
|
|
4
|
+
*
|
|
5
|
+
* Extracts per-file patterns (not just aggregate summaries like pretrain):
|
|
6
|
+
* - Service/controller/repository class patterns
|
|
7
|
+
* - API route definitions and middleware usage
|
|
8
|
+
* - Error handling strategies per file
|
|
9
|
+
* - Export conventions per module
|
|
10
|
+
* - Test patterns (describe/it structure)
|
|
11
|
+
* - Configuration patterns
|
|
12
|
+
*
|
|
13
|
+
* Chunk types:
|
|
14
|
+
* pattern:file:{path} — Per-file pattern summary
|
|
15
|
+
* pattern:service:{name} — Service class patterns
|
|
16
|
+
* pattern:route:{path} — API route patterns
|
|
17
|
+
* pattern:error:{path} — Error handling patterns per file
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* node node_modules/moflo/bin/index-patterns.mjs # Incremental
|
|
21
|
+
* node node_modules/moflo/bin/index-patterns.mjs --force # Full reindex
|
|
22
|
+
* node node_modules/moflo/bin/index-patterns.mjs --verbose # Detailed logging
|
|
23
|
+
* node node_modules/moflo/bin/index-patterns.mjs --stats # Print stats and exit
|
|
24
|
+
* npx flo-patterns # Via npx
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
28
|
+
import { resolve, dirname, relative, basename, extname } from 'path';
|
|
29
|
+
import { fileURLToPath } from 'url';
|
|
30
|
+
import { createHash } from 'crypto';
|
|
31
|
+
import { spawn } from 'child_process';
|
|
32
|
+
import { mofloResolveURL } from './lib/moflo-resolve.mjs';
|
|
33
|
+
const initSqlJs = (await import(mofloResolveURL('sql.js'))).default;
|
|
34
|
+
|
|
35
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
|
|
37
|
+
function findProjectRoot() {
|
|
38
|
+
let dir = process.cwd();
|
|
39
|
+
const root = resolve(dir, '/');
|
|
40
|
+
while (dir !== root) {
|
|
41
|
+
if (existsSync(resolve(dir, 'package.json'))) return dir;
|
|
42
|
+
dir = dirname(dir);
|
|
43
|
+
}
|
|
44
|
+
return process.cwd();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const projectRoot = findProjectRoot();
|
|
48
|
+
const NAMESPACE = 'patterns';
|
|
49
|
+
const DB_PATH = resolve(projectRoot, '.swarm/memory.db');
|
|
50
|
+
const HASH_CACHE_PATH = resolve(projectRoot, '.swarm/patterns-hash.txt');
|
|
51
|
+
|
|
52
|
+
const args = process.argv.slice(2);
|
|
53
|
+
const force = args.includes('--force');
|
|
54
|
+
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
55
|
+
const statsOnly = args.includes('--stats');
|
|
56
|
+
|
|
57
|
+
function log(msg) { console.log(`[index-patterns] ${msg}`); }
|
|
58
|
+
function debug(msg) { if (verbose) console.log(`[index-patterns] ${msg}`); }
|
|
59
|
+
|
|
60
|
+
const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.py', '.go', '.rs']);
|
|
61
|
+
const EXCLUDE_DIRS = new Set([
|
|
62
|
+
'node_modules', 'dist', 'build', '.next', 'coverage',
|
|
63
|
+
'.claude', '.swarm', '.claude-flow', '.git', 'template',
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Database helpers
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
function ensureDbDir() {
|
|
71
|
+
const dir = dirname(DB_PATH);
|
|
72
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function getDb() {
|
|
76
|
+
ensureDbDir();
|
|
77
|
+
const SQL = await initSqlJs();
|
|
78
|
+
let db;
|
|
79
|
+
if (existsSync(DB_PATH)) {
|
|
80
|
+
const buffer = readFileSync(DB_PATH);
|
|
81
|
+
db = new SQL.Database(buffer);
|
|
82
|
+
} else {
|
|
83
|
+
db = new SQL.Database();
|
|
84
|
+
}
|
|
85
|
+
db.run(`
|
|
86
|
+
CREATE TABLE IF NOT EXISTS memory_entries (
|
|
87
|
+
id TEXT PRIMARY KEY,
|
|
88
|
+
key TEXT NOT NULL,
|
|
89
|
+
namespace TEXT DEFAULT 'default',
|
|
90
|
+
content TEXT NOT NULL,
|
|
91
|
+
type TEXT DEFAULT 'semantic',
|
|
92
|
+
embedding TEXT,
|
|
93
|
+
embedding_model TEXT DEFAULT 'local',
|
|
94
|
+
embedding_dimensions INTEGER,
|
|
95
|
+
tags TEXT,
|
|
96
|
+
metadata TEXT,
|
|
97
|
+
owner_id TEXT,
|
|
98
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
|
99
|
+
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
|
100
|
+
expires_at INTEGER,
|
|
101
|
+
last_accessed_at INTEGER,
|
|
102
|
+
access_count INTEGER DEFAULT 0,
|
|
103
|
+
status TEXT DEFAULT 'active',
|
|
104
|
+
UNIQUE(namespace, key)
|
|
105
|
+
)
|
|
106
|
+
`);
|
|
107
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_memory_key_ns ON memory_entries(key, namespace)`);
|
|
108
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_memory_namespace ON memory_entries(namespace)`);
|
|
109
|
+
return db;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function saveDb(db) {
|
|
113
|
+
const data = db.export();
|
|
114
|
+
writeFileSync(DB_PATH, Buffer.from(data));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function generateId() {
|
|
118
|
+
return `mem_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function storeEntry(db, key, content, tags = []) {
|
|
122
|
+
const now = Date.now();
|
|
123
|
+
const id = generateId();
|
|
124
|
+
db.run(`
|
|
125
|
+
INSERT OR REPLACE INTO memory_entries
|
|
126
|
+
(id, key, namespace, content, metadata, tags, created_at, updated_at, status)
|
|
127
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'active')
|
|
128
|
+
`, [id, key, NAMESPACE, content, '{}', JSON.stringify(tags), now, now]);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function deleteNamespace(db) {
|
|
132
|
+
db.run(`DELETE FROM memory_entries WHERE namespace = ?`, [NAMESPACE]);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function countNamespace(db) {
|
|
136
|
+
const stmt = db.prepare(`SELECT COUNT(*) as cnt FROM memory_entries WHERE namespace = ?`);
|
|
137
|
+
stmt.bind([NAMESPACE]);
|
|
138
|
+
let count = 0;
|
|
139
|
+
if (stmt.step()) count = stmt.getAsObject().cnt;
|
|
140
|
+
stmt.free();
|
|
141
|
+
return count;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// File collection
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
function collectSourceFiles(dir, maxDepth = 8, depth = 0) {
|
|
149
|
+
if (depth > maxDepth) return [];
|
|
150
|
+
const files = [];
|
|
151
|
+
let entries;
|
|
152
|
+
try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return []; }
|
|
153
|
+
for (const entry of entries) {
|
|
154
|
+
if (EXCLUDE_DIRS.has(entry.name)) continue;
|
|
155
|
+
const fullPath = resolve(dir, entry.name);
|
|
156
|
+
if (entry.isDirectory()) {
|
|
157
|
+
files.push(...collectSourceFiles(fullPath, maxDepth, depth + 1));
|
|
158
|
+
} else if (entry.isFile() && SOURCE_EXTENSIONS.has(extname(entry.name))) {
|
|
159
|
+
files.push(fullPath);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return files;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Pattern extraction — per-file
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
function extractFilePatterns(filePath, content) {
|
|
170
|
+
const lines = content.split('\n');
|
|
171
|
+
const relPath = relative(projectRoot, filePath);
|
|
172
|
+
const patterns = [];
|
|
173
|
+
const tags = [];
|
|
174
|
+
|
|
175
|
+
// Collect metrics
|
|
176
|
+
const imports = [];
|
|
177
|
+
const exports = [];
|
|
178
|
+
const classes = [];
|
|
179
|
+
const functions = [];
|
|
180
|
+
const routes = [];
|
|
181
|
+
const errorHandling = [];
|
|
182
|
+
const interfaces = [];
|
|
183
|
+
|
|
184
|
+
for (const line of lines) {
|
|
185
|
+
const t = line.trim();
|
|
186
|
+
|
|
187
|
+
// Imports
|
|
188
|
+
const impMatch = t.match(/^import\s+.*?from\s+['"]([^'"]+)['"]/);
|
|
189
|
+
if (impMatch) imports.push(impMatch[1]);
|
|
190
|
+
|
|
191
|
+
// Exports
|
|
192
|
+
if (/^export\s+default\b/.test(t)) exports.push('default');
|
|
193
|
+
const namedExp = t.match(/^export\s+(?:const|function|class|interface|type|enum)\s+(\w+)/);
|
|
194
|
+
if (namedExp) exports.push(namedExp[1]);
|
|
195
|
+
|
|
196
|
+
// Classes
|
|
197
|
+
const classMatch = t.match(/^(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/);
|
|
198
|
+
if (classMatch) {
|
|
199
|
+
classes.push(classMatch[1]);
|
|
200
|
+
if (/Service\b/.test(classMatch[1])) tags.push('service');
|
|
201
|
+
if (/Controller\b/.test(classMatch[1])) tags.push('controller');
|
|
202
|
+
if (/Repository\b/.test(classMatch[1])) tags.push('repository');
|
|
203
|
+
if (/Provider\b/.test(classMatch[1])) tags.push('provider');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Functions
|
|
207
|
+
const fnMatch = t.match(/^(?:export\s+)?(?:async\s+)?function\s+(\w+)/);
|
|
208
|
+
if (fnMatch) functions.push(fnMatch[1]);
|
|
209
|
+
const arrowMatch = t.match(/^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\(/);
|
|
210
|
+
if (arrowMatch) functions.push(arrowMatch[1]);
|
|
211
|
+
|
|
212
|
+
// Interfaces/types
|
|
213
|
+
const ifaceMatch = t.match(/^(?:export\s+)?(?:interface|type)\s+(\w+)/);
|
|
214
|
+
if (ifaceMatch) interfaces.push(ifaceMatch[1]);
|
|
215
|
+
|
|
216
|
+
// Routes
|
|
217
|
+
const routeMatch = t.match(/\.(get|post|put|patch|delete)\s*\(\s*['"]([^'"]*)['"]/i);
|
|
218
|
+
if (routeMatch) routes.push(`${routeMatch[1].toUpperCase()} ${routeMatch[2]}`);
|
|
219
|
+
if (/@(Get|Post|Put|Delete|Patch)\s*\(/.test(t)) {
|
|
220
|
+
const dec = t.match(/@(\w+)\s*\(\s*['"]([^'"]*)['"]/);
|
|
221
|
+
if (dec) routes.push(`${dec[1].toUpperCase()} ${dec[2]}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Error handling
|
|
225
|
+
if (/\bcatch\s*\(/.test(t)) errorHandling.push('try-catch');
|
|
226
|
+
if (/\.catch\(/.test(t)) errorHandling.push('promise-catch');
|
|
227
|
+
if (/throw\s+new\s+(\w+)/.test(t)) {
|
|
228
|
+
const err = t.match(/throw\s+new\s+(\w+)/);
|
|
229
|
+
if (err) errorHandling.push(`throws:${err[1]}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Only create entries for files with meaningful patterns
|
|
234
|
+
if (classes.length === 0 && functions.length < 2 && routes.length === 0 && interfaces.length < 2) {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Build human-readable pattern summary for this file
|
|
239
|
+
const parts = [];
|
|
240
|
+
if (classes.length > 0) parts.push(`Classes: ${classes.join(', ')}`);
|
|
241
|
+
if (functions.length > 0) parts.push(`Functions: ${functions.slice(0, 10).join(', ')}${functions.length > 10 ? ` (+${functions.length - 10} more)` : ''}`);
|
|
242
|
+
if (interfaces.length > 0) parts.push(`Types: ${interfaces.slice(0, 8).join(', ')}${interfaces.length > 8 ? ` (+${interfaces.length - 8} more)` : ''}`);
|
|
243
|
+
if (routes.length > 0) parts.push(`Routes: ${routes.join(', ')}`);
|
|
244
|
+
if (exports.length > 0) parts.push(`Exports: ${exports.slice(0, 8).join(', ')}${exports.length > 8 ? ` (+${exports.length - 8} more)` : ''}`);
|
|
245
|
+
if (errorHandling.length > 0) {
|
|
246
|
+
const unique = [...new Set(errorHandling)];
|
|
247
|
+
parts.push(`Error handling: ${unique.join(', ')}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const summary = `# ${relPath}\n${parts.join('\n')}`;
|
|
251
|
+
|
|
252
|
+
// File-level pattern entry
|
|
253
|
+
patterns.push({
|
|
254
|
+
key: `pattern:file:${relPath}`,
|
|
255
|
+
content: summary,
|
|
256
|
+
tags: ['file-pattern', ...tags],
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Service/controller entries get their own chunk for better search
|
|
260
|
+
for (const cls of classes) {
|
|
261
|
+
if (/Service|Controller|Repository|Provider|Handler|Manager/.test(cls)) {
|
|
262
|
+
const clsMethods = functions.filter(f => f !== cls); // rough heuristic
|
|
263
|
+
patterns.push({
|
|
264
|
+
key: `pattern:class:${cls}`,
|
|
265
|
+
content: `# ${cls} (${relPath})\nType: ${tags.filter(t => ['service', 'controller', 'repository', 'provider'].includes(t)).join(', ') || 'class'}\nMethods: ${clsMethods.slice(0, 15).join(', ') || 'none detected'}`,
|
|
266
|
+
tags: ['class-pattern', ...tags],
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Route entries
|
|
272
|
+
if (routes.length > 0) {
|
|
273
|
+
patterns.push({
|
|
274
|
+
key: `pattern:routes:${relPath}`,
|
|
275
|
+
content: `# Routes in ${relPath}\n${routes.join('\n')}`,
|
|
276
|
+
tags: ['route-pattern', 'api'],
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return patterns;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
// Main
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
async function main() {
|
|
288
|
+
const startTime = Date.now();
|
|
289
|
+
|
|
290
|
+
// Stats mode
|
|
291
|
+
if (statsOnly) {
|
|
292
|
+
const db = await getDb();
|
|
293
|
+
const count = countNamespace(db);
|
|
294
|
+
const files = collectSourceFiles(projectRoot);
|
|
295
|
+
log(`${files.length} source files, ${count} chunks in patterns namespace`);
|
|
296
|
+
db.close();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Collect files
|
|
301
|
+
const files = collectSourceFiles(projectRoot);
|
|
302
|
+
log(`Found ${files.length} source files`);
|
|
303
|
+
|
|
304
|
+
if (files.length === 0) {
|
|
305
|
+
log('No source files found');
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Hash check for incremental
|
|
310
|
+
const hashInput = files.map(f => {
|
|
311
|
+
try { return `${f}:${statSync(f).mtimeMs}`; } catch { return f; }
|
|
312
|
+
}).join('\n');
|
|
313
|
+
const currentHash = createHash('sha256').update(hashInput).digest('hex');
|
|
314
|
+
|
|
315
|
+
if (!force && existsSync(HASH_CACHE_PATH)) {
|
|
316
|
+
const cached = readFileSync(HASH_CACHE_PATH, 'utf-8').trim();
|
|
317
|
+
if (cached === currentHash) {
|
|
318
|
+
log('No changes detected (use --force to reindex)');
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Extract patterns from all files
|
|
324
|
+
const allPatterns = [];
|
|
325
|
+
let filesWithPatterns = 0;
|
|
326
|
+
|
|
327
|
+
for (const filePath of files) {
|
|
328
|
+
let content;
|
|
329
|
+
try { content = readFileSync(filePath, 'utf-8'); } catch { continue; }
|
|
330
|
+
const patterns = extractFilePatterns(filePath, content);
|
|
331
|
+
if (patterns.length > 0) {
|
|
332
|
+
filesWithPatterns++;
|
|
333
|
+
allPatterns.push(...patterns);
|
|
334
|
+
}
|
|
335
|
+
debug(`${relative(projectRoot, filePath)}: ${patterns.length} patterns`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
log(`Extracted ${allPatterns.length} pattern chunks from ${filesWithPatterns} files`);
|
|
339
|
+
|
|
340
|
+
// Write to database
|
|
341
|
+
const db = await getDb();
|
|
342
|
+
deleteNamespace(db);
|
|
343
|
+
|
|
344
|
+
for (const p of allPatterns) {
|
|
345
|
+
storeEntry(db, p.key, p.content, p.tags);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
saveDb(db);
|
|
349
|
+
db.close();
|
|
350
|
+
|
|
351
|
+
// Save hash
|
|
352
|
+
writeFileSync(HASH_CACHE_PATH, currentHash, 'utf-8');
|
|
353
|
+
|
|
354
|
+
// Trigger embedding generation in background
|
|
355
|
+
try {
|
|
356
|
+
const embeddingScript = resolve(projectRoot, 'node_modules/moflo/bin/build-embeddings.mjs');
|
|
357
|
+
if (existsSync(embeddingScript)) {
|
|
358
|
+
const child = spawn('node', [embeddingScript, '--namespace', NAMESPACE], {
|
|
359
|
+
cwd: projectRoot,
|
|
360
|
+
detached: true,
|
|
361
|
+
stdio: 'ignore',
|
|
362
|
+
windowsHide: true,
|
|
363
|
+
});
|
|
364
|
+
child.unref();
|
|
365
|
+
debug('Embedding generation started in background');
|
|
366
|
+
}
|
|
367
|
+
} catch { /* ignore */ }
|
|
368
|
+
|
|
369
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
370
|
+
log(`Done in ${elapsed}s — ${allPatterns.length} pattern chunks written`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
main().catch(err => {
|
|
374
|
+
log(`Error: ${err.message}`);
|
|
375
|
+
process.exit(1);
|
|
376
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "moflo",
|
|
3
|
-
"version": "4.8.
|
|
3
|
+
"version": "4.8.38",
|
|
4
4
|
"description": "MoFlo — AI agent orchestration for Claude Code. Forked from ruflo/claude-flow with patches applied to source, plus feature-level orchestration.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -791,22 +791,31 @@ async function checkIntelligence() {
|
|
|
791
791
|
catch (e) {
|
|
792
792
|
failures.push(`SONA (${e instanceof Error ? e.message : 'error'})`);
|
|
793
793
|
}
|
|
794
|
-
// 2. ReasoningBank — store
|
|
794
|
+
// 2. ReasoningBank — verify instantiation and trajectory store/distill lifecycle
|
|
795
795
|
try {
|
|
796
796
|
const rb = neural.createReasoningBank();
|
|
797
|
-
const
|
|
798
|
-
|
|
799
|
-
id: 'doctor-test',
|
|
797
|
+
const trajectory = {
|
|
798
|
+
trajectoryId: 'doctor-test',
|
|
800
799
|
context: 'health check',
|
|
801
800
|
domain: 'general',
|
|
802
|
-
|
|
803
|
-
steps: [{ action: 'test', reward: 1, embedding, timestamp: Date.now() }],
|
|
801
|
+
steps: [{ action: 'test', reward: 1, stateAfter: new Float32Array(64).fill(0.2), timestamp: Date.now() }],
|
|
804
802
|
startTime: Date.now(),
|
|
805
803
|
endTime: Date.now(),
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
804
|
+
qualityScore: 0.9,
|
|
805
|
+
isComplete: true,
|
|
806
|
+
verdict: {
|
|
807
|
+
success: true,
|
|
808
|
+
confidence: 0.9,
|
|
809
|
+
strengths: ['health check passed'],
|
|
810
|
+
weaknesses: [],
|
|
811
|
+
improvements: [],
|
|
812
|
+
relevanceScore: 0.9,
|
|
813
|
+
},
|
|
814
|
+
};
|
|
815
|
+
rb.storeTrajectory(trajectory);
|
|
816
|
+
// distill() populates memories (storeTrajectory alone does not)
|
|
817
|
+
const distilled = await rb.distill(trajectory);
|
|
818
|
+
if (distilled || rb.getTrajectories().length > 0) {
|
|
810
819
|
results.push('ReasoningBank');
|
|
811
820
|
}
|
|
812
821
|
else {
|
|
@@ -816,7 +825,7 @@ async function checkIntelligence() {
|
|
|
816
825
|
results.push('ReasoningBank(memory)');
|
|
817
826
|
}
|
|
818
827
|
else {
|
|
819
|
-
failures.push('ReasoningBank (
|
|
828
|
+
failures.push('ReasoningBank (distill returned no data)');
|
|
820
829
|
}
|
|
821
830
|
}
|
|
822
831
|
}
|
|
@@ -1244,6 +1244,13 @@ function extractPatterns(files) {
|
|
|
1244
1244
|
const structurePatterns = new Map();
|
|
1245
1245
|
const apiPatterns = new Map();
|
|
1246
1246
|
const functionSigs = [];
|
|
1247
|
+
// Extended counters (collected in single pass)
|
|
1248
|
+
let asyncCount = 0, syncCount = 0, promiseCatchCount = 0, awaitCount = 0;
|
|
1249
|
+
const decoratorCounts = new Map();
|
|
1250
|
+
let interfaceCount = 0, typeAliasCount = 0, enumCount = 0, genericCount = 0;
|
|
1251
|
+
let describeCount = 0, itCount = 0, testCount = 0, expectCount = 0, mockCount = 0;
|
|
1252
|
+
let envAccessCount = 0, configImportCount = 0;
|
|
1253
|
+
const logPatterns = new Map();
|
|
1247
1254
|
for (const file of files) {
|
|
1248
1255
|
let content;
|
|
1249
1256
|
try {
|
|
@@ -1286,8 +1293,8 @@ function extractPatterns(files) {
|
|
|
1286
1293
|
const errClass = trimmed.match(/throw\s+new\s+(\w+)/)?.[1] || 'Error';
|
|
1287
1294
|
errorPatterns.set(`throw:${errClass}`, (errorPatterns.get(`throw:${errClass}`) || 0) + 1);
|
|
1288
1295
|
}
|
|
1289
|
-
// Function signatures (collect first
|
|
1290
|
-
if (functionSigs.length <
|
|
1296
|
+
// Function signatures (collect first 200)
|
|
1297
|
+
if (functionSigs.length < 200) {
|
|
1291
1298
|
const fnMatch = trimmed.match(/^(?:export\s+)?(?:async\s+)?function\s+(\w+)/);
|
|
1292
1299
|
if (fnMatch)
|
|
1293
1300
|
functionSigs.push(fnMatch[1]);
|
|
@@ -1333,23 +1340,91 @@ function extractPatterns(files) {
|
|
|
1333
1340
|
if (/interface\s+\w+/.test(trimmed)) {
|
|
1334
1341
|
structurePatterns.set('typed-interface', (structurePatterns.get('typed-interface') || 0) + 1);
|
|
1335
1342
|
}
|
|
1343
|
+
// Extended: async patterns
|
|
1344
|
+
if (/^(?:export\s+)?async\s+function\b/.test(trimmed) || /=\s*async\s*\(/.test(trimmed))
|
|
1345
|
+
asyncCount++;
|
|
1346
|
+
else if (/^(?:export\s+)?function\b/.test(trimmed) || /=\s*\([^)]*\)\s*=>/.test(trimmed))
|
|
1347
|
+
syncCount++;
|
|
1348
|
+
if (/\.catch\(/.test(trimmed))
|
|
1349
|
+
promiseCatchCount++;
|
|
1350
|
+
if (/\bawait\b/.test(trimmed))
|
|
1351
|
+
awaitCount++;
|
|
1352
|
+
// Extended: decorators
|
|
1353
|
+
const dec = trimmed.match(/^@(\w+)/);
|
|
1354
|
+
if (dec)
|
|
1355
|
+
decoratorCounts.set(dec[1], (decoratorCounts.get(dec[1]) || 0) + 1);
|
|
1356
|
+
// Extended: type system
|
|
1357
|
+
if (/^(?:export\s+)?interface\s+\w+/.test(trimmed))
|
|
1358
|
+
interfaceCount++;
|
|
1359
|
+
if (/^(?:export\s+)?type\s+\w+\s*=/.test(trimmed))
|
|
1360
|
+
typeAliasCount++;
|
|
1361
|
+
if (/^(?:export\s+)?enum\s+\w+/.test(trimmed))
|
|
1362
|
+
enumCount++;
|
|
1363
|
+
if (/<\w+(?:\s+extends\s+\w+)?>/.test(trimmed) && /(?:function|class|interface|type)\s/.test(trimmed))
|
|
1364
|
+
genericCount++;
|
|
1365
|
+
// Extended: testing
|
|
1366
|
+
if (/^describe\s*\(/.test(trimmed))
|
|
1367
|
+
describeCount++;
|
|
1368
|
+
if (/^\s*it\s*\(/.test(trimmed))
|
|
1369
|
+
itCount++;
|
|
1370
|
+
if (/^\s*test\s*\(/.test(trimmed))
|
|
1371
|
+
testCount++;
|
|
1372
|
+
if (/expect\s*\(/.test(trimmed))
|
|
1373
|
+
expectCount++;
|
|
1374
|
+
if (/\b(?:vi\.mock|jest\.mock|sinon\.stub|\.mockImplementation|\.mockReturnValue)\b/.test(trimmed))
|
|
1375
|
+
mockCount++;
|
|
1376
|
+
// Extended: config/env
|
|
1377
|
+
if (/process\.env\b/.test(trimmed))
|
|
1378
|
+
envAccessCount++;
|
|
1379
|
+
if (/config|\.env|dotenv|convict/i.test(trimmed) && /import|require/.test(trimmed))
|
|
1380
|
+
configImportCount++;
|
|
1381
|
+
// Extended: logging
|
|
1382
|
+
if (/console\.log\b/.test(trimmed))
|
|
1383
|
+
logPatterns.set('console.log', (logPatterns.get('console.log') || 0) + 1);
|
|
1384
|
+
if (/console\.error\b/.test(trimmed))
|
|
1385
|
+
logPatterns.set('console.error', (logPatterns.get('console.error') || 0) + 1);
|
|
1386
|
+
if (/console\.warn\b/.test(trimmed))
|
|
1387
|
+
logPatterns.set('console.warn', (logPatterns.get('console.warn') || 0) + 1);
|
|
1388
|
+
if (/\blogger\.\w+/.test(trimmed))
|
|
1389
|
+
logPatterns.set('logger.*', (logPatterns.get('logger.*') || 0) + 1);
|
|
1336
1390
|
}
|
|
1337
1391
|
}
|
|
1338
1392
|
const patterns = [];
|
|
1339
|
-
//
|
|
1340
|
-
const sortedImports = [...importCounts.entries()].sort((a, b) => b[1] - a[1])
|
|
1393
|
+
// --- Import patterns: one entry per top module (not just a single aggregate) ---
|
|
1394
|
+
const sortedImports = [...importCounts.entries()].sort((a, b) => b[1] - a[1]);
|
|
1395
|
+
// Summary entry
|
|
1341
1396
|
if (sortedImports.length > 0) {
|
|
1342
1397
|
patterns.push({
|
|
1343
|
-
type: 'import',
|
|
1344
|
-
value: `Top modules: ${sortedImports.map(([m, c]) => `${m}(${c})`).join(', ')}`,
|
|
1398
|
+
type: 'import-summary',
|
|
1399
|
+
value: `Top modules: ${sortedImports.slice(0, 15).map(([m, c]) => `${m}(${c})`).join(', ')}`,
|
|
1345
1400
|
count: sortedImports.reduce((s, [, c]) => s + c, 0),
|
|
1346
1401
|
examples: sortedImports.slice(0, 5).map(([m]) => m),
|
|
1347
1402
|
});
|
|
1348
1403
|
}
|
|
1349
|
-
//
|
|
1404
|
+
// Individual module entries (top 20 non-relative)
|
|
1405
|
+
for (const [mod, count] of sortedImports.filter(([m]) => m !== '<relative>').slice(0, 20)) {
|
|
1406
|
+
patterns.push({
|
|
1407
|
+
type: 'import-module',
|
|
1408
|
+
value: `Module "${mod}" imported ${count} times across codebase`,
|
|
1409
|
+
count,
|
|
1410
|
+
examples: [mod],
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
// Relative vs external ratio
|
|
1414
|
+
const relativeCount = importCounts.get('<relative>') || 0;
|
|
1415
|
+
const externalCount = sortedImports.filter(([m]) => m !== '<relative>').reduce((s, [, c]) => s + c, 0);
|
|
1416
|
+
if (relativeCount + externalCount > 0) {
|
|
1417
|
+
patterns.push({
|
|
1418
|
+
type: 'import-ratio',
|
|
1419
|
+
value: `relative:${relativeCount} external:${externalCount} ratio:${(relativeCount / Math.max(1, relativeCount + externalCount) * 100).toFixed(0)}%`,
|
|
1420
|
+
count: relativeCount + externalCount,
|
|
1421
|
+
examples: relativeCount > externalCount ? ['Mostly internal imports'] : ['Mostly external deps'],
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
// --- Export patterns ---
|
|
1350
1425
|
if (exportPatterns.default + exportPatterns.named > 0) {
|
|
1351
1426
|
patterns.push({
|
|
1352
|
-
type: 'export',
|
|
1427
|
+
type: 'export-style',
|
|
1353
1428
|
value: `default:${exportPatterns.default} named:${exportPatterns.named}`,
|
|
1354
1429
|
count: exportPatterns.default + exportPatterns.named,
|
|
1355
1430
|
examples: exportPatterns.named > exportPatterns.default
|
|
@@ -1357,44 +1432,153 @@ function extractPatterns(files) {
|
|
|
1357
1432
|
: ['Default exports preferred'],
|
|
1358
1433
|
});
|
|
1359
1434
|
}
|
|
1360
|
-
// Error handling
|
|
1361
|
-
const sortedErrors = [...errorPatterns.entries()].sort((a, b) => b[1] - a[1])
|
|
1435
|
+
// --- Error handling: one entry per strategy ---
|
|
1436
|
+
const sortedErrors = [...errorPatterns.entries()].sort((a, b) => b[1] - a[1]);
|
|
1437
|
+
// Summary
|
|
1362
1438
|
if (sortedErrors.length > 0) {
|
|
1363
1439
|
patterns.push({
|
|
1364
|
-
type: 'error-
|
|
1440
|
+
type: 'error-summary',
|
|
1365
1441
|
value: sortedErrors.map(([t, c]) => `${t}(${c})`).join(', '),
|
|
1366
1442
|
count: sortedErrors.reduce((s, [, c]) => s + c, 0),
|
|
1367
1443
|
examples: sortedErrors.slice(0, 3).map(([t]) => t),
|
|
1368
1444
|
});
|
|
1369
1445
|
}
|
|
1370
|
-
//
|
|
1446
|
+
// Individual error strategies
|
|
1447
|
+
for (const [errType, count] of sortedErrors.slice(0, 15)) {
|
|
1448
|
+
patterns.push({
|
|
1449
|
+
type: 'error-strategy',
|
|
1450
|
+
value: `${errType}: ${count} occurrences`,
|
|
1451
|
+
count,
|
|
1452
|
+
examples: [errType],
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
// --- Naming conventions ---
|
|
1371
1456
|
const dominant = namingStyles.camelCase >= namingStyles.snake_case ? 'camelCase' : 'snake_case';
|
|
1372
1457
|
patterns.push({
|
|
1373
|
-
type: 'naming',
|
|
1458
|
+
type: 'naming-convention',
|
|
1374
1459
|
value: `camelCase:${namingStyles.camelCase} snake_case:${namingStyles.snake_case} PascalCase:${namingStyles.PascalCase} dominant:${dominant}`,
|
|
1375
1460
|
count: namingStyles.camelCase + namingStyles.snake_case + namingStyles.PascalCase,
|
|
1376
1461
|
examples: functionSigs.slice(0, 5),
|
|
1377
1462
|
});
|
|
1378
|
-
//
|
|
1463
|
+
// Individual naming entries
|
|
1464
|
+
if (namingStyles.camelCase > 0) {
|
|
1465
|
+
patterns.push({ type: 'naming-camelCase', value: `camelCase identifiers: ${namingStyles.camelCase}`, count: namingStyles.camelCase, examples: functionSigs.filter(f => /^[a-z]/.test(f)).slice(0, 5) });
|
|
1466
|
+
}
|
|
1467
|
+
if (namingStyles.PascalCase > 0) {
|
|
1468
|
+
patterns.push({ type: 'naming-PascalCase', value: `PascalCase identifiers: ${namingStyles.PascalCase}`, count: namingStyles.PascalCase, examples: functionSigs.filter(f => /^[A-Z]/.test(f)).slice(0, 5) });
|
|
1469
|
+
}
|
|
1470
|
+
if (namingStyles.snake_case > 0) {
|
|
1471
|
+
patterns.push({ type: 'naming-snake_case', value: `snake_case identifiers: ${namingStyles.snake_case}`, count: namingStyles.snake_case, examples: [] });
|
|
1472
|
+
}
|
|
1473
|
+
// --- Structure patterns: one entry per pattern type ---
|
|
1379
1474
|
const sortedStructures = [...structurePatterns.entries()].sort((a, b) => b[1] - a[1]);
|
|
1380
1475
|
if (sortedStructures.length > 0) {
|
|
1381
1476
|
patterns.push({
|
|
1382
|
-
type: 'structure',
|
|
1477
|
+
type: 'structure-summary',
|
|
1383
1478
|
value: sortedStructures.map(([t, c]) => `${t}(${c})`).join(', '),
|
|
1384
1479
|
count: sortedStructures.reduce((s, [, c]) => s + c, 0),
|
|
1385
1480
|
examples: sortedStructures.slice(0, 3).map(([t]) => t),
|
|
1386
1481
|
});
|
|
1387
1482
|
}
|
|
1388
|
-
|
|
1483
|
+
for (const [structType, count] of sortedStructures) {
|
|
1484
|
+
patterns.push({
|
|
1485
|
+
type: `structure-${structType}`,
|
|
1486
|
+
value: `${structType}: ${count} occurrences`,
|
|
1487
|
+
count,
|
|
1488
|
+
examples: [structType],
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
// --- API patterns: one entry per method/type ---
|
|
1389
1492
|
const sortedApi = [...apiPatterns.entries()].sort((a, b) => b[1] - a[1]);
|
|
1390
1493
|
if (sortedApi.length > 0) {
|
|
1391
1494
|
patterns.push({
|
|
1392
|
-
type: 'api-
|
|
1495
|
+
type: 'api-summary',
|
|
1393
1496
|
value: sortedApi.map(([t, c]) => `${t}(${c})`).join(', '),
|
|
1394
1497
|
count: sortedApi.reduce((s, [, c]) => s + c, 0),
|
|
1395
1498
|
examples: sortedApi.slice(0, 3).map(([t]) => t),
|
|
1396
1499
|
});
|
|
1397
1500
|
}
|
|
1501
|
+
for (const [apiType, count] of sortedApi) {
|
|
1502
|
+
patterns.push({
|
|
1503
|
+
type: `api-${apiType.toLowerCase()}`,
|
|
1504
|
+
value: `${apiType}: ${count} occurrences`,
|
|
1505
|
+
count,
|
|
1506
|
+
examples: [apiType],
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
// --- Function signature patterns ---
|
|
1510
|
+
if (functionSigs.length > 0) {
|
|
1511
|
+
// Prefix grouping: group functions by common prefixes (get*, set*, handle*, on*, is*, etc.)
|
|
1512
|
+
const prefixGroups = new Map();
|
|
1513
|
+
const prefixRegex = /^(get|set|handle|on|is|has|create|update|delete|find|fetch|load|save|init|check|validate|parse|format|build|render|compute|process|resolve|transform|convert|ensure|generate|register|dispatch|emit|notify|apply|reset|clear|remove|add|with|to|from)/i;
|
|
1514
|
+
for (const fn of functionSigs) {
|
|
1515
|
+
const match = fn.match(prefixRegex);
|
|
1516
|
+
const prefix = match ? match[1].toLowerCase() : '_other';
|
|
1517
|
+
if (!prefixGroups.has(prefix))
|
|
1518
|
+
prefixGroups.set(prefix, []);
|
|
1519
|
+
prefixGroups.get(prefix).push(fn);
|
|
1520
|
+
}
|
|
1521
|
+
for (const [prefix, fns] of [...prefixGroups.entries()].sort((a, b) => b[1].length - a[1].length).slice(0, 15)) {
|
|
1522
|
+
if (fns.length >= 2) {
|
|
1523
|
+
patterns.push({
|
|
1524
|
+
type: `function-prefix-${prefix}`,
|
|
1525
|
+
value: `${prefix}* functions: ${fns.length} (${fns.slice(0, 5).join(', ')}${fns.length > 5 ? ` +${fns.length - 5} more` : ''})`,
|
|
1526
|
+
count: fns.length,
|
|
1527
|
+
examples: fns.slice(0, 5),
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
// --- Async patterns (collected in single pass above) ---
|
|
1533
|
+
if (asyncCount + syncCount > 0) {
|
|
1534
|
+
patterns.push({
|
|
1535
|
+
type: 'async-style',
|
|
1536
|
+
value: `async:${asyncCount} sync:${syncCount} await-usage:${awaitCount} promise-catch:${promiseCatchCount}`,
|
|
1537
|
+
count: asyncCount + syncCount,
|
|
1538
|
+
examples: asyncCount > syncCount ? ['Async-first codebase'] : ['Sync-first codebase'],
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
// --- Decorator/annotation patterns (collected in single pass above) ---
|
|
1542
|
+
for (const [decName, count] of [...decoratorCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10)) {
|
|
1543
|
+
if (count >= 2) {
|
|
1544
|
+
patterns.push({
|
|
1545
|
+
type: 'decorator',
|
|
1546
|
+
value: `@${decName}: ${count} usages`,
|
|
1547
|
+
count,
|
|
1548
|
+
examples: [`@${decName}`],
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
// --- Type system patterns (collected in single pass above) ---
|
|
1553
|
+
if (interfaceCount > 0)
|
|
1554
|
+
patterns.push({ type: 'type-interface', value: `${interfaceCount} interfaces defined`, count: interfaceCount, examples: [] });
|
|
1555
|
+
if (typeAliasCount > 0)
|
|
1556
|
+
patterns.push({ type: 'type-alias', value: `${typeAliasCount} type aliases`, count: typeAliasCount, examples: [] });
|
|
1557
|
+
if (enumCount > 0)
|
|
1558
|
+
patterns.push({ type: 'type-enum', value: `${enumCount} enums`, count: enumCount, examples: [] });
|
|
1559
|
+
if (genericCount > 0)
|
|
1560
|
+
patterns.push({ type: 'type-generics', value: `${genericCount} generic declarations`, count: genericCount, examples: [] });
|
|
1561
|
+
// --- Testing patterns (collected in single pass above) ---
|
|
1562
|
+
if (describeCount + itCount + testCount > 0) {
|
|
1563
|
+
patterns.push({
|
|
1564
|
+
type: 'test-framework',
|
|
1565
|
+
value: `describe:${describeCount} it:${itCount} test:${testCount} expect:${expectCount} mocks:${mockCount}`,
|
|
1566
|
+
count: describeCount + itCount + testCount,
|
|
1567
|
+
examples: itCount > testCount ? ['it() style (BDD)'] : ['test() style'],
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
if (mockCount > 0) {
|
|
1571
|
+
patterns.push({ type: 'test-mocking', value: `${mockCount} mock/stub usages`, count: mockCount, examples: [] });
|
|
1572
|
+
}
|
|
1573
|
+
// --- Config/env patterns (collected in single pass above) ---
|
|
1574
|
+
if (envAccessCount > 0)
|
|
1575
|
+
patterns.push({ type: 'config-env', value: `process.env accessed ${envAccessCount} times`, count: envAccessCount, examples: [] });
|
|
1576
|
+
if (configImportCount > 0)
|
|
1577
|
+
patterns.push({ type: 'config-import', value: `${configImportCount} config-related imports`, count: configImportCount, examples: [] });
|
|
1578
|
+
// --- Logging patterns (collected in single pass above) ---
|
|
1579
|
+
for (const [logType, count] of [...logPatterns.entries()].sort((a, b) => b[1] - a[1])) {
|
|
1580
|
+
patterns.push({ type: `logging-${logType.replace(/\./g, '-')}`, value: `${logType}: ${count} usages`, count, examples: [logType] });
|
|
1581
|
+
}
|
|
1398
1582
|
return patterns;
|
|
1399
1583
|
}
|
|
1400
1584
|
export const hooksPretrain = {
|
|
@@ -1419,7 +1603,7 @@ export const hooksPretrain = {
|
|
|
1419
1603
|
: (typeof rawFileTypes === 'string' ? rawFileTypes : 'ts,js,py,md').split(',').map(e => e.trim());
|
|
1420
1604
|
const startTime = Date.now();
|
|
1421
1605
|
// Determine file limit by depth
|
|
1422
|
-
const fileLimit = depth === 'deep' ?
|
|
1606
|
+
const fileLimit = depth === 'deep' ? 500 : depth === 'shallow' ? 80 : 200;
|
|
1423
1607
|
const extensions = new Set(fileTypesArr);
|
|
1424
1608
|
// Phase 1: Retrieve - collect source files
|
|
1425
1609
|
const retrieveStart = Date.now();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moflo/cli",
|
|
3
|
-
"version": "4.8.
|
|
3
|
+
"version": "4.8.38",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MoFlo CLI — AI agent orchestration with specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|