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.
- package/.claude/scripts/hooks.mjs +15 -13
- package/.claude/scripts/index-all.mjs +110 -0
- package/README.md +3 -3
- package/bin/index-patterns.mjs +376 -0
- package/package.json +1 -1
- package/src/@claude-flow/cli/dist/src/commands/doctor.js +39 -25
- package/src/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +202 -18
- package/src/@claude-flow/cli/dist/src/version.js +1 -1
- package/src/@claude-flow/cli/package.json +1 -1
|
@@ -258,21 +258,23 @@ async function main() {
|
|
|
258
258
|
}
|
|
259
259
|
|
|
260
260
|
case 'session-start': {
|
|
261
|
-
//
|
|
262
|
-
// Start daemon quietly in background
|
|
261
|
+
// Start daemon quietly in background (no DB writes)
|
|
263
262
|
runDaemonStartBackground();
|
|
264
|
-
//
|
|
265
|
-
runIndexGuidanceBackground();
|
|
266
|
-
// Generate structural code map in background
|
|
267
|
-
runCodeMapBackground();
|
|
268
|
-
// Index test files in background
|
|
269
|
-
runTestIndexBackground();
|
|
270
|
-
// Run pretrain in background to extract patterns from repository
|
|
263
|
+
// Run pretrain in background (writes to patterns namespace, fast)
|
|
271
264
|
runBackgroundPretrain();
|
|
272
|
-
//
|
|
273
|
-
// This
|
|
274
|
-
|
|
275
|
-
|
|
265
|
+
// Run all DB-writing indexers SEQUENTIALLY in a single background process.
|
|
266
|
+
// This avoids sql.js last-write-wins concurrency (issue #78).
|
|
267
|
+
// Chain: guidance → code-map → tests → HNSW rebuild (must be last).
|
|
268
|
+
const indexAllScript = resolve(__dirname, 'index-all.mjs');
|
|
269
|
+
if (existsSync(indexAllScript)) {
|
|
270
|
+
spawnWindowless('node', [indexAllScript], 'sequential indexing chain');
|
|
271
|
+
} else {
|
|
272
|
+
// Fallback to parallel if index-all.mjs not available
|
|
273
|
+
runIndexGuidanceBackground();
|
|
274
|
+
runCodeMapBackground();
|
|
275
|
+
runTestIndexBackground();
|
|
276
|
+
runHNSWRebuildBackground();
|
|
277
|
+
}
|
|
276
278
|
break;
|
|
277
279
|
}
|
|
278
280
|
|
|
@@ -0,0 +1,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
|
|
187
|
-
2. **Incremental** — Each indexer tracks file modification times. Only files that changed since the last index run are re-processed. The first run
|
|
186
|
+
1. **Session start hook** — When your AI client starts a new session, MoFlo's `SessionStart` hook launches the indexers sequentially in a single background process. This runs silently — you can start working immediately.
|
|
187
|
+
2. **Incremental** — Each indexer tracks file modification times. Only files that changed since the last index run are re-processed. The first run on a large codebase may take a minute or two; subsequent runs typically finish in under a second.
|
|
188
188
|
3. **Embedding generation** — Guidance chunks are embedded using MiniLM-L6-v2 (384 dimensions, WASM). These vectors are stored in the SQLite memory database and used for semantic search.
|
|
189
189
|
4. **No blocking** — The indexers run in the background and don't block your session from starting. You can begin working immediately.
|
|
190
190
|
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Index code patterns into claude-flow memory under the `patterns` namespace
|
|
4
|
+
*
|
|
5
|
+
* Extracts per-file patterns (not just aggregate summaries like pretrain):
|
|
6
|
+
* - Service/controller/repository class patterns
|
|
7
|
+
* - API route definitions and middleware usage
|
|
8
|
+
* - Error handling strategies per file
|
|
9
|
+
* - Export conventions per module
|
|
10
|
+
* - Test patterns (describe/it structure)
|
|
11
|
+
* - Configuration patterns
|
|
12
|
+
*
|
|
13
|
+
* Chunk types:
|
|
14
|
+
* pattern:file:{path} — Per-file pattern summary
|
|
15
|
+
* pattern:service:{name} — Service class patterns
|
|
16
|
+
* pattern:route:{path} — API route patterns
|
|
17
|
+
* pattern:error:{path} — Error handling patterns per file
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* node node_modules/moflo/bin/index-patterns.mjs # Incremental
|
|
21
|
+
* node node_modules/moflo/bin/index-patterns.mjs --force # Full reindex
|
|
22
|
+
* node node_modules/moflo/bin/index-patterns.mjs --verbose # Detailed logging
|
|
23
|
+
* node node_modules/moflo/bin/index-patterns.mjs --stats # Print stats and exit
|
|
24
|
+
* npx flo-patterns # Via npx
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'fs';
|
|
28
|
+
import { resolve, dirname, relative, basename, extname } from 'path';
|
|
29
|
+
import { fileURLToPath } from 'url';
|
|
30
|
+
import { createHash } from 'crypto';
|
|
31
|
+
import { spawn } from 'child_process';
|
|
32
|
+
import { mofloResolveURL } from './lib/moflo-resolve.mjs';
|
|
33
|
+
const initSqlJs = (await import(mofloResolveURL('sql.js'))).default;
|
|
34
|
+
|
|
35
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
|
|
37
|
+
function findProjectRoot() {
|
|
38
|
+
let dir = process.cwd();
|
|
39
|
+
const root = resolve(dir, '/');
|
|
40
|
+
while (dir !== root) {
|
|
41
|
+
if (existsSync(resolve(dir, 'package.json'))) return dir;
|
|
42
|
+
dir = dirname(dir);
|
|
43
|
+
}
|
|
44
|
+
return process.cwd();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const projectRoot = findProjectRoot();
|
|
48
|
+
const NAMESPACE = 'patterns';
|
|
49
|
+
const DB_PATH = resolve(projectRoot, '.swarm/memory.db');
|
|
50
|
+
const HASH_CACHE_PATH = resolve(projectRoot, '.swarm/patterns-hash.txt');
|
|
51
|
+
|
|
52
|
+
const args = process.argv.slice(2);
|
|
53
|
+
const force = args.includes('--force');
|
|
54
|
+
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
55
|
+
const statsOnly = args.includes('--stats');
|
|
56
|
+
|
|
57
|
+
function log(msg) { console.log(`[index-patterns] ${msg}`); }
|
|
58
|
+
function debug(msg) { if (verbose) console.log(`[index-patterns] ${msg}`); }
|
|
59
|
+
|
|
60
|
+
const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.py', '.go', '.rs']);
|
|
61
|
+
const EXCLUDE_DIRS = new Set([
|
|
62
|
+
'node_modules', 'dist', 'build', '.next', 'coverage',
|
|
63
|
+
'.claude', '.swarm', '.claude-flow', '.git', 'template',
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Database helpers
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
function ensureDbDir() {
|
|
71
|
+
const dir = dirname(DB_PATH);
|
|
72
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function getDb() {
|
|
76
|
+
ensureDbDir();
|
|
77
|
+
const SQL = await initSqlJs();
|
|
78
|
+
let db;
|
|
79
|
+
if (existsSync(DB_PATH)) {
|
|
80
|
+
const buffer = readFileSync(DB_PATH);
|
|
81
|
+
db = new SQL.Database(buffer);
|
|
82
|
+
} else {
|
|
83
|
+
db = new SQL.Database();
|
|
84
|
+
}
|
|
85
|
+
db.run(`
|
|
86
|
+
CREATE TABLE IF NOT EXISTS memory_entries (
|
|
87
|
+
id TEXT PRIMARY KEY,
|
|
88
|
+
key TEXT NOT NULL,
|
|
89
|
+
namespace TEXT DEFAULT 'default',
|
|
90
|
+
content TEXT NOT NULL,
|
|
91
|
+
type TEXT DEFAULT 'semantic',
|
|
92
|
+
embedding TEXT,
|
|
93
|
+
embedding_model TEXT DEFAULT 'local',
|
|
94
|
+
embedding_dimensions INTEGER,
|
|
95
|
+
tags TEXT,
|
|
96
|
+
metadata TEXT,
|
|
97
|
+
owner_id TEXT,
|
|
98
|
+
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
|
99
|
+
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now') * 1000),
|
|
100
|
+
expires_at INTEGER,
|
|
101
|
+
last_accessed_at INTEGER,
|
|
102
|
+
access_count INTEGER DEFAULT 0,
|
|
103
|
+
status TEXT DEFAULT 'active',
|
|
104
|
+
UNIQUE(namespace, key)
|
|
105
|
+
)
|
|
106
|
+
`);
|
|
107
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_memory_key_ns ON memory_entries(key, namespace)`);
|
|
108
|
+
db.run(`CREATE INDEX IF NOT EXISTS idx_memory_namespace ON memory_entries(namespace)`);
|
|
109
|
+
return db;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function saveDb(db) {
|
|
113
|
+
const data = db.export();
|
|
114
|
+
writeFileSync(DB_PATH, Buffer.from(data));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function generateId() {
|
|
118
|
+
return `mem_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function storeEntry(db, key, content, tags = []) {
|
|
122
|
+
const now = Date.now();
|
|
123
|
+
const id = generateId();
|
|
124
|
+
db.run(`
|
|
125
|
+
INSERT OR REPLACE INTO memory_entries
|
|
126
|
+
(id, key, namespace, content, metadata, tags, created_at, updated_at, status)
|
|
127
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'active')
|
|
128
|
+
`, [id, key, NAMESPACE, content, '{}', JSON.stringify(tags), now, now]);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function deleteNamespace(db) {
|
|
132
|
+
db.run(`DELETE FROM memory_entries WHERE namespace = ?`, [NAMESPACE]);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function countNamespace(db) {
|
|
136
|
+
const stmt = db.prepare(`SELECT COUNT(*) as cnt FROM memory_entries WHERE namespace = ?`);
|
|
137
|
+
stmt.bind([NAMESPACE]);
|
|
138
|
+
let count = 0;
|
|
139
|
+
if (stmt.step()) count = stmt.getAsObject().cnt;
|
|
140
|
+
stmt.free();
|
|
141
|
+
return count;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
// File collection
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
function collectSourceFiles(dir, maxDepth = 8, depth = 0) {
|
|
149
|
+
if (depth > maxDepth) return [];
|
|
150
|
+
const files = [];
|
|
151
|
+
let entries;
|
|
152
|
+
try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return []; }
|
|
153
|
+
for (const entry of entries) {
|
|
154
|
+
if (EXCLUDE_DIRS.has(entry.name)) continue;
|
|
155
|
+
const fullPath = resolve(dir, entry.name);
|
|
156
|
+
if (entry.isDirectory()) {
|
|
157
|
+
files.push(...collectSourceFiles(fullPath, maxDepth, depth + 1));
|
|
158
|
+
} else if (entry.isFile() && SOURCE_EXTENSIONS.has(extname(entry.name))) {
|
|
159
|
+
files.push(fullPath);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return files;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Pattern extraction — per-file
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
|
|
169
|
+
function extractFilePatterns(filePath, content) {
|
|
170
|
+
const lines = content.split('\n');
|
|
171
|
+
const relPath = relative(projectRoot, filePath);
|
|
172
|
+
const patterns = [];
|
|
173
|
+
const tags = [];
|
|
174
|
+
|
|
175
|
+
// Collect metrics
|
|
176
|
+
const imports = [];
|
|
177
|
+
const exports = [];
|
|
178
|
+
const classes = [];
|
|
179
|
+
const functions = [];
|
|
180
|
+
const routes = [];
|
|
181
|
+
const errorHandling = [];
|
|
182
|
+
const interfaces = [];
|
|
183
|
+
|
|
184
|
+
for (const line of lines) {
|
|
185
|
+
const t = line.trim();
|
|
186
|
+
|
|
187
|
+
// Imports
|
|
188
|
+
const impMatch = t.match(/^import\s+.*?from\s+['"]([^'"]+)['"]/);
|
|
189
|
+
if (impMatch) imports.push(impMatch[1]);
|
|
190
|
+
|
|
191
|
+
// Exports
|
|
192
|
+
if (/^export\s+default\b/.test(t)) exports.push('default');
|
|
193
|
+
const namedExp = t.match(/^export\s+(?:const|function|class|interface|type|enum)\s+(\w+)/);
|
|
194
|
+
if (namedExp) exports.push(namedExp[1]);
|
|
195
|
+
|
|
196
|
+
// Classes
|
|
197
|
+
const classMatch = t.match(/^(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/);
|
|
198
|
+
if (classMatch) {
|
|
199
|
+
classes.push(classMatch[1]);
|
|
200
|
+
if (/Service\b/.test(classMatch[1])) tags.push('service');
|
|
201
|
+
if (/Controller\b/.test(classMatch[1])) tags.push('controller');
|
|
202
|
+
if (/Repository\b/.test(classMatch[1])) tags.push('repository');
|
|
203
|
+
if (/Provider\b/.test(classMatch[1])) tags.push('provider');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Functions
|
|
207
|
+
const fnMatch = t.match(/^(?:export\s+)?(?:async\s+)?function\s+(\w+)/);
|
|
208
|
+
if (fnMatch) functions.push(fnMatch[1]);
|
|
209
|
+
const arrowMatch = t.match(/^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\(/);
|
|
210
|
+
if (arrowMatch) functions.push(arrowMatch[1]);
|
|
211
|
+
|
|
212
|
+
// Interfaces/types
|
|
213
|
+
const ifaceMatch = t.match(/^(?:export\s+)?(?:interface|type)\s+(\w+)/);
|
|
214
|
+
if (ifaceMatch) interfaces.push(ifaceMatch[1]);
|
|
215
|
+
|
|
216
|
+
// Routes
|
|
217
|
+
const routeMatch = t.match(/\.(get|post|put|patch|delete)\s*\(\s*['"]([^'"]*)['"]/i);
|
|
218
|
+
if (routeMatch) routes.push(`${routeMatch[1].toUpperCase()} ${routeMatch[2]}`);
|
|
219
|
+
if (/@(Get|Post|Put|Delete|Patch)\s*\(/.test(t)) {
|
|
220
|
+
const dec = t.match(/@(\w+)\s*\(\s*['"]([^'"]*)['"]/);
|
|
221
|
+
if (dec) routes.push(`${dec[1].toUpperCase()} ${dec[2]}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Error handling
|
|
225
|
+
if (/\bcatch\s*\(/.test(t)) errorHandling.push('try-catch');
|
|
226
|
+
if (/\.catch\(/.test(t)) errorHandling.push('promise-catch');
|
|
227
|
+
if (/throw\s+new\s+(\w+)/.test(t)) {
|
|
228
|
+
const err = t.match(/throw\s+new\s+(\w+)/);
|
|
229
|
+
if (err) errorHandling.push(`throws:${err[1]}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Only create entries for files with meaningful patterns
|
|
234
|
+
if (classes.length === 0 && functions.length < 2 && routes.length === 0 && interfaces.length < 2) {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Build human-readable pattern summary for this file
|
|
239
|
+
const parts = [];
|
|
240
|
+
if (classes.length > 0) parts.push(`Classes: ${classes.join(', ')}`);
|
|
241
|
+
if (functions.length > 0) parts.push(`Functions: ${functions.slice(0, 10).join(', ')}${functions.length > 10 ? ` (+${functions.length - 10} more)` : ''}`);
|
|
242
|
+
if (interfaces.length > 0) parts.push(`Types: ${interfaces.slice(0, 8).join(', ')}${interfaces.length > 8 ? ` (+${interfaces.length - 8} more)` : ''}`);
|
|
243
|
+
if (routes.length > 0) parts.push(`Routes: ${routes.join(', ')}`);
|
|
244
|
+
if (exports.length > 0) parts.push(`Exports: ${exports.slice(0, 8).join(', ')}${exports.length > 8 ? ` (+${exports.length - 8} more)` : ''}`);
|
|
245
|
+
if (errorHandling.length > 0) {
|
|
246
|
+
const unique = [...new Set(errorHandling)];
|
|
247
|
+
parts.push(`Error handling: ${unique.join(', ')}`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const summary = `# ${relPath}\n${parts.join('\n')}`;
|
|
251
|
+
|
|
252
|
+
// File-level pattern entry
|
|
253
|
+
patterns.push({
|
|
254
|
+
key: `pattern:file:${relPath}`,
|
|
255
|
+
content: summary,
|
|
256
|
+
tags: ['file-pattern', ...tags],
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Service/controller entries get their own chunk for better search
|
|
260
|
+
for (const cls of classes) {
|
|
261
|
+
if (/Service|Controller|Repository|Provider|Handler|Manager/.test(cls)) {
|
|
262
|
+
const clsMethods = functions.filter(f => f !== cls); // rough heuristic
|
|
263
|
+
patterns.push({
|
|
264
|
+
key: `pattern:class:${cls}`,
|
|
265
|
+
content: `# ${cls} (${relPath})\nType: ${tags.filter(t => ['service', 'controller', 'repository', 'provider'].includes(t)).join(', ') || 'class'}\nMethods: ${clsMethods.slice(0, 15).join(', ') || 'none detected'}`,
|
|
266
|
+
tags: ['class-pattern', ...tags],
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Route entries
|
|
272
|
+
if (routes.length > 0) {
|
|
273
|
+
patterns.push({
|
|
274
|
+
key: `pattern:routes:${relPath}`,
|
|
275
|
+
content: `# Routes in ${relPath}\n${routes.join('\n')}`,
|
|
276
|
+
tags: ['route-pattern', 'api'],
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return patterns;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
// Main
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
async function main() {
|
|
288
|
+
const startTime = Date.now();
|
|
289
|
+
|
|
290
|
+
// Stats mode
|
|
291
|
+
if (statsOnly) {
|
|
292
|
+
const db = await getDb();
|
|
293
|
+
const count = countNamespace(db);
|
|
294
|
+
const files = collectSourceFiles(projectRoot);
|
|
295
|
+
log(`${files.length} source files, ${count} chunks in patterns namespace`);
|
|
296
|
+
db.close();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Collect files
|
|
301
|
+
const files = collectSourceFiles(projectRoot);
|
|
302
|
+
log(`Found ${files.length} source files`);
|
|
303
|
+
|
|
304
|
+
if (files.length === 0) {
|
|
305
|
+
log('No source files found');
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Hash check for incremental
|
|
310
|
+
const hashInput = files.map(f => {
|
|
311
|
+
try { return `${f}:${statSync(f).mtimeMs}`; } catch { return f; }
|
|
312
|
+
}).join('\n');
|
|
313
|
+
const currentHash = createHash('sha256').update(hashInput).digest('hex');
|
|
314
|
+
|
|
315
|
+
if (!force && existsSync(HASH_CACHE_PATH)) {
|
|
316
|
+
const cached = readFileSync(HASH_CACHE_PATH, 'utf-8').trim();
|
|
317
|
+
if (cached === currentHash) {
|
|
318
|
+
log('No changes detected (use --force to reindex)');
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Extract patterns from all files
|
|
324
|
+
const allPatterns = [];
|
|
325
|
+
let filesWithPatterns = 0;
|
|
326
|
+
|
|
327
|
+
for (const filePath of files) {
|
|
328
|
+
let content;
|
|
329
|
+
try { content = readFileSync(filePath, 'utf-8'); } catch { continue; }
|
|
330
|
+
const patterns = extractFilePatterns(filePath, content);
|
|
331
|
+
if (patterns.length > 0) {
|
|
332
|
+
filesWithPatterns++;
|
|
333
|
+
allPatterns.push(...patterns);
|
|
334
|
+
}
|
|
335
|
+
debug(`${relative(projectRoot, filePath)}: ${patterns.length} patterns`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
log(`Extracted ${allPatterns.length} pattern chunks from ${filesWithPatterns} files`);
|
|
339
|
+
|
|
340
|
+
// Write to database
|
|
341
|
+
const db = await getDb();
|
|
342
|
+
deleteNamespace(db);
|
|
343
|
+
|
|
344
|
+
for (const p of allPatterns) {
|
|
345
|
+
storeEntry(db, p.key, p.content, p.tags);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
saveDb(db);
|
|
349
|
+
db.close();
|
|
350
|
+
|
|
351
|
+
// Save hash
|
|
352
|
+
writeFileSync(HASH_CACHE_PATH, currentHash, 'utf-8');
|
|
353
|
+
|
|
354
|
+
// Trigger embedding generation in background
|
|
355
|
+
try {
|
|
356
|
+
const embeddingScript = resolve(projectRoot, 'node_modules/moflo/bin/build-embeddings.mjs');
|
|
357
|
+
if (existsSync(embeddingScript)) {
|
|
358
|
+
const child = spawn('node', [embeddingScript, '--namespace', NAMESPACE], {
|
|
359
|
+
cwd: projectRoot,
|
|
360
|
+
detached: true,
|
|
361
|
+
stdio: 'ignore',
|
|
362
|
+
windowsHide: true,
|
|
363
|
+
});
|
|
364
|
+
child.unref();
|
|
365
|
+
debug('Embedding generation started in background');
|
|
366
|
+
}
|
|
367
|
+
} catch { /* ignore */ }
|
|
368
|
+
|
|
369
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
370
|
+
log(`Done in ${elapsed}s — ${allPatterns.length} pattern chunks written`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
main().catch(err => {
|
|
374
|
+
log(`Error: ${err.message}`);
|
|
375
|
+
process.exit(1);
|
|
376
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "moflo",
|
|
3
|
-
"version": "4.8.
|
|
3
|
+
"version": "4.8.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
|
-
|
|
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
|
-
//
|
|
737
|
-
const
|
|
738
|
-
if (
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
const
|
|
745
|
-
|
|
746
|
-
|
|
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
|
-
//
|
|
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
|
|
794
|
+
// 2. ReasoningBank — verify instantiation and trajectory store/distill lifecycle
|
|
783
795
|
try {
|
|
784
796
|
const rb = neural.createReasoningBank();
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
id: 'doctor-test',
|
|
797
|
+
const trajectory = {
|
|
798
|
+
trajectoryId: 'doctor-test',
|
|
788
799
|
context: 'health check',
|
|
789
800
|
domain: 'general',
|
|
790
|
-
|
|
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
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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 (
|
|
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
|
|
1290
|
-
if (functionSigs.length <
|
|
1296
|
+
// Function signatures (collect first 200)
|
|
1297
|
+
if (functionSigs.length < 200) {
|
|
1291
1298
|
const fnMatch = trimmed.match(/^(?:export\s+)?(?:async\s+)?function\s+(\w+)/);
|
|
1292
1299
|
if (fnMatch)
|
|
1293
1300
|
functionSigs.push(fnMatch[1]);
|
|
@@ -1333,23 +1340,91 @@ function extractPatterns(files) {
|
|
|
1333
1340
|
if (/interface\s+\w+/.test(trimmed)) {
|
|
1334
1341
|
structurePatterns.set('typed-interface', (structurePatterns.get('typed-interface') || 0) + 1);
|
|
1335
1342
|
}
|
|
1343
|
+
// Extended: async patterns
|
|
1344
|
+
if (/^(?:export\s+)?async\s+function\b/.test(trimmed) || /=\s*async\s*\(/.test(trimmed))
|
|
1345
|
+
asyncCount++;
|
|
1346
|
+
else if (/^(?:export\s+)?function\b/.test(trimmed) || /=\s*\([^)]*\)\s*=>/.test(trimmed))
|
|
1347
|
+
syncCount++;
|
|
1348
|
+
if (/\.catch\(/.test(trimmed))
|
|
1349
|
+
promiseCatchCount++;
|
|
1350
|
+
if (/\bawait\b/.test(trimmed))
|
|
1351
|
+
awaitCount++;
|
|
1352
|
+
// Extended: decorators
|
|
1353
|
+
const dec = trimmed.match(/^@(\w+)/);
|
|
1354
|
+
if (dec)
|
|
1355
|
+
decoratorCounts.set(dec[1], (decoratorCounts.get(dec[1]) || 0) + 1);
|
|
1356
|
+
// Extended: type system
|
|
1357
|
+
if (/^(?:export\s+)?interface\s+\w+/.test(trimmed))
|
|
1358
|
+
interfaceCount++;
|
|
1359
|
+
if (/^(?:export\s+)?type\s+\w+\s*=/.test(trimmed))
|
|
1360
|
+
typeAliasCount++;
|
|
1361
|
+
if (/^(?:export\s+)?enum\s+\w+/.test(trimmed))
|
|
1362
|
+
enumCount++;
|
|
1363
|
+
if (/<\w+(?:\s+extends\s+\w+)?>/.test(trimmed) && /(?:function|class|interface|type)\s/.test(trimmed))
|
|
1364
|
+
genericCount++;
|
|
1365
|
+
// Extended: testing
|
|
1366
|
+
if (/^describe\s*\(/.test(trimmed))
|
|
1367
|
+
describeCount++;
|
|
1368
|
+
if (/^\s*it\s*\(/.test(trimmed))
|
|
1369
|
+
itCount++;
|
|
1370
|
+
if (/^\s*test\s*\(/.test(trimmed))
|
|
1371
|
+
testCount++;
|
|
1372
|
+
if (/expect\s*\(/.test(trimmed))
|
|
1373
|
+
expectCount++;
|
|
1374
|
+
if (/\b(?:vi\.mock|jest\.mock|sinon\.stub|\.mockImplementation|\.mockReturnValue)\b/.test(trimmed))
|
|
1375
|
+
mockCount++;
|
|
1376
|
+
// Extended: config/env
|
|
1377
|
+
if (/process\.env\b/.test(trimmed))
|
|
1378
|
+
envAccessCount++;
|
|
1379
|
+
if (/config|\.env|dotenv|convict/i.test(trimmed) && /import|require/.test(trimmed))
|
|
1380
|
+
configImportCount++;
|
|
1381
|
+
// Extended: logging
|
|
1382
|
+
if (/console\.log\b/.test(trimmed))
|
|
1383
|
+
logPatterns.set('console.log', (logPatterns.get('console.log') || 0) + 1);
|
|
1384
|
+
if (/console\.error\b/.test(trimmed))
|
|
1385
|
+
logPatterns.set('console.error', (logPatterns.get('console.error') || 0) + 1);
|
|
1386
|
+
if (/console\.warn\b/.test(trimmed))
|
|
1387
|
+
logPatterns.set('console.warn', (logPatterns.get('console.warn') || 0) + 1);
|
|
1388
|
+
if (/\blogger\.\w+/.test(trimmed))
|
|
1389
|
+
logPatterns.set('logger.*', (logPatterns.get('logger.*') || 0) + 1);
|
|
1336
1390
|
}
|
|
1337
1391
|
}
|
|
1338
1392
|
const patterns = [];
|
|
1339
|
-
//
|
|
1340
|
-
const sortedImports = [...importCounts.entries()].sort((a, b) => b[1] - a[1])
|
|
1393
|
+
// --- Import patterns: one entry per top module (not just a single aggregate) ---
|
|
1394
|
+
const sortedImports = [...importCounts.entries()].sort((a, b) => b[1] - a[1]);
|
|
1395
|
+
// Summary entry
|
|
1341
1396
|
if (sortedImports.length > 0) {
|
|
1342
1397
|
patterns.push({
|
|
1343
|
-
type: 'import',
|
|
1344
|
-
value: `Top modules: ${sortedImports.map(([m, c]) => `${m}(${c})`).join(', ')}`,
|
|
1398
|
+
type: 'import-summary',
|
|
1399
|
+
value: `Top modules: ${sortedImports.slice(0, 15).map(([m, c]) => `${m}(${c})`).join(', ')}`,
|
|
1345
1400
|
count: sortedImports.reduce((s, [, c]) => s + c, 0),
|
|
1346
1401
|
examples: sortedImports.slice(0, 5).map(([m]) => m),
|
|
1347
1402
|
});
|
|
1348
1403
|
}
|
|
1349
|
-
//
|
|
1404
|
+
// Individual module entries (top 20 non-relative)
|
|
1405
|
+
for (const [mod, count] of sortedImports.filter(([m]) => m !== '<relative>').slice(0, 20)) {
|
|
1406
|
+
patterns.push({
|
|
1407
|
+
type: 'import-module',
|
|
1408
|
+
value: `Module "${mod}" imported ${count} times across codebase`,
|
|
1409
|
+
count,
|
|
1410
|
+
examples: [mod],
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
// Relative vs external ratio
|
|
1414
|
+
const relativeCount = importCounts.get('<relative>') || 0;
|
|
1415
|
+
const externalCount = sortedImports.filter(([m]) => m !== '<relative>').reduce((s, [, c]) => s + c, 0);
|
|
1416
|
+
if (relativeCount + externalCount > 0) {
|
|
1417
|
+
patterns.push({
|
|
1418
|
+
type: 'import-ratio',
|
|
1419
|
+
value: `relative:${relativeCount} external:${externalCount} ratio:${(relativeCount / Math.max(1, relativeCount + externalCount) * 100).toFixed(0)}%`,
|
|
1420
|
+
count: relativeCount + externalCount,
|
|
1421
|
+
examples: relativeCount > externalCount ? ['Mostly internal imports'] : ['Mostly external deps'],
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
// --- Export patterns ---
|
|
1350
1425
|
if (exportPatterns.default + exportPatterns.named > 0) {
|
|
1351
1426
|
patterns.push({
|
|
1352
|
-
type: 'export',
|
|
1427
|
+
type: 'export-style',
|
|
1353
1428
|
value: `default:${exportPatterns.default} named:${exportPatterns.named}`,
|
|
1354
1429
|
count: exportPatterns.default + exportPatterns.named,
|
|
1355
1430
|
examples: exportPatterns.named > exportPatterns.default
|
|
@@ -1357,44 +1432,153 @@ function extractPatterns(files) {
|
|
|
1357
1432
|
: ['Default exports preferred'],
|
|
1358
1433
|
});
|
|
1359
1434
|
}
|
|
1360
|
-
// Error handling
|
|
1361
|
-
const sortedErrors = [...errorPatterns.entries()].sort((a, b) => b[1] - a[1])
|
|
1435
|
+
// --- Error handling: one entry per strategy ---
|
|
1436
|
+
const sortedErrors = [...errorPatterns.entries()].sort((a, b) => b[1] - a[1]);
|
|
1437
|
+
// Summary
|
|
1362
1438
|
if (sortedErrors.length > 0) {
|
|
1363
1439
|
patterns.push({
|
|
1364
|
-
type: 'error-
|
|
1440
|
+
type: 'error-summary',
|
|
1365
1441
|
value: sortedErrors.map(([t, c]) => `${t}(${c})`).join(', '),
|
|
1366
1442
|
count: sortedErrors.reduce((s, [, c]) => s + c, 0),
|
|
1367
1443
|
examples: sortedErrors.slice(0, 3).map(([t]) => t),
|
|
1368
1444
|
});
|
|
1369
1445
|
}
|
|
1370
|
-
//
|
|
1446
|
+
// Individual error strategies
|
|
1447
|
+
for (const [errType, count] of sortedErrors.slice(0, 15)) {
|
|
1448
|
+
patterns.push({
|
|
1449
|
+
type: 'error-strategy',
|
|
1450
|
+
value: `${errType}: ${count} occurrences`,
|
|
1451
|
+
count,
|
|
1452
|
+
examples: [errType],
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
// --- Naming conventions ---
|
|
1371
1456
|
const dominant = namingStyles.camelCase >= namingStyles.snake_case ? 'camelCase' : 'snake_case';
|
|
1372
1457
|
patterns.push({
|
|
1373
|
-
type: 'naming',
|
|
1458
|
+
type: 'naming-convention',
|
|
1374
1459
|
value: `camelCase:${namingStyles.camelCase} snake_case:${namingStyles.snake_case} PascalCase:${namingStyles.PascalCase} dominant:${dominant}`,
|
|
1375
1460
|
count: namingStyles.camelCase + namingStyles.snake_case + namingStyles.PascalCase,
|
|
1376
1461
|
examples: functionSigs.slice(0, 5),
|
|
1377
1462
|
});
|
|
1378
|
-
//
|
|
1463
|
+
// Individual naming entries
|
|
1464
|
+
if (namingStyles.camelCase > 0) {
|
|
1465
|
+
patterns.push({ type: 'naming-camelCase', value: `camelCase identifiers: ${namingStyles.camelCase}`, count: namingStyles.camelCase, examples: functionSigs.filter(f => /^[a-z]/.test(f)).slice(0, 5) });
|
|
1466
|
+
}
|
|
1467
|
+
if (namingStyles.PascalCase > 0) {
|
|
1468
|
+
patterns.push({ type: 'naming-PascalCase', value: `PascalCase identifiers: ${namingStyles.PascalCase}`, count: namingStyles.PascalCase, examples: functionSigs.filter(f => /^[A-Z]/.test(f)).slice(0, 5) });
|
|
1469
|
+
}
|
|
1470
|
+
if (namingStyles.snake_case > 0) {
|
|
1471
|
+
patterns.push({ type: 'naming-snake_case', value: `snake_case identifiers: ${namingStyles.snake_case}`, count: namingStyles.snake_case, examples: [] });
|
|
1472
|
+
}
|
|
1473
|
+
// --- Structure patterns: one entry per pattern type ---
|
|
1379
1474
|
const sortedStructures = [...structurePatterns.entries()].sort((a, b) => b[1] - a[1]);
|
|
1380
1475
|
if (sortedStructures.length > 0) {
|
|
1381
1476
|
patterns.push({
|
|
1382
|
-
type: 'structure',
|
|
1477
|
+
type: 'structure-summary',
|
|
1383
1478
|
value: sortedStructures.map(([t, c]) => `${t}(${c})`).join(', '),
|
|
1384
1479
|
count: sortedStructures.reduce((s, [, c]) => s + c, 0),
|
|
1385
1480
|
examples: sortedStructures.slice(0, 3).map(([t]) => t),
|
|
1386
1481
|
});
|
|
1387
1482
|
}
|
|
1388
|
-
|
|
1483
|
+
for (const [structType, count] of sortedStructures) {
|
|
1484
|
+
patterns.push({
|
|
1485
|
+
type: `structure-${structType}`,
|
|
1486
|
+
value: `${structType}: ${count} occurrences`,
|
|
1487
|
+
count,
|
|
1488
|
+
examples: [structType],
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
// --- API patterns: one entry per method/type ---
|
|
1389
1492
|
const sortedApi = [...apiPatterns.entries()].sort((a, b) => b[1] - a[1]);
|
|
1390
1493
|
if (sortedApi.length > 0) {
|
|
1391
1494
|
patterns.push({
|
|
1392
|
-
type: 'api-
|
|
1495
|
+
type: 'api-summary',
|
|
1393
1496
|
value: sortedApi.map(([t, c]) => `${t}(${c})`).join(', '),
|
|
1394
1497
|
count: sortedApi.reduce((s, [, c]) => s + c, 0),
|
|
1395
1498
|
examples: sortedApi.slice(0, 3).map(([t]) => t),
|
|
1396
1499
|
});
|
|
1397
1500
|
}
|
|
1501
|
+
for (const [apiType, count] of sortedApi) {
|
|
1502
|
+
patterns.push({
|
|
1503
|
+
type: `api-${apiType.toLowerCase()}`,
|
|
1504
|
+
value: `${apiType}: ${count} occurrences`,
|
|
1505
|
+
count,
|
|
1506
|
+
examples: [apiType],
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
// --- Function signature patterns ---
|
|
1510
|
+
if (functionSigs.length > 0) {
|
|
1511
|
+
// Prefix grouping: group functions by common prefixes (get*, set*, handle*, on*, is*, etc.)
|
|
1512
|
+
const prefixGroups = new Map();
|
|
1513
|
+
const prefixRegex = /^(get|set|handle|on|is|has|create|update|delete|find|fetch|load|save|init|check|validate|parse|format|build|render|compute|process|resolve|transform|convert|ensure|generate|register|dispatch|emit|notify|apply|reset|clear|remove|add|with|to|from)/i;
|
|
1514
|
+
for (const fn of functionSigs) {
|
|
1515
|
+
const match = fn.match(prefixRegex);
|
|
1516
|
+
const prefix = match ? match[1].toLowerCase() : '_other';
|
|
1517
|
+
if (!prefixGroups.has(prefix))
|
|
1518
|
+
prefixGroups.set(prefix, []);
|
|
1519
|
+
prefixGroups.get(prefix).push(fn);
|
|
1520
|
+
}
|
|
1521
|
+
for (const [prefix, fns] of [...prefixGroups.entries()].sort((a, b) => b[1].length - a[1].length).slice(0, 15)) {
|
|
1522
|
+
if (fns.length >= 2) {
|
|
1523
|
+
patterns.push({
|
|
1524
|
+
type: `function-prefix-${prefix}`,
|
|
1525
|
+
value: `${prefix}* functions: ${fns.length} (${fns.slice(0, 5).join(', ')}${fns.length > 5 ? ` +${fns.length - 5} more` : ''})`,
|
|
1526
|
+
count: fns.length,
|
|
1527
|
+
examples: fns.slice(0, 5),
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
// --- Async patterns (collected in single pass above) ---
|
|
1533
|
+
if (asyncCount + syncCount > 0) {
|
|
1534
|
+
patterns.push({
|
|
1535
|
+
type: 'async-style',
|
|
1536
|
+
value: `async:${asyncCount} sync:${syncCount} await-usage:${awaitCount} promise-catch:${promiseCatchCount}`,
|
|
1537
|
+
count: asyncCount + syncCount,
|
|
1538
|
+
examples: asyncCount > syncCount ? ['Async-first codebase'] : ['Sync-first codebase'],
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
// --- Decorator/annotation patterns (collected in single pass above) ---
|
|
1542
|
+
for (const [decName, count] of [...decoratorCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10)) {
|
|
1543
|
+
if (count >= 2) {
|
|
1544
|
+
patterns.push({
|
|
1545
|
+
type: 'decorator',
|
|
1546
|
+
value: `@${decName}: ${count} usages`,
|
|
1547
|
+
count,
|
|
1548
|
+
examples: [`@${decName}`],
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
// --- Type system patterns (collected in single pass above) ---
|
|
1553
|
+
if (interfaceCount > 0)
|
|
1554
|
+
patterns.push({ type: 'type-interface', value: `${interfaceCount} interfaces defined`, count: interfaceCount, examples: [] });
|
|
1555
|
+
if (typeAliasCount > 0)
|
|
1556
|
+
patterns.push({ type: 'type-alias', value: `${typeAliasCount} type aliases`, count: typeAliasCount, examples: [] });
|
|
1557
|
+
if (enumCount > 0)
|
|
1558
|
+
patterns.push({ type: 'type-enum', value: `${enumCount} enums`, count: enumCount, examples: [] });
|
|
1559
|
+
if (genericCount > 0)
|
|
1560
|
+
patterns.push({ type: 'type-generics', value: `${genericCount} generic declarations`, count: genericCount, examples: [] });
|
|
1561
|
+
// --- Testing patterns (collected in single pass above) ---
|
|
1562
|
+
if (describeCount + itCount + testCount > 0) {
|
|
1563
|
+
patterns.push({
|
|
1564
|
+
type: 'test-framework',
|
|
1565
|
+
value: `describe:${describeCount} it:${itCount} test:${testCount} expect:${expectCount} mocks:${mockCount}`,
|
|
1566
|
+
count: describeCount + itCount + testCount,
|
|
1567
|
+
examples: itCount > testCount ? ['it() style (BDD)'] : ['test() style'],
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
if (mockCount > 0) {
|
|
1571
|
+
patterns.push({ type: 'test-mocking', value: `${mockCount} mock/stub usages`, count: mockCount, examples: [] });
|
|
1572
|
+
}
|
|
1573
|
+
// --- Config/env patterns (collected in single pass above) ---
|
|
1574
|
+
if (envAccessCount > 0)
|
|
1575
|
+
patterns.push({ type: 'config-env', value: `process.env accessed ${envAccessCount} times`, count: envAccessCount, examples: [] });
|
|
1576
|
+
if (configImportCount > 0)
|
|
1577
|
+
patterns.push({ type: 'config-import', value: `${configImportCount} config-related imports`, count: configImportCount, examples: [] });
|
|
1578
|
+
// --- Logging patterns (collected in single pass above) ---
|
|
1579
|
+
for (const [logType, count] of [...logPatterns.entries()].sort((a, b) => b[1] - a[1])) {
|
|
1580
|
+
patterns.push({ type: `logging-${logType.replace(/\./g, '-')}`, value: `${logType}: ${count} usages`, count, examples: [logType] });
|
|
1581
|
+
}
|
|
1398
1582
|
return patterns;
|
|
1399
1583
|
}
|
|
1400
1584
|
export const hooksPretrain = {
|
|
@@ -1419,7 +1603,7 @@ export const hooksPretrain = {
|
|
|
1419
1603
|
: (typeof rawFileTypes === 'string' ? rawFileTypes : 'ts,js,py,md').split(',').map(e => e.trim());
|
|
1420
1604
|
const startTime = Date.now();
|
|
1421
1605
|
// Determine file limit by depth
|
|
1422
|
-
const fileLimit = depth === 'deep' ?
|
|
1606
|
+
const fileLimit = depth === 'deep' ? 500 : depth === 'shallow' ? 80 : 200;
|
|
1423
1607
|
const extensions = new Set(fileTypesArr);
|
|
1424
1608
|
// Phase 1: Retrieve - collect source files
|
|
1425
1609
|
const retrieveStart = Date.now();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moflo/cli",
|
|
3
|
-
"version": "4.8.
|
|
3
|
+
"version": "4.8.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",
|