moflo 4.8.34 → 4.8.37

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,110 @@
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/bin/cli.js'),
39
+ resolve(projectRoot, 'node_modules/.bin/flo'),
40
+ ];
41
+ for (const p of paths) {
42
+ if (existsSync(p)) return p;
43
+ }
44
+ return null;
45
+ }
46
+
47
+ function runStep(label, cmd, args, timeoutMs = 120_000) {
48
+ const start = Date.now();
49
+ log(`START ${label}`);
50
+ try {
51
+ execFileSync(cmd, args, {
52
+ cwd: projectRoot,
53
+ timeout: timeoutMs,
54
+ stdio: 'ignore',
55
+ windowsHide: true,
56
+ });
57
+ const elapsed = ((Date.now() - start) / 1000).toFixed(1);
58
+ log(`DONE ${label} (${elapsed}s)`);
59
+ return true;
60
+ } catch (err) {
61
+ const elapsed = ((Date.now() - start) / 1000).toFixed(1);
62
+ log(`FAIL ${label} (${elapsed}s): ${err.message?.split('\n')[0] || 'unknown'}`);
63
+ return false;
64
+ }
65
+ }
66
+
67
+ async function main() {
68
+ const startTime = Date.now();
69
+ log('Sequential indexing chain started');
70
+
71
+ // 1. Guidance indexer
72
+ const guidanceScript = resolveBin('flo-index', 'index-guidance.mjs');
73
+ if (guidanceScript) {
74
+ runStep('guidance-index', 'node', [guidanceScript]);
75
+ } else {
76
+ log('SKIP guidance-index (script not found)');
77
+ }
78
+
79
+ // 2. Code map generator (the big one — ~22s)
80
+ const codeMapScript = resolveBin('flo-codemap', 'generate-code-map.mjs');
81
+ if (codeMapScript) {
82
+ runStep('code-map', 'node', [codeMapScript], 180_000);
83
+ } else {
84
+ log('SKIP code-map (script not found)');
85
+ }
86
+
87
+ // 3. Test indexer
88
+ const testScript = resolveBin('flo-testmap', 'index-tests.mjs');
89
+ if (testScript) {
90
+ runStep('test-index', 'node', [testScript]);
91
+ } else {
92
+ log('SKIP test-index (script not found)');
93
+ }
94
+
95
+ // 4. HNSW rebuild — MUST run last, after all writes are committed
96
+ const localCli = getLocalCliPath();
97
+ if (localCli) {
98
+ runStep('hnsw-rebuild', 'node', [localCli, 'memory', 'rebuild', '--force']);
99
+ } else {
100
+ log('SKIP hnsw-rebuild (CLI not found)');
101
+ }
102
+
103
+ const totalElapsed = ((Date.now() - startTime) / 1000).toFixed(1);
104
+ log(`Sequential indexing chain complete (${totalElapsed}s)`);
105
+ }
106
+
107
+ main().catch(err => {
108
+ log(`FATAL: ${err.message}`);
109
+ process.exit(0);
110
+ });
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
 
@@ -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.34",
3
+ "version": "4.8.37",
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",
@@ -730,24 +730,36 @@ async function checkSemanticQuality() {
730
730
  };
731
731
  }
732
732
  }
733
- // Check memory-backed patterns (populated by pretrain) as a fallback for neural checks
734
- async function checkMemoryPatterns(namespace) {
733
+ // Check memory-backed patterns (populated by pretrain) as a fallback for neural checks.
734
+ // Uses the same pattern-search handler that pretrain writes to.
735
+ async function checkMemoryPatterns(_namespace) {
735
736
  try {
736
- // @ts-ignore memory is not in tsconfig project references but is available at runtime
737
- const memoryMod = await import('../../../../memory/dist/index.js');
738
- if (memoryMod.search) {
739
- const results = await memoryMod.search({ query: 'pattern', namespace, limit: 1 });
740
- return results?.length ?? 0;
741
- }
742
- // Fallback: try AgentDB route
743
- if (memoryMod.getStore) {
744
- const store = memoryMod.getStore();
745
- const entries = store?.list?.(namespace) ?? [];
746
- return entries.length;
737
+ // Use the pattern-search handler (same store pretrain writes to)
738
+ const hooksMod = await import('../mcp-tools/hooks-tools.js');
739
+ if (hooksMod.hooksPatternSearch) {
740
+ const result = await hooksMod.hooksPatternSearch.handler({
741
+ query: 'pretrain',
742
+ topK: 1,
743
+ minConfidence: 0.1,
744
+ });
745
+ const matches = result?.results;
746
+ if (Array.isArray(matches))
747
+ return matches.length;
747
748
  }
748
749
  }
749
750
  catch {
750
- // Memory module not available
751
+ // hooks module not available
752
+ }
753
+ // Secondary fallback: check the memory DB file exists
754
+ try {
755
+ const { existsSync } = await import('fs');
756
+ const { join } = await import('path');
757
+ const dbPath = join(process.cwd(), '.claude', 'memory.db');
758
+ if (existsSync(dbPath))
759
+ return 1;
760
+ }
761
+ catch {
762
+ // fs not available
751
763
  }
752
764
  return 0;
753
765
  }
@@ -779,22 +791,24 @@ async function checkIntelligence() {
779
791
  catch (e) {
780
792
  failures.push(`SONA (${e instanceof Error ? e.message : 'error'})`);
781
793
  }
782
- // 2. ReasoningBank — store + retrieve
794
+ // 2. ReasoningBank — verify instantiation and trajectory store/distill lifecycle
783
795
  try {
784
796
  const rb = neural.createReasoningBank();
785
- const embedding = new Float32Array(64).fill(0.2);
786
- rb.storeTrajectory({
787
- id: 'doctor-test',
797
+ const trajectory = {
798
+ trajectoryId: 'doctor-test',
788
799
  context: 'health check',
789
800
  domain: 'general',
790
- mode: 'balanced',
791
- steps: [{ action: 'test', reward: 1, embedding, timestamp: Date.now() }],
801
+ steps: [{ action: 'test', reward: 1, embedding: new Float32Array(64).fill(0.2), timestamp: Date.now() }],
792
802
  startTime: Date.now(),
793
803
  endTime: Date.now(),
794
- quality: 1,
795
- });
796
- const retrieved = rb.retrieve(embedding, 1);
797
- if (retrieved.length > 0) {
804
+ qualityScore: 0.9,
805
+ isComplete: true,
806
+ verdict: { success: true, quality: 0.9, feedback: 'test' },
807
+ };
808
+ rb.storeTrajectory(trajectory);
809
+ // distill() populates memories (storeTrajectory alone does not)
810
+ const distilled = await rb.distill(trajectory);
811
+ if (distilled || rb.getTrajectories().length > 0) {
798
812
  results.push('ReasoningBank');
799
813
  }
800
814
  else {
@@ -804,7 +818,7 @@ async function checkIntelligence() {
804
818
  results.push('ReasoningBank(memory)');
805
819
  }
806
820
  else {
807
- failures.push('ReasoningBank (retrieve failed)');
821
+ failures.push('ReasoningBank (distill returned no data)');
808
822
  }
809
823
  }
810
824
  }
@@ -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.34';
5
+ export const VERSION = '4.8.37';
6
6
  //# sourceMappingURL=version.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moflo/cli",
3
- "version": "4.8.34",
3
+ "version": "4.8.37",
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",