moflo 4.6.11 → 4.7.1
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/helpers/hook-handler.cjs +35 -6
- package/.claude/settings.json +4 -4
- package/.claude/workflow-state.json +5 -0
- package/README.md +11 -1
- package/bin/hooks.mjs +519 -0
- package/bin/session-start-launcher.mjs +63 -0
- package/bin/setup-project.mjs +1 -1
- package/package.json +3 -2
- package/src/@claude-flow/cli/README.md +452 -7536
- package/src/@claude-flow/cli/dist/src/commands/doctor.js +1 -1
- package/src/@claude-flow/cli/dist/src/commands/embeddings.js +4 -4
- package/src/@claude-flow/cli/dist/src/commands/init.js +35 -8
- package/src/@claude-flow/cli/dist/src/commands/swarm.js +2 -2
- package/src/@claude-flow/cli/dist/src/init/claudemd-generator.js +316 -294
- package/src/@claude-flow/cli/dist/src/init/executor.js +461 -465
- package/src/@claude-flow/cli/dist/src/init/helpers-generator.d.ts +0 -36
- package/src/@claude-flow/cli/dist/src/init/helpers-generator.js +146 -1124
- package/src/@claude-flow/cli/dist/src/init/index.d.ts +1 -1
- package/src/@claude-flow/cli/dist/src/init/index.js +1 -1
- package/src/@claude-flow/cli/dist/src/init/mcp-generator.js +6 -3
- package/src/@claude-flow/cli/dist/src/init/moflo-init.js +108 -424
- package/src/@claude-flow/cli/dist/src/init/settings-generator.js +50 -120
- package/src/@claude-flow/cli/dist/src/init/types.d.ts +2 -0
- package/src/@claude-flow/cli/dist/src/init/types.js +2 -0
- package/src/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +275 -32
- package/src/@claude-flow/cli/dist/src/plugins/store/discovery.js +4 -204
- package/src/@claude-flow/cli/dist/src/plugins/tests/standalone-test.js +4 -4
- package/src/@claude-flow/cli/dist/src/runtime/headless.d.ts +3 -3
- package/src/@claude-flow/cli/dist/src/runtime/headless.js +31 -31
- package/src/@claude-flow/cli/dist/src/services/agentic-flow-bridge.d.ts +3 -3
- package/src/@claude-flow/cli/dist/src/services/agentic-flow-bridge.js +3 -1
- package/src/@claude-flow/cli/dist/src/services/headless-worker-executor.js +14 -0
- package/src/@claude-flow/cli/dist/src/services/workflow-gate.js +21 -1
- package/src/@claude-flow/cli/dist/src/transfer/store/tests/standalone-test.js +4 -4
- package/src/@claude-flow/cli/package.json +1 -1
|
@@ -189,87 +189,84 @@ function generateStatusLineConfig(_options) {
|
|
|
189
189
|
}
|
|
190
190
|
/**
|
|
191
191
|
* Generate hooks configuration
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
* working identically on Windows, macOS, and Linux.
|
|
192
|
+
* All hooks route through the npx flo CLI for consistent behavior.
|
|
193
|
+
* The CLI handles routing, gates, learning, and session management.
|
|
195
194
|
*/
|
|
196
195
|
function generateHooksConfig(config) {
|
|
197
196
|
const hooks = {};
|
|
198
|
-
//
|
|
199
|
-
// No shell-level error suppression needed (2>/dev/null || true breaks Windows).
|
|
200
|
-
// PreToolUse — validate commands and edits before execution
|
|
197
|
+
// PreToolUse — gates and validation before tool execution
|
|
201
198
|
if (config.preToolUse) {
|
|
202
199
|
hooks.PreToolUse = [
|
|
203
200
|
{
|
|
204
|
-
matcher: '
|
|
205
|
-
hooks: [
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
201
|
+
matcher: '^(Write|Edit|MultiEdit)$',
|
|
202
|
+
hooks: [{ type: 'command', command: 'npx flo hooks pre-edit', timeout: 5000 }],
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
matcher: '^(Glob|Grep)$',
|
|
206
|
+
hooks: [{ type: 'command', command: 'npx flo gate check-before-scan', timeout: 3000 }],
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
matcher: '^Read$',
|
|
210
|
+
hooks: [{ type: 'command', command: 'npx flo gate check-before-read', timeout: 3000 }],
|
|
212
211
|
},
|
|
213
212
|
{
|
|
214
|
-
matcher: '
|
|
213
|
+
matcher: '^Task$',
|
|
215
214
|
hooks: [
|
|
216
|
-
{
|
|
217
|
-
|
|
218
|
-
command: hookHandlerCmd('pre-edit'),
|
|
219
|
-
timeout: config.timeout,
|
|
220
|
-
},
|
|
215
|
+
{ type: 'command', command: 'npx flo gate check-before-agent', timeout: 3000 },
|
|
216
|
+
{ type: 'command', command: 'npx flo hooks pre-task', timeout: 5000 },
|
|
221
217
|
],
|
|
222
218
|
},
|
|
219
|
+
{
|
|
220
|
+
matcher: '^Bash$',
|
|
221
|
+
hooks: [{ type: 'command', command: 'npx flo gate check-dangerous-command', timeout: 2000 }],
|
|
222
|
+
},
|
|
223
223
|
];
|
|
224
224
|
}
|
|
225
|
-
// PostToolUse — record
|
|
225
|
+
// PostToolUse — record outcomes for learning
|
|
226
226
|
if (config.postToolUse) {
|
|
227
227
|
hooks.PostToolUse = [
|
|
228
228
|
{
|
|
229
|
-
matcher: 'Write|Edit|MultiEdit',
|
|
230
|
-
hooks: [
|
|
231
|
-
{
|
|
232
|
-
type: 'command',
|
|
233
|
-
command: hookHandlerCmd('post-edit'),
|
|
234
|
-
timeout: 10000,
|
|
235
|
-
},
|
|
236
|
-
],
|
|
229
|
+
matcher: '^(Write|Edit|MultiEdit)$',
|
|
230
|
+
hooks: [{ type: 'command', command: 'npx flo hooks post-edit', timeout: 5000 }],
|
|
237
231
|
},
|
|
238
232
|
{
|
|
239
|
-
matcher: '
|
|
240
|
-
hooks: [
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
233
|
+
matcher: '^Task$',
|
|
234
|
+
hooks: [{ type: 'command', command: 'npx flo hooks post-task', timeout: 5000 }],
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
matcher: '^TaskCreate$',
|
|
238
|
+
hooks: [{ type: 'command', command: 'npx flo gate record-task-created', timeout: 2000 }],
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
matcher: '^Bash$',
|
|
242
|
+
hooks: [{ type: 'command', command: 'npx flo gate check-bash-memory', timeout: 2000 }],
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
matcher: '^mcp__claude-flow__memory_(search|retrieve)$',
|
|
246
|
+
hooks: [{ type: 'command', command: 'npx flo gate record-memory-searched', timeout: 2000 }],
|
|
247
247
|
},
|
|
248
248
|
];
|
|
249
249
|
}
|
|
250
|
-
// UserPromptSubmit — intelligent task routing
|
|
250
|
+
// UserPromptSubmit — gate reminders + intelligent task routing
|
|
251
251
|
if (config.userPromptSubmit) {
|
|
252
252
|
hooks.UserPromptSubmit = [
|
|
253
253
|
{
|
|
254
254
|
hooks: [
|
|
255
|
-
{
|
|
256
|
-
|
|
257
|
-
command: hookHandlerCmd('route'),
|
|
258
|
-
timeout: 10000,
|
|
259
|
-
},
|
|
255
|
+
{ type: 'command', command: 'npx flo gate prompt-reminder', timeout: 2000 },
|
|
256
|
+
{ type: 'command', command: 'npx flo hooks route', timeout: 5000 },
|
|
260
257
|
],
|
|
261
258
|
},
|
|
262
259
|
];
|
|
263
260
|
}
|
|
264
|
-
// SessionStart —
|
|
261
|
+
// SessionStart — launch daemon, indexers, pretrain via session-start-launcher
|
|
265
262
|
if (config.sessionStart) {
|
|
266
263
|
hooks.SessionStart = [
|
|
267
264
|
{
|
|
268
265
|
hooks: [
|
|
269
266
|
{
|
|
270
267
|
type: 'command',
|
|
271
|
-
command:
|
|
272
|
-
timeout:
|
|
268
|
+
command: 'node "$CLAUDE_PROJECT_DIR/.claude/scripts/session-start-launcher.mjs"',
|
|
269
|
+
timeout: 3000,
|
|
273
270
|
},
|
|
274
271
|
{
|
|
275
272
|
type: 'command',
|
|
@@ -280,100 +277,33 @@ function generateHooksConfig(config) {
|
|
|
280
277
|
},
|
|
281
278
|
];
|
|
282
279
|
}
|
|
283
|
-
//
|
|
284
|
-
if (config.sessionStart) {
|
|
285
|
-
hooks.SessionEnd = [
|
|
286
|
-
{
|
|
287
|
-
hooks: [
|
|
288
|
-
{
|
|
289
|
-
type: 'command',
|
|
290
|
-
command: hookHandlerCmd('session-end'),
|
|
291
|
-
timeout: 10000,
|
|
292
|
-
},
|
|
293
|
-
],
|
|
294
|
-
},
|
|
295
|
-
];
|
|
296
|
-
}
|
|
297
|
-
// Stop — sync auto memory on exit
|
|
280
|
+
// Stop — persist session + sync auto memory
|
|
298
281
|
if (config.stop) {
|
|
299
282
|
hooks.Stop = [
|
|
300
283
|
{
|
|
301
284
|
hooks: [
|
|
302
|
-
{
|
|
303
|
-
|
|
304
|
-
command: autoMemoryCmd('sync'),
|
|
305
|
-
timeout: 10000,
|
|
306
|
-
},
|
|
285
|
+
{ type: 'command', command: 'npx flo hooks session-end', timeout: 5000 },
|
|
286
|
+
{ type: 'command', command: autoMemoryCmd('sync'), timeout: 10000 },
|
|
307
287
|
],
|
|
308
288
|
},
|
|
309
289
|
];
|
|
310
290
|
}
|
|
311
|
-
// PreCompact —
|
|
291
|
+
// PreCompact — guidance before context window compaction
|
|
312
292
|
if (config.preCompact) {
|
|
313
293
|
hooks.PreCompact = [
|
|
314
294
|
{
|
|
315
|
-
|
|
316
|
-
hooks: [
|
|
317
|
-
{
|
|
318
|
-
type: 'command',
|
|
319
|
-
command: hookHandlerCmd('compact-manual'),
|
|
320
|
-
},
|
|
321
|
-
{
|
|
322
|
-
type: 'command',
|
|
323
|
-
command: hookHandlerCmd('session-end'),
|
|
324
|
-
timeout: 5000,
|
|
325
|
-
},
|
|
326
|
-
],
|
|
327
|
-
},
|
|
328
|
-
{
|
|
329
|
-
matcher: 'auto',
|
|
330
|
-
hooks: [
|
|
331
|
-
{
|
|
332
|
-
type: 'command',
|
|
333
|
-
command: hookHandlerCmd('compact-auto'),
|
|
334
|
-
},
|
|
335
|
-
{
|
|
336
|
-
type: 'command',
|
|
337
|
-
command: hookHandlerCmd('session-end'),
|
|
338
|
-
timeout: 6000,
|
|
339
|
-
},
|
|
340
|
-
],
|
|
295
|
+
hooks: [{ type: 'command', command: 'npx flo gate compact-guidance', timeout: 3000 }],
|
|
341
296
|
},
|
|
342
297
|
];
|
|
343
298
|
}
|
|
344
|
-
//
|
|
345
|
-
hooks.SubagentStart = [
|
|
346
|
-
{
|
|
347
|
-
hooks: [
|
|
348
|
-
{
|
|
349
|
-
type: 'command',
|
|
350
|
-
command: hookHandlerCmd('status'),
|
|
351
|
-
timeout: 3000,
|
|
352
|
-
},
|
|
353
|
-
],
|
|
354
|
-
},
|
|
355
|
-
];
|
|
356
|
-
// SubagentStop — track agent completion for metrics
|
|
357
|
-
// NOTE: The valid event is "SubagentStop" (not "SubagentEnd")
|
|
358
|
-
hooks.SubagentStop = [
|
|
359
|
-
{
|
|
360
|
-
hooks: [
|
|
361
|
-
{
|
|
362
|
-
type: 'command',
|
|
363
|
-
command: hookHandlerCmd('post-task'),
|
|
364
|
-
timeout: 5000,
|
|
365
|
-
},
|
|
366
|
-
],
|
|
367
|
-
},
|
|
368
|
-
];
|
|
369
|
-
// Notification — capture Claude Code notifications for logging
|
|
299
|
+
// Notification — capture notifications for logging
|
|
370
300
|
if (config.notification) {
|
|
371
301
|
hooks.Notification = [
|
|
372
302
|
{
|
|
373
303
|
hooks: [
|
|
374
304
|
{
|
|
375
305
|
type: 'command',
|
|
376
|
-
command:
|
|
306
|
+
command: 'npx flo hooks notification',
|
|
377
307
|
timeout: 3000,
|
|
378
308
|
},
|
|
379
309
|
],
|
|
@@ -156,6 +156,8 @@ export interface MCPConfig {
|
|
|
156
156
|
autoStart: boolean;
|
|
157
157
|
/** Server port */
|
|
158
158
|
port: number;
|
|
159
|
+
/** Defer tool schema loading — schemas loaded on demand via ToolSearch */
|
|
160
|
+
toolDefer: boolean;
|
|
159
161
|
}
|
|
160
162
|
/**
|
|
161
163
|
* Runtime configuration (.claude-flow/)
|
|
@@ -118,6 +118,7 @@ export const DEFAULT_INIT_OPTIONS = {
|
|
|
118
118
|
flowNexus: false,
|
|
119
119
|
autoStart: false,
|
|
120
120
|
port: 3000,
|
|
121
|
+
toolDefer: true,
|
|
121
122
|
},
|
|
122
123
|
runtime: {
|
|
123
124
|
topology: 'hierarchical-mesh',
|
|
@@ -245,6 +246,7 @@ export const FULL_INIT_OPTIONS = {
|
|
|
245
246
|
flowNexus: true,
|
|
246
247
|
autoStart: false,
|
|
247
248
|
port: 3000,
|
|
249
|
+
toolDefer: true,
|
|
248
250
|
},
|
|
249
251
|
embeddings: {
|
|
250
252
|
enabled: true,
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Hooks MCP Tools
|
|
3
3
|
* Provides intelligent hooks functionality via MCP protocol
|
|
4
4
|
*/
|
|
5
|
-
import { mkdirSync, writeFileSync, existsSync, readFileSync, statSync } from 'fs';
|
|
6
|
-
import { dirname, join, resolve } from 'path';
|
|
5
|
+
import { mkdirSync, writeFileSync, existsSync, readFileSync, statSync, readdirSync } from 'fs';
|
|
6
|
+
import { dirname, join, resolve, extname } from 'path';
|
|
7
7
|
// Real vector search functions - lazy loaded to avoid circular imports
|
|
8
8
|
let searchEntriesFn = null;
|
|
9
9
|
async function getRealSearchFunction() {
|
|
@@ -1107,21 +1107,9 @@ export const hooksPostTask = {
|
|
|
1107
1107
|
}
|
|
1108
1108
|
catch { /* non-critical */ }
|
|
1109
1109
|
}
|
|
1110
|
-
//
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
const storeFn = await getRealStoreFunction();
|
|
1114
|
-
if (storeFn) {
|
|
1115
|
-
await storeFn({
|
|
1116
|
-
key: `routing-decision:${taskId}`,
|
|
1117
|
-
namespace: 'patterns',
|
|
1118
|
-
value: JSON.stringify({ task: taskText, agent, success, quality, keywords: outcomeKeywords }),
|
|
1119
|
-
tags: ['routing-decision'],
|
|
1120
|
-
});
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
catch { /* non-critical */ }
|
|
1124
|
-
}
|
|
1110
|
+
// Routing learnings are persisted via the file-based routing-outcomes.json above.
|
|
1111
|
+
// No redundant write to the memory DB — the JSON file is the authoritative source
|
|
1112
|
+
// for the routing loop (loadRoutingOutcomes → learnedPatternsFromOutcomes → route).
|
|
1125
1113
|
const duration = Date.now() - startTime;
|
|
1126
1114
|
return {
|
|
1127
1115
|
taskId,
|
|
@@ -1204,6 +1192,209 @@ export const hooksExplain = {
|
|
|
1204
1192
|
},
|
|
1205
1193
|
};
|
|
1206
1194
|
// Pretrain hook - repository analysis for intelligence bootstrap
|
|
1195
|
+
/** Recursively collect files matching given extensions up to a limit */
|
|
1196
|
+
function collectFiles(dir, extensions, limit, collected = []) {
|
|
1197
|
+
if (collected.length >= limit)
|
|
1198
|
+
return collected;
|
|
1199
|
+
let entries;
|
|
1200
|
+
try {
|
|
1201
|
+
entries = readdirSync(dir);
|
|
1202
|
+
}
|
|
1203
|
+
catch {
|
|
1204
|
+
return collected;
|
|
1205
|
+
}
|
|
1206
|
+
for (const entry of entries) {
|
|
1207
|
+
if (collected.length >= limit)
|
|
1208
|
+
break;
|
|
1209
|
+
const fullPath = join(dir, entry);
|
|
1210
|
+
// Skip common non-source directories
|
|
1211
|
+
if (entry === 'node_modules' || entry === '.git' || entry === 'dist' || entry === 'build' || entry === '.next' || entry === 'coverage')
|
|
1212
|
+
continue;
|
|
1213
|
+
try {
|
|
1214
|
+
const stat = statSync(fullPath);
|
|
1215
|
+
if (stat.isDirectory()) {
|
|
1216
|
+
collectFiles(fullPath, extensions, limit, collected);
|
|
1217
|
+
}
|
|
1218
|
+
else if (stat.isFile() && extensions.has(extname(entry).slice(1))) {
|
|
1219
|
+
collected.push(fullPath);
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
catch {
|
|
1223
|
+
// skip unreadable
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
return collected;
|
|
1227
|
+
}
|
|
1228
|
+
/** Simple hash for dedup */
|
|
1229
|
+
function simpleHash(str) {
|
|
1230
|
+
let h = 0;
|
|
1231
|
+
for (let i = 0; i < str.length; i++) {
|
|
1232
|
+
h = ((h << 5) - h + str.charCodeAt(i)) | 0;
|
|
1233
|
+
}
|
|
1234
|
+
return Math.abs(h).toString(36);
|
|
1235
|
+
}
|
|
1236
|
+
/** Extract code patterns from file contents */
|
|
1237
|
+
function extractPatterns(files) {
|
|
1238
|
+
const importCounts = new Map();
|
|
1239
|
+
const exportPatterns = { default: 0, named: 0 };
|
|
1240
|
+
const errorPatterns = new Map();
|
|
1241
|
+
const namingStyles = { camelCase: 0, snake_case: 0, PascalCase: 0 };
|
|
1242
|
+
const structurePatterns = new Map();
|
|
1243
|
+
const apiPatterns = new Map();
|
|
1244
|
+
const functionSigs = [];
|
|
1245
|
+
for (const file of files) {
|
|
1246
|
+
let content;
|
|
1247
|
+
try {
|
|
1248
|
+
content = readFileSync(file, 'utf-8');
|
|
1249
|
+
}
|
|
1250
|
+
catch {
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
const lines = content.split('\n');
|
|
1254
|
+
for (const line of lines) {
|
|
1255
|
+
const trimmed = line.trim();
|
|
1256
|
+
// Import patterns
|
|
1257
|
+
const importMatch = trimmed.match(/^import\s+.*?from\s+['"]([^'"]+)['"]/);
|
|
1258
|
+
if (importMatch) {
|
|
1259
|
+
const mod = importMatch[1].startsWith('.') ? '<relative>' : importMatch[1].split('/')[0];
|
|
1260
|
+
importCounts.set(mod, (importCounts.get(mod) || 0) + 1);
|
|
1261
|
+
}
|
|
1262
|
+
const requireMatch = trimmed.match(/require\(['"]([^'"]+)['"]\)/);
|
|
1263
|
+
if (requireMatch) {
|
|
1264
|
+
const mod = requireMatch[1].startsWith('.') ? '<relative>' : requireMatch[1].split('/')[0];
|
|
1265
|
+
importCounts.set(mod, (importCounts.get(mod) || 0) + 1);
|
|
1266
|
+
}
|
|
1267
|
+
// Python imports
|
|
1268
|
+
const pyImport = trimmed.match(/^(?:from\s+(\S+)\s+import|import\s+(\S+))/);
|
|
1269
|
+
if (pyImport) {
|
|
1270
|
+
const mod = (pyImport[1] || pyImport[2]).split('.')[0];
|
|
1271
|
+
importCounts.set(mod, (importCounts.get(mod) || 0) + 1);
|
|
1272
|
+
}
|
|
1273
|
+
// Export patterns
|
|
1274
|
+
if (/^export\s+default\b/.test(trimmed))
|
|
1275
|
+
exportPatterns.default++;
|
|
1276
|
+
else if (/^export\s+(?:const|function|class|interface|type|enum)\b/.test(trimmed))
|
|
1277
|
+
exportPatterns.named++;
|
|
1278
|
+
// Error handling patterns
|
|
1279
|
+
if (/\bcatch\s*\(/.test(trimmed)) {
|
|
1280
|
+
const errType = trimmed.match(/catch\s*\(\s*(\w+)/)?.[1] || 'generic';
|
|
1281
|
+
errorPatterns.set(errType, (errorPatterns.get(errType) || 0) + 1);
|
|
1282
|
+
}
|
|
1283
|
+
if (/throw\s+new\s+(\w+)/.test(trimmed)) {
|
|
1284
|
+
const errClass = trimmed.match(/throw\s+new\s+(\w+)/)?.[1] || 'Error';
|
|
1285
|
+
errorPatterns.set(`throw:${errClass}`, (errorPatterns.get(`throw:${errClass}`) || 0) + 1);
|
|
1286
|
+
}
|
|
1287
|
+
// Function signatures (collect first 50)
|
|
1288
|
+
if (functionSigs.length < 50) {
|
|
1289
|
+
const fnMatch = trimmed.match(/^(?:export\s+)?(?:async\s+)?function\s+(\w+)/);
|
|
1290
|
+
if (fnMatch)
|
|
1291
|
+
functionSigs.push(fnMatch[1]);
|
|
1292
|
+
const arrowMatch = trimmed.match(/^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\(/);
|
|
1293
|
+
if (arrowMatch)
|
|
1294
|
+
functionSigs.push(arrowMatch[1]);
|
|
1295
|
+
}
|
|
1296
|
+
// Naming conventions from identifiers
|
|
1297
|
+
const identifiers = trimmed.match(/\b[a-zA-Z_]\w{2,}\b/g) || [];
|
|
1298
|
+
for (const id of identifiers.slice(0, 5)) {
|
|
1299
|
+
if (/^[a-z][a-zA-Z0-9]*$/.test(id) && /[A-Z]/.test(id))
|
|
1300
|
+
namingStyles.camelCase++;
|
|
1301
|
+
else if (/_/.test(id) && id === id.toLowerCase())
|
|
1302
|
+
namingStyles.snake_case++;
|
|
1303
|
+
else if (/^[A-Z][a-zA-Z0-9]*$/.test(id) && /[a-z]/.test(id))
|
|
1304
|
+
namingStyles.PascalCase++;
|
|
1305
|
+
}
|
|
1306
|
+
// API/Route patterns
|
|
1307
|
+
const routeMatch = trimmed.match(/\.(get|post|put|patch|delete|use)\s*\(\s*['"\/]/i);
|
|
1308
|
+
if (routeMatch) {
|
|
1309
|
+
const method = routeMatch[1].toUpperCase();
|
|
1310
|
+
apiPatterns.set(method, (apiPatterns.get(method) || 0) + 1);
|
|
1311
|
+
}
|
|
1312
|
+
if (/router\.|app\.|@(Get|Post|Put|Delete|Patch)\b/.test(trimmed)) {
|
|
1313
|
+
apiPatterns.set('route-definition', (apiPatterns.get('route-definition') || 0) + 1);
|
|
1314
|
+
}
|
|
1315
|
+
if (/middleware|\.use\(/.test(trimmed)) {
|
|
1316
|
+
apiPatterns.set('middleware', (apiPatterns.get('middleware') || 0) + 1);
|
|
1317
|
+
}
|
|
1318
|
+
// Structure patterns
|
|
1319
|
+
if (/return\s*\{\s*success/.test(trimmed)) {
|
|
1320
|
+
structurePatterns.set('return-success-object', (structurePatterns.get('return-success-object') || 0) + 1);
|
|
1321
|
+
}
|
|
1322
|
+
if (/class\s+\w+Service\b/.test(trimmed)) {
|
|
1323
|
+
structurePatterns.set('service-class', (structurePatterns.get('service-class') || 0) + 1);
|
|
1324
|
+
}
|
|
1325
|
+
if (/class\s+\w+Controller\b/.test(trimmed)) {
|
|
1326
|
+
structurePatterns.set('controller-class', (structurePatterns.get('controller-class') || 0) + 1);
|
|
1327
|
+
}
|
|
1328
|
+
if (/class\s+\w+Repository\b/.test(trimmed)) {
|
|
1329
|
+
structurePatterns.set('repository-class', (structurePatterns.get('repository-class') || 0) + 1);
|
|
1330
|
+
}
|
|
1331
|
+
if (/interface\s+\w+/.test(trimmed)) {
|
|
1332
|
+
structurePatterns.set('typed-interface', (structurePatterns.get('typed-interface') || 0) + 1);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
const patterns = [];
|
|
1337
|
+
// Top imports by frequency
|
|
1338
|
+
const sortedImports = [...importCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 15);
|
|
1339
|
+
if (sortedImports.length > 0) {
|
|
1340
|
+
patterns.push({
|
|
1341
|
+
type: 'import',
|
|
1342
|
+
value: `Top modules: ${sortedImports.map(([m, c]) => `${m}(${c})`).join(', ')}`,
|
|
1343
|
+
count: sortedImports.reduce((s, [, c]) => s + c, 0),
|
|
1344
|
+
examples: sortedImports.slice(0, 5).map(([m]) => m),
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
// Export patterns
|
|
1348
|
+
if (exportPatterns.default + exportPatterns.named > 0) {
|
|
1349
|
+
patterns.push({
|
|
1350
|
+
type: 'export',
|
|
1351
|
+
value: `default:${exportPatterns.default} named:${exportPatterns.named}`,
|
|
1352
|
+
count: exportPatterns.default + exportPatterns.named,
|
|
1353
|
+
examples: exportPatterns.named > exportPatterns.default
|
|
1354
|
+
? ['Named exports preferred']
|
|
1355
|
+
: ['Default exports preferred'],
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
// Error handling
|
|
1359
|
+
const sortedErrors = [...errorPatterns.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
1360
|
+
if (sortedErrors.length > 0) {
|
|
1361
|
+
patterns.push({
|
|
1362
|
+
type: 'error-handling',
|
|
1363
|
+
value: sortedErrors.map(([t, c]) => `${t}(${c})`).join(', '),
|
|
1364
|
+
count: sortedErrors.reduce((s, [, c]) => s + c, 0),
|
|
1365
|
+
examples: sortedErrors.slice(0, 3).map(([t]) => t),
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
// Naming conventions
|
|
1369
|
+
const dominant = namingStyles.camelCase >= namingStyles.snake_case ? 'camelCase' : 'snake_case';
|
|
1370
|
+
patterns.push({
|
|
1371
|
+
type: 'naming',
|
|
1372
|
+
value: `camelCase:${namingStyles.camelCase} snake_case:${namingStyles.snake_case} PascalCase:${namingStyles.PascalCase} dominant:${dominant}`,
|
|
1373
|
+
count: namingStyles.camelCase + namingStyles.snake_case + namingStyles.PascalCase,
|
|
1374
|
+
examples: functionSigs.slice(0, 5),
|
|
1375
|
+
});
|
|
1376
|
+
// Structure patterns
|
|
1377
|
+
const sortedStructures = [...structurePatterns.entries()].sort((a, b) => b[1] - a[1]);
|
|
1378
|
+
if (sortedStructures.length > 0) {
|
|
1379
|
+
patterns.push({
|
|
1380
|
+
type: 'structure',
|
|
1381
|
+
value: sortedStructures.map(([t, c]) => `${t}(${c})`).join(', '),
|
|
1382
|
+
count: sortedStructures.reduce((s, [, c]) => s + c, 0),
|
|
1383
|
+
examples: sortedStructures.slice(0, 3).map(([t]) => t),
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
// API patterns
|
|
1387
|
+
const sortedApi = [...apiPatterns.entries()].sort((a, b) => b[1] - a[1]);
|
|
1388
|
+
if (sortedApi.length > 0) {
|
|
1389
|
+
patterns.push({
|
|
1390
|
+
type: 'api-pattern',
|
|
1391
|
+
value: sortedApi.map(([t, c]) => `${t}(${c})`).join(', '),
|
|
1392
|
+
count: sortedApi.reduce((s, [, c]) => s + c, 0),
|
|
1393
|
+
examples: sortedApi.slice(0, 3).map(([t]) => t),
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
return patterns;
|
|
1397
|
+
}
|
|
1207
1398
|
export const hooksPretrain = {
|
|
1208
1399
|
name: 'hooks_pretrain',
|
|
1209
1400
|
description: 'Analyze repository to bootstrap intelligence (4-step pipeline)',
|
|
@@ -1212,32 +1403,84 @@ export const hooksPretrain = {
|
|
|
1212
1403
|
properties: {
|
|
1213
1404
|
path: { type: 'string', description: 'Repository path' },
|
|
1214
1405
|
depth: { type: 'string', description: 'Analysis depth (shallow, medium, deep)' },
|
|
1406
|
+
fileTypes: { type: 'string', description: 'Comma-separated file extensions to scan (default: ts,js,py,md)' },
|
|
1215
1407
|
skipCache: { type: 'boolean', description: 'Skip cached analysis' },
|
|
1216
1408
|
},
|
|
1217
1409
|
},
|
|
1218
1410
|
handler: async (params) => {
|
|
1219
|
-
const
|
|
1411
|
+
const repoPath = resolve(params.path || '.');
|
|
1220
1412
|
const depth = params.depth || 'medium';
|
|
1413
|
+
const fileTypesStr = params.fileTypes || 'ts,js,py,md';
|
|
1221
1414
|
const startTime = Date.now();
|
|
1222
|
-
//
|
|
1223
|
-
const
|
|
1415
|
+
// Determine file limit by depth
|
|
1416
|
+
const fileLimit = depth === 'deep' ? 100 : depth === 'shallow' ? 30 : 60;
|
|
1417
|
+
const extensions = new Set(fileTypesStr.split(',').map(e => e.trim()));
|
|
1418
|
+
// Phase 1: Retrieve - collect source files
|
|
1419
|
+
const retrieveStart = Date.now();
|
|
1420
|
+
const files = collectFiles(repoPath, extensions, fileLimit);
|
|
1421
|
+
const retrieveDuration = Date.now() - retrieveStart;
|
|
1422
|
+
// Phase 2: Judge - extract patterns from files
|
|
1423
|
+
const judgeStart = Date.now();
|
|
1424
|
+
const patterns = extractPatterns(files);
|
|
1425
|
+
const judgeDuration = Date.now() - judgeStart;
|
|
1426
|
+
// Phase 3: Distill - store patterns in memory DB
|
|
1427
|
+
const distillStart = Date.now();
|
|
1428
|
+
let patternsStored = 0;
|
|
1429
|
+
let storageErrors = 0;
|
|
1430
|
+
const storeFn = await getRealStoreFunction();
|
|
1431
|
+
if (storeFn) {
|
|
1432
|
+
for (const pattern of patterns) {
|
|
1433
|
+
const hash = simpleHash(`${pattern.type}:${pattern.value}`);
|
|
1434
|
+
try {
|
|
1435
|
+
await storeFn({
|
|
1436
|
+
key: `pattern-${pattern.type}-${hash}`,
|
|
1437
|
+
value: JSON.stringify({
|
|
1438
|
+
type: pattern.type,
|
|
1439
|
+
value: pattern.value,
|
|
1440
|
+
count: pattern.count,
|
|
1441
|
+
examples: pattern.examples,
|
|
1442
|
+
filesAnalyzed: files.length,
|
|
1443
|
+
extractedAt: new Date().toISOString(),
|
|
1444
|
+
}),
|
|
1445
|
+
namespace: 'patterns',
|
|
1446
|
+
generateEmbeddingFlag: true,
|
|
1447
|
+
tags: [pattern.type, 'pretrain', `depth-${depth}`],
|
|
1448
|
+
});
|
|
1449
|
+
patternsStored++;
|
|
1450
|
+
}
|
|
1451
|
+
catch {
|
|
1452
|
+
storageErrors++;
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
const distillDuration = Date.now() - distillStart;
|
|
1457
|
+
// Phase 4: Consolidate - summary
|
|
1458
|
+
const consolidateStart = Date.now();
|
|
1459
|
+
const consolidateDuration = Date.now() - consolidateStart;
|
|
1224
1460
|
return {
|
|
1225
|
-
path,
|
|
1461
|
+
path: repoPath,
|
|
1226
1462
|
depth,
|
|
1463
|
+
fileTypes: fileTypesStr,
|
|
1227
1464
|
stats: {
|
|
1228
|
-
filesAnalyzed:
|
|
1229
|
-
patternsExtracted:
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1465
|
+
filesAnalyzed: files.length,
|
|
1466
|
+
patternsExtracted: patterns.length,
|
|
1467
|
+
patternsStored,
|
|
1468
|
+
storageErrors,
|
|
1469
|
+
patternTypes: patterns.map(p => p.type),
|
|
1233
1470
|
},
|
|
1471
|
+
patterns: patterns.map(p => ({
|
|
1472
|
+
type: p.type,
|
|
1473
|
+
summary: p.value,
|
|
1474
|
+
count: p.count,
|
|
1475
|
+
examples: p.examples,
|
|
1476
|
+
})),
|
|
1234
1477
|
pipeline: {
|
|
1235
|
-
retrieve: { status: 'completed',
|
|
1236
|
-
judge: { status: 'completed',
|
|
1237
|
-
distill: { status: 'completed',
|
|
1238
|
-
consolidate: { status: 'completed', duration:
|
|
1478
|
+
retrieve: { status: 'completed', filesFound: files.length, duration: retrieveDuration },
|
|
1479
|
+
judge: { status: 'completed', patternsFound: patterns.length, duration: judgeDuration },
|
|
1480
|
+
distill: { status: storeFn ? 'completed' : 'skipped', stored: patternsStored, errors: storageErrors, duration: distillDuration },
|
|
1481
|
+
consolidate: { status: 'completed', duration: consolidateDuration },
|
|
1239
1482
|
},
|
|
1240
|
-
duration: Date.now() - startTime
|
|
1483
|
+
duration: Date.now() - startTime,
|
|
1241
1484
|
};
|
|
1242
1485
|
},
|
|
1243
1486
|
};
|
|
@@ -1993,7 +2236,7 @@ export const hooksPatternStore = {
|
|
|
1993
2236
|
storeResult = await storeFn({
|
|
1994
2237
|
key: patternId,
|
|
1995
2238
|
value: JSON.stringify({ pattern, type, confidence, metadata, timestamp }),
|
|
1996
|
-
namespace: '
|
|
2239
|
+
namespace: 'patterns',
|
|
1997
2240
|
generateEmbeddingFlag: true,
|
|
1998
2241
|
tags: [type, `confidence-${Math.round(confidence * 100)}`, 'reasoning-pattern'],
|
|
1999
2242
|
});
|