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.
@@ -258,21 +258,23 @@ async function main() {
258
258
  }
259
259
 
260
260
  case 'session-start': {
261
- // All startup tasks run in background (non-blocking)
262
- // Start daemon quietly in background
261
+ // Start daemon quietly in background (no DB writes)
263
262
  runDaemonStartBackground();
264
- // Index guidance files in background
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
- // Force HNSW rebuild to ensure all processes use identical fresh index
273
- // This fixes agent search result mismatches (0.61 vs 0.81 similarity)
274
- runHNSWRebuildBackground();
275
- // Neural patterns now loaded by moflo core routing — no external patching.
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 three indexers in parallel as background processes.
187
- 2. **Incremental** — Each indexer tracks file modification times. Only files that changed since the last index run are re-processed. The first run takes longer; subsequent runs typically finish in under a second.
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
- // All startup tasks run in background (non-blocking)
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
- // Index guidance files in background
267
- runIndexGuidanceBackground();
268
- // Generate structural code map in background
269
- runCodeMapBackground();
270
- // Index test files in background
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.35",
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 + retrieve
794
+ // 2. ReasoningBank — verify instantiation and trajectory store/distill lifecycle
795
795
  try {
796
796
  const rb = neural.createReasoningBank();
797
- const embedding = new Float32Array(64).fill(0.2);
798
- rb.storeTrajectory({
799
- id: 'doctor-test',
797
+ const trajectory = {
798
+ trajectoryId: 'doctor-test',
800
799
  context: 'health check',
801
800
  domain: 'general',
802
- mode: 'balanced',
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
- quality: 1,
807
- });
808
- const retrieved = rb.retrieve(embedding, 1);
809
- if (retrieved.length > 0) {
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 (retrieve failed)');
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 50)
1290
- if (functionSigs.length < 50) {
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
- // Top imports by frequency
1340
- const sortedImports = [...importCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 15);
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
- // Export patterns
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]).slice(0, 10);
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-handling',
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
- // Naming conventions
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
- // Structure patterns
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
- // API patterns
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-pattern',
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' ? 100 : depth === 'shallow' ? 30 : 60;
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();
@@ -2,5 +2,5 @@
2
2
  * Auto-generated by build. Do not edit manually.
3
3
  * Source of truth: root package.json → scripts/sync-version.mjs
4
4
  */
5
- export const VERSION = '4.8.35';
5
+ export const VERSION = '4.8.38';
6
6
  //# sourceMappingURL=version.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moflo/cli",
3
- "version": "4.8.35",
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",