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.
Files changed (35) hide show
  1. package/.claude/helpers/hook-handler.cjs +35 -6
  2. package/.claude/settings.json +4 -4
  3. package/.claude/workflow-state.json +5 -0
  4. package/README.md +11 -1
  5. package/bin/hooks.mjs +519 -0
  6. package/bin/session-start-launcher.mjs +63 -0
  7. package/bin/setup-project.mjs +1 -1
  8. package/package.json +3 -2
  9. package/src/@claude-flow/cli/README.md +452 -7536
  10. package/src/@claude-flow/cli/dist/src/commands/doctor.js +1 -1
  11. package/src/@claude-flow/cli/dist/src/commands/embeddings.js +4 -4
  12. package/src/@claude-flow/cli/dist/src/commands/init.js +35 -8
  13. package/src/@claude-flow/cli/dist/src/commands/swarm.js +2 -2
  14. package/src/@claude-flow/cli/dist/src/init/claudemd-generator.js +316 -294
  15. package/src/@claude-flow/cli/dist/src/init/executor.js +461 -465
  16. package/src/@claude-flow/cli/dist/src/init/helpers-generator.d.ts +0 -36
  17. package/src/@claude-flow/cli/dist/src/init/helpers-generator.js +146 -1124
  18. package/src/@claude-flow/cli/dist/src/init/index.d.ts +1 -1
  19. package/src/@claude-flow/cli/dist/src/init/index.js +1 -1
  20. package/src/@claude-flow/cli/dist/src/init/mcp-generator.js +6 -3
  21. package/src/@claude-flow/cli/dist/src/init/moflo-init.js +108 -424
  22. package/src/@claude-flow/cli/dist/src/init/settings-generator.js +50 -120
  23. package/src/@claude-flow/cli/dist/src/init/types.d.ts +2 -0
  24. package/src/@claude-flow/cli/dist/src/init/types.js +2 -0
  25. package/src/@claude-flow/cli/dist/src/mcp-tools/hooks-tools.js +275 -32
  26. package/src/@claude-flow/cli/dist/src/plugins/store/discovery.js +4 -204
  27. package/src/@claude-flow/cli/dist/src/plugins/tests/standalone-test.js +4 -4
  28. package/src/@claude-flow/cli/dist/src/runtime/headless.d.ts +3 -3
  29. package/src/@claude-flow/cli/dist/src/runtime/headless.js +31 -31
  30. package/src/@claude-flow/cli/dist/src/services/agentic-flow-bridge.d.ts +3 -3
  31. package/src/@claude-flow/cli/dist/src/services/agentic-flow-bridge.js +3 -1
  32. package/src/@claude-flow/cli/dist/src/services/headless-worker-executor.js +14 -0
  33. package/src/@claude-flow/cli/dist/src/services/workflow-gate.js +21 -1
  34. package/src/@claude-flow/cli/dist/src/transfer/store/tests/standalone-test.js +4 -4
  35. 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
- * Uses local hook-handler.cjs for cross-platform compatibility.
193
- * All hooks invoke scripts directly via `node <script> <subcommand>`,
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
- // Node.js scripts handle errors internally via try/catch.
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: 'Bash',
205
- hooks: [
206
- {
207
- type: 'command',
208
- command: hookHandlerCmd('pre-bash'),
209
- timeout: config.timeout,
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: 'Write|Edit|MultiEdit',
213
+ matcher: '^Task$',
215
214
  hooks: [
216
- {
217
- type: 'command',
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 edits and commands for session metrics / learning
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: 'Bash',
240
- hooks: [
241
- {
242
- type: 'command',
243
- command: hookHandlerCmd('post-bash'),
244
- timeout: config.timeout,
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
- type: 'command',
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 — restore session state + import auto memory
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: hookHandlerCmd('session-restore'),
272
- timeout: 15000,
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
- // SessionEnd — persist session state
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
- type: 'command',
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 — preserve context before compaction
291
+ // PreCompact — guidance before context window compaction
312
292
  if (config.preCompact) {
313
293
  hooks.PreCompact = [
314
294
  {
315
- matcher: 'manual',
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
- // SubagentStartstatus update when a sub-agent is spawned
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
+ // Notificationcapture 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: hookHandlerCmd('notify'),
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
- // Optionally store in memory DB for cross-session vector retrieval
1111
- if (params.storeDecisions && taskText && agent) {
1112
- try {
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 path = params.path || '.';
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
- // Scale analysis results by depth level
1223
- const multiplier = depth === 'deep' ? 3 : depth === 'shallow' ? 1 : 2;
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: 42 * multiplier,
1229
- patternsExtracted: 15 * multiplier,
1230
- strategiesLearned: 8 * multiplier,
1231
- trajectoriesEvaluated: 23 * multiplier,
1232
- contradictionsResolved: 3,
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', duration: 120 * multiplier },
1236
- judge: { status: 'completed', duration: 180 * multiplier },
1237
- distill: { status: 'completed', duration: 90 * multiplier },
1238
- consolidate: { status: 'completed', duration: 60 * multiplier },
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 + (500 * multiplier),
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: 'pattern',
2239
+ namespace: 'patterns',
1997
2240
  generateEmbeddingFlag: true,
1998
2241
  tags: [type, `confidence-${Math.round(confidence * 100)}`, 'reasoning-pattern'],
1999
2242
  });