claudecto 0.1.0

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 (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +275 -0
  3. package/dist/__tests__/package.test.d.ts +2 -0
  4. package/dist/__tests__/package.test.d.ts.map +1 -0
  5. package/dist/__tests__/package.test.js +53 -0
  6. package/dist/__tests__/package.test.js.map +1 -0
  7. package/dist/cli.d.ts +6 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +200 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/index.d.ts +12 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +12 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/server/index.d.ts +11 -0
  16. package/dist/server/index.d.ts.map +1 -0
  17. package/dist/server/index.js +1207 -0
  18. package/dist/server/index.js.map +1 -0
  19. package/dist/services/advisor.d.ts +117 -0
  20. package/dist/services/advisor.d.ts.map +1 -0
  21. package/dist/services/advisor.js +2636 -0
  22. package/dist/services/advisor.js.map +1 -0
  23. package/dist/services/agent-generator.d.ts +71 -0
  24. package/dist/services/agent-generator.d.ts.map +1 -0
  25. package/dist/services/agent-generator.js +295 -0
  26. package/dist/services/agent-generator.js.map +1 -0
  27. package/dist/services/agents.d.ts +67 -0
  28. package/dist/services/agents.d.ts.map +1 -0
  29. package/dist/services/agents.js +405 -0
  30. package/dist/services/agents.js.map +1 -0
  31. package/dist/services/analytics.d.ts +145 -0
  32. package/dist/services/analytics.d.ts.map +1 -0
  33. package/dist/services/analytics.js +609 -0
  34. package/dist/services/analytics.js.map +1 -0
  35. package/dist/services/blueprints.d.ts +31 -0
  36. package/dist/services/blueprints.d.ts.map +1 -0
  37. package/dist/services/blueprints.js +317 -0
  38. package/dist/services/blueprints.js.map +1 -0
  39. package/dist/services/claude-dir.d.ts +50 -0
  40. package/dist/services/claude-dir.d.ts.map +1 -0
  41. package/dist/services/claude-dir.js +193 -0
  42. package/dist/services/claude-dir.js.map +1 -0
  43. package/dist/services/hooks.d.ts +38 -0
  44. package/dist/services/hooks.d.ts.map +1 -0
  45. package/dist/services/hooks.js +165 -0
  46. package/dist/services/hooks.js.map +1 -0
  47. package/dist/services/insights.d.ts +52 -0
  48. package/dist/services/insights.d.ts.map +1 -0
  49. package/dist/services/insights.js +1035 -0
  50. package/dist/services/insights.js.map +1 -0
  51. package/dist/services/memory.d.ts +14 -0
  52. package/dist/services/memory.d.ts.map +1 -0
  53. package/dist/services/memory.js +25 -0
  54. package/dist/services/memory.js.map +1 -0
  55. package/dist/services/plans.d.ts +20 -0
  56. package/dist/services/plans.d.ts.map +1 -0
  57. package/dist/services/plans.js +149 -0
  58. package/dist/services/plans.js.map +1 -0
  59. package/dist/services/project-intelligence.d.ts +75 -0
  60. package/dist/services/project-intelligence.d.ts.map +1 -0
  61. package/dist/services/project-intelligence.js +731 -0
  62. package/dist/services/project-intelligence.js.map +1 -0
  63. package/dist/services/search.d.ts +32 -0
  64. package/dist/services/search.d.ts.map +1 -0
  65. package/dist/services/search.js +203 -0
  66. package/dist/services/search.js.map +1 -0
  67. package/dist/services/sessions.d.ts +25 -0
  68. package/dist/services/sessions.d.ts.map +1 -0
  69. package/dist/services/sessions.js +248 -0
  70. package/dist/services/sessions.js.map +1 -0
  71. package/dist/services/skills.d.ts +30 -0
  72. package/dist/services/skills.d.ts.map +1 -0
  73. package/dist/services/skills.js +197 -0
  74. package/dist/services/skills.js.map +1 -0
  75. package/dist/services/stats.d.ts +23 -0
  76. package/dist/services/stats.d.ts.map +1 -0
  77. package/dist/services/stats.js +88 -0
  78. package/dist/services/stats.js.map +1 -0
  79. package/dist/services/teams.d.ts +115 -0
  80. package/dist/services/teams.d.ts.map +1 -0
  81. package/dist/services/teams.js +421 -0
  82. package/dist/services/teams.js.map +1 -0
  83. package/dist/services/tech-stack.d.ts +98 -0
  84. package/dist/services/tech-stack.d.ts.map +1 -0
  85. package/dist/services/tech-stack.js +1088 -0
  86. package/dist/services/tech-stack.js.map +1 -0
  87. package/dist/services/terminal.d.ts +75 -0
  88. package/dist/services/terminal.d.ts.map +1 -0
  89. package/dist/services/terminal.js +224 -0
  90. package/dist/services/terminal.js.map +1 -0
  91. package/dist/types.d.ts +1095 -0
  92. package/dist/types.d.ts.map +1 -0
  93. package/dist/types.js +18 -0
  94. package/dist/types.js.map +1 -0
  95. package/dist/ui/assets/index-BiH4Nhdk.css +1 -0
  96. package/dist/ui/assets/index-Brv-K8bd.css +1 -0
  97. package/dist/ui/assets/index-BwMBEdQz.js +3108 -0
  98. package/dist/ui/assets/index-BwMBEdQz.js.map +1 -0
  99. package/dist/ui/assets/index-CEWz7ABD.js +3108 -0
  100. package/dist/ui/assets/index-CEWz7ABD.js.map +1 -0
  101. package/dist/ui/assets/index-CIZ3vvc-.css +1 -0
  102. package/dist/ui/assets/index-CsU3cI0n.js +3108 -0
  103. package/dist/ui/assets/index-CsU3cI0n.js.map +1 -0
  104. package/dist/ui/assets/index-D3AY6iCS.js +3133 -0
  105. package/dist/ui/assets/index-D3AY6iCS.js.map +1 -0
  106. package/dist/ui/assets/index-D8lNZ0Ye.css +1 -0
  107. package/dist/ui/assets/index-DmgeppSA.js +3108 -0
  108. package/dist/ui/assets/index-DmgeppSA.js.map +1 -0
  109. package/dist/ui/favicon.svg +43 -0
  110. package/dist/ui/index.html +23 -0
  111. package/dist/utils/jsonl.d.ts +16 -0
  112. package/dist/utils/jsonl.d.ts.map +1 -0
  113. package/dist/utils/jsonl.js +51 -0
  114. package/dist/utils/jsonl.js.map +1 -0
  115. package/package.json +106 -0
@@ -0,0 +1,2636 @@
1
+ /**
2
+ * AI Advisor Service - Proactive Intelligence Engine
3
+ *
4
+ * Analyzes Claude Code usage patterns and generates actionable recommendations:
5
+ * - Repeated prompts → Skill suggestions
6
+ * - Error patterns → Hook suggestions
7
+ * - Clarification loops → CLAUDE.md additions
8
+ * - Model performance → Model recommendations
9
+ *
10
+ * IMPORTANT: This service must intelligently distinguish between:
11
+ * - Actual user requests/intentions (skill candidates)
12
+ * - Tool results (file contents, command outputs)
13
+ * - System messages and code snippets
14
+ */
15
+ import path from 'node:path';
16
+ import fs from 'node:fs/promises';
17
+ import crypto from 'node:crypto';
18
+ import { exec } from 'node:child_process';
19
+ import { promisify } from 'node:util';
20
+ const execAsync = promisify(exec);
21
+ const SKILL_KNOWLEDGE_BASE = [
22
+ // === DEPLOYMENT SKILLS ===
23
+ {
24
+ name: 'deploy-vercel',
25
+ description: 'Deploy the current project to Vercel with automatic preview URLs',
26
+ category: 'deployment',
27
+ techStack: ['next', 'nextjs', 'react', 'vite', 'astro', 'nuxt', 'svelte'],
28
+ triggers: ['deploy', 'vercel', 'ship', 'publish', 'production', 'preview'],
29
+ confidence: 0.9,
30
+ content: `---
31
+ description: Deploy the current project to Vercel
32
+ user-invocable: true
33
+ ---
34
+
35
+ # Deploy to Vercel
36
+
37
+ Deploy the current project to Vercel for production or preview.
38
+
39
+ ## Instructions
40
+
41
+ 1. Check if the Vercel CLI is installed (\`which vercel\`), suggest installation if not
42
+ 2. Run \`vercel\` to deploy (or \`vercel --prod\` for production)
43
+ 3. If no vercel.json exists and deployment fails, help configure it
44
+ 4. Return the deployment URL when complete
45
+
46
+ $ARGUMENTS`,
47
+ },
48
+ {
49
+ name: 'deploy-netlify',
50
+ description: 'Deploy the current project to Netlify',
51
+ category: 'deployment',
52
+ techStack: ['next', 'nextjs', 'react', 'vite', 'gatsby', 'hugo', 'astro'],
53
+ triggers: ['deploy', 'netlify', 'ship', 'publish'],
54
+ confidence: 0.85,
55
+ content: `---
56
+ description: Deploy the current project to Netlify
57
+ user-invocable: true
58
+ ---
59
+
60
+ # Deploy to Netlify
61
+
62
+ Deploy the current project to Netlify.
63
+
64
+ ## Instructions
65
+
66
+ 1. Check if Netlify CLI is installed (\`which netlify\`)
67
+ 2. Run \`netlify deploy\` for preview or \`netlify deploy --prod\` for production
68
+ 3. Return the deployment URL
69
+
70
+ $ARGUMENTS`,
71
+ },
72
+ // === GIT & VERSION CONTROL SKILLS ===
73
+ {
74
+ name: 'commit',
75
+ description: 'Create well-structured git commits with conventional commit messages',
76
+ category: 'git',
77
+ techStack: ['*'], // Universal
78
+ triggers: ['commit', 'git', 'save', 'changes', 'staged'],
79
+ confidence: 0.95,
80
+ content: `---
81
+ description: Create a well-structured git commit with conventional commit message
82
+ user-invocable: true
83
+ ---
84
+
85
+ # Smart Commit
86
+
87
+ Create a well-structured git commit following conventional commits.
88
+
89
+ ## Instructions
90
+
91
+ 1. Run \`git status\` and \`git diff --staged\` to understand changes
92
+ 2. Analyze the changes to determine the commit type:
93
+ - feat: New feature
94
+ - fix: Bug fix
95
+ - docs: Documentation only
96
+ - style: Formatting, no code change
97
+ - refactor: Code change without feature/fix
98
+ - test: Adding tests
99
+ - chore: Maintenance
100
+ 3. Generate a commit message: \`<type>(<scope>): <description>\`
101
+ 4. Include a body explaining WHY if the change is significant
102
+ 5. Run \`git commit -m "..."\`
103
+
104
+ $ARGUMENTS`,
105
+ },
106
+ {
107
+ name: 'pr-create',
108
+ description: 'Create a pull request with proper description and checklist',
109
+ category: 'git',
110
+ techStack: ['*'],
111
+ triggers: ['pr', 'pull request', 'merge', 'review', 'github'],
112
+ confidence: 0.9,
113
+ content: `---
114
+ description: Create a pull request with proper title, description, and checklist
115
+ user-invocable: true
116
+ ---
117
+
118
+ # Create Pull Request
119
+
120
+ Create a well-structured pull request.
121
+
122
+ ## Instructions
123
+
124
+ 1. Check current branch and commits with \`git log main..HEAD\`
125
+ 2. Analyze the changes to write a clear PR title
126
+ 3. Generate a description with:
127
+ - Summary of changes
128
+ - Why this change is needed
129
+ - How to test
130
+ - Checklist of items
131
+ 4. Use \`gh pr create\` to create the PR
132
+ 5. Return the PR URL
133
+
134
+ $ARGUMENTS`,
135
+ },
136
+ {
137
+ name: 'code-review',
138
+ description: 'Review code for issues, security vulnerabilities, and improvements',
139
+ category: 'git',
140
+ techStack: ['*'],
141
+ triggers: ['review', 'pr', 'feedback', 'check', 'audit'],
142
+ confidence: 0.88,
143
+ content: `---
144
+ description: Review code for issues, security, and improvements
145
+ user-invocable: true
146
+ ---
147
+
148
+ # Code Review
149
+
150
+ Perform a thorough code review.
151
+
152
+ ## Review Checklist
153
+
154
+ ### Correctness
155
+ - Logic errors and edge cases
156
+ - Null/undefined handling
157
+ - Error handling completeness
158
+
159
+ ### Security
160
+ - Input validation
161
+ - SQL injection / XSS vulnerabilities
162
+ - Sensitive data exposure
163
+
164
+ ### Performance
165
+ - Inefficient algorithms
166
+ - Unnecessary re-renders
167
+ - Memory leaks
168
+
169
+ ### Maintainability
170
+ - Code readability
171
+ - DRY principle
172
+ - Test coverage
173
+
174
+ ## Output Format
175
+
176
+ 1. **Critical Issues** - Must fix
177
+ 2. **Suggestions** - Recommended improvements
178
+ 3. **Positive Notes** - What was done well
179
+
180
+ $ARGUMENTS`,
181
+ },
182
+ // === TESTING SKILLS ===
183
+ {
184
+ name: 'run-tests',
185
+ description: 'Run tests and analyze results with helpful feedback',
186
+ category: 'testing',
187
+ techStack: ['*'],
188
+ triggers: ['test', 'tests', 'spec', 'jest', 'vitest', 'pytest', 'mocha'],
189
+ confidence: 0.92,
190
+ content: `---
191
+ description: Run tests and analyze results
192
+ user-invocable: true
193
+ ---
194
+
195
+ # Run Tests
196
+
197
+ Run the project's test suite and analyze results.
198
+
199
+ ## Instructions
200
+
201
+ 1. Detect the test framework (jest, vitest, pytest, etc.)
202
+ 2. Run the appropriate test command
203
+ 3. If tests fail:
204
+ - Analyze the failure messages
205
+ - Identify the root cause
206
+ - Suggest fixes
207
+ 4. Report summary: passed, failed, skipped
208
+
209
+ $ARGUMENTS`,
210
+ },
211
+ {
212
+ name: 'generate-tests',
213
+ description: 'Generate comprehensive unit tests for the specified code',
214
+ category: 'testing',
215
+ techStack: ['*'],
216
+ triggers: ['test', 'tests', 'coverage', 'unit test', 'spec'],
217
+ confidence: 0.85,
218
+ content: `---
219
+ description: Generate comprehensive unit tests
220
+ user-invocable: true
221
+ ---
222
+
223
+ # Generate Tests
224
+
225
+ Generate unit tests for the specified code.
226
+
227
+ ## Instructions
228
+
229
+ 1. Analyze the code to understand its functionality
230
+ 2. Generate tests covering:
231
+ - Happy path scenarios
232
+ - Edge cases
233
+ - Error handling
234
+ - Boundary conditions
235
+ 3. Follow the project's existing test patterns
236
+ 4. Use appropriate assertions
237
+
238
+ $ARGUMENTS`,
239
+ },
240
+ // === CODE QUALITY SKILLS ===
241
+ {
242
+ name: 'lint-fix',
243
+ description: 'Run linter and automatically fix issues',
244
+ category: 'code-quality',
245
+ techStack: ['javascript', 'typescript', 'react', 'node'],
246
+ triggers: ['lint', 'eslint', 'fix', 'format', 'style'],
247
+ confidence: 0.9,
248
+ content: `---
249
+ description: Run linter and fix issues
250
+ user-invocable: true
251
+ ---
252
+
253
+ # Lint and Fix
254
+
255
+ Run the linter and fix issues automatically.
256
+
257
+ ## Instructions
258
+
259
+ 1. Detect the linter (eslint, biome, etc.)
260
+ 2. Run with auto-fix: \`npm run lint -- --fix\` or equivalent
261
+ 3. Report remaining issues that need manual attention
262
+
263
+ $ARGUMENTS`,
264
+ },
265
+ {
266
+ name: 'type-check',
267
+ description: 'Run TypeScript type checking and help fix errors',
268
+ category: 'code-quality',
269
+ techStack: ['typescript', 'ts'],
270
+ triggers: ['type', 'types', 'typescript', 'tsc', 'type error'],
271
+ confidence: 0.88,
272
+ content: `---
273
+ description: Run TypeScript type checking
274
+ user-invocable: true
275
+ ---
276
+
277
+ # Type Check
278
+
279
+ Run TypeScript type checking and help fix errors.
280
+
281
+ ## Instructions
282
+
283
+ 1. Run \`npx tsc --noEmit\` to check types
284
+ 2. If errors found:
285
+ - Explain each error clearly
286
+ - Suggest fixes
287
+ - Apply fixes if requested
288
+
289
+ $ARGUMENTS`,
290
+ },
291
+ // === DOCUMENTATION SKILLS ===
292
+ {
293
+ name: 'generate-docs',
294
+ description: 'Generate documentation for code',
295
+ category: 'documentation',
296
+ techStack: ['*'],
297
+ triggers: ['docs', 'documentation', 'readme', 'jsdoc', 'comment'],
298
+ confidence: 0.85,
299
+ content: `---
300
+ description: Generate documentation for code
301
+ user-invocable: true
302
+ ---
303
+
304
+ # Generate Documentation
305
+
306
+ Generate comprehensive documentation.
307
+
308
+ ## Instructions
309
+
310
+ 1. Analyze the code structure
311
+ 2. Generate appropriate documentation:
312
+ - JSDoc comments for functions
313
+ - README sections
314
+ - API documentation
315
+ 3. Follow the project's documentation style
316
+
317
+ $ARGUMENTS`,
318
+ },
319
+ {
320
+ name: 'update-readme',
321
+ description: 'Update the README with current project information',
322
+ category: 'documentation',
323
+ techStack: ['*'],
324
+ triggers: ['readme', 'documentation', 'docs', 'update'],
325
+ confidence: 0.82,
326
+ content: `---
327
+ description: Update README with current project info
328
+ user-invocable: true
329
+ ---
330
+
331
+ # Update README
332
+
333
+ Update the README file with current project information.
334
+
335
+ ## Instructions
336
+
337
+ 1. Read the current README
338
+ 2. Analyze the project structure
339
+ 3. Update sections:
340
+ - Installation instructions
341
+ - Usage examples
342
+ - API reference (if applicable)
343
+ - Contributing guidelines
344
+
345
+ $ARGUMENTS`,
346
+ },
347
+ // === SCAFFOLDING SKILLS ===
348
+ {
349
+ name: 'create-component',
350
+ description: 'Create a new React/Vue/Svelte component with proper structure',
351
+ category: 'scaffolding',
352
+ techStack: ['react', 'vue', 'svelte', 'next', 'nextjs', 'nuxt'],
353
+ triggers: ['component', 'create', 'new', 'scaffold', 'generate'],
354
+ confidence: 0.88,
355
+ content: `---
356
+ description: Create a new component with proper structure
357
+ user-invocable: true
358
+ ---
359
+
360
+ # Create Component
361
+
362
+ Create a new component following project conventions.
363
+
364
+ ## Instructions
365
+
366
+ 1. Detect the framework (React, Vue, Svelte)
367
+ 2. Find existing component patterns in the project
368
+ 3. Create the component with:
369
+ - Proper file structure
370
+ - TypeScript types (if using TS)
371
+ - Basic props interface
372
+ - Matching style file if project uses CSS modules
373
+ 4. Add basic tests if project has test files for components
374
+
375
+ $ARGUMENTS - provide component name and description`,
376
+ },
377
+ {
378
+ name: 'create-api-route',
379
+ description: 'Create a new API route/endpoint',
380
+ category: 'scaffolding',
381
+ techStack: ['next', 'nextjs', 'express', 'fastify', 'hono', 'node'],
382
+ triggers: ['api', 'route', 'endpoint', 'create', 'new'],
383
+ confidence: 0.85,
384
+ content: `---
385
+ description: Create a new API route/endpoint
386
+ user-invocable: true
387
+ ---
388
+
389
+ # Create API Route
390
+
391
+ Create a new API endpoint following project patterns.
392
+
393
+ ## Instructions
394
+
395
+ 1. Detect the API framework (Next.js API routes, Express, etc.)
396
+ 2. Find existing API route patterns
397
+ 3. Create the route with:
398
+ - Proper request/response handling
399
+ - Input validation
400
+ - Error handling
401
+ - TypeScript types
402
+
403
+ $ARGUMENTS - provide route path and description`,
404
+ },
405
+ // === DEBUGGING SKILLS ===
406
+ {
407
+ name: 'debug',
408
+ description: 'Help debug an issue with systematic analysis',
409
+ category: 'debugging',
410
+ techStack: ['*'],
411
+ triggers: ['debug', 'error', 'bug', 'issue', 'fix', 'broken', 'not working'],
412
+ confidence: 0.9,
413
+ content: `---
414
+ description: Systematically debug an issue
415
+ user-invocable: true
416
+ ---
417
+
418
+ # Debug Issue
419
+
420
+ Systematically debug and fix an issue.
421
+
422
+ ## Process
423
+
424
+ 1. **Understand**: What's the expected vs actual behavior?
425
+ 2. **Reproduce**: Identify minimal steps to reproduce
426
+ 3. **Locate**: Find the root cause through:
427
+ - Reading error messages
428
+ - Checking recent changes
429
+ - Adding debug logging
430
+ 4. **Fix**: Implement the minimal fix
431
+ 5. **Verify**: Confirm the fix works
432
+
433
+ $ARGUMENTS - describe the issue`,
434
+ },
435
+ // === MCP & INTEGRATION SKILLS ===
436
+ {
437
+ name: 'mcp-builder',
438
+ description: 'Guide for creating MCP servers to integrate external APIs',
439
+ category: 'integration',
440
+ techStack: ['typescript', 'node', 'python'],
441
+ triggers: ['mcp', 'server', 'integration', 'api', 'tool'],
442
+ confidence: 0.8,
443
+ content: `---
444
+ description: Create MCP servers for external integrations
445
+ user-invocable: true
446
+ ---
447
+
448
+ # MCP Server Builder
449
+
450
+ Create MCP servers to integrate external APIs and services.
451
+
452
+ ## Structure
453
+
454
+ \`\`\`typescript
455
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
456
+
457
+ const server = new Server({
458
+ name: "my-mcp-server",
459
+ version: "1.0.0",
460
+ }, {
461
+ capabilities: { tools: {} }
462
+ });
463
+ \`\`\`
464
+
465
+ ## Instructions
466
+
467
+ 1. Define the tools with clear schemas
468
+ 2. Implement request handlers
469
+ 3. Add proper error handling
470
+ 4. Test the integration
471
+
472
+ $ARGUMENTS - describe what to integrate`,
473
+ },
474
+ // === REFACTORING SKILLS ===
475
+ {
476
+ name: 'refactor',
477
+ description: 'Refactor code following best practices',
478
+ category: 'refactoring',
479
+ techStack: ['*'],
480
+ triggers: ['refactor', 'clean', 'improve', 'optimize', 'restructure'],
481
+ confidence: 0.85,
482
+ content: `---
483
+ description: Refactor code following best practices
484
+ user-invocable: true
485
+ ---
486
+
487
+ # Refactor Code
488
+
489
+ Refactor code for better readability, maintainability, and performance.
490
+
491
+ ## Goals
492
+
493
+ - **Readability**: Clear naming, single responsibility
494
+ - **DRY**: Remove duplication
495
+ - **SOLID**: Apply principles where appropriate
496
+ - **Performance**: Optimize hot paths
497
+
498
+ ## Process
499
+
500
+ 1. Identify code smells
501
+ 2. Plan incremental changes
502
+ 3. Refactor step by step
503
+ 4. Verify behavior after each step
504
+
505
+ $ARGUMENTS - specify what to refactor`,
506
+ },
507
+ ];
508
+ const AGENT_KNOWLEDGE_BASE = [
509
+ {
510
+ name: 'code-reviewer',
511
+ description: 'Reviews code for issues, security vulnerabilities, and improvements',
512
+ triggers: ['review', 'pr review', 'check code', 'audit', 'look over', 'feedback on'],
513
+ techStack: ['*'],
514
+ category: 'review',
515
+ content: `---
516
+ name: code-reviewer
517
+ description: Reviews code for issues, security vulnerabilities, and improvements
518
+ allowed-tools: Read, Glob, Grep, LSP
519
+ ---
520
+
521
+ # Code Reviewer Agent
522
+
523
+ You are a thorough code reviewer. Analyze code for:
524
+
525
+ ## Review Checklist
526
+
527
+ ### Correctness
528
+ - Logic errors and edge cases
529
+ - Null/undefined handling
530
+ - Error handling completeness
531
+
532
+ ### Security
533
+ - Input validation
534
+ - SQL injection / XSS vulnerabilities
535
+ - Sensitive data exposure
536
+
537
+ ### Performance
538
+ - Inefficient algorithms
539
+ - Unnecessary re-renders (React)
540
+ - Memory leaks
541
+
542
+ ### Maintainability
543
+ - Code readability
544
+ - DRY principle adherence
545
+ - Test coverage
546
+
547
+ ## Output Format
548
+
549
+ Provide feedback in these categories:
550
+ 1. **Critical Issues** - Must fix before merging
551
+ 2. **Suggestions** - Recommended improvements
552
+ 3. **Positive Notes** - What was done well
553
+
554
+ $ARGUMENTS`,
555
+ },
556
+ {
557
+ name: 'test-writer',
558
+ description: 'Generates comprehensive unit and integration tests',
559
+ triggers: ['write tests', 'add tests', 'test coverage', 'unit tests', 'integration tests', 'spec'],
560
+ techStack: ['*'],
561
+ category: 'testing',
562
+ content: `---
563
+ name: test-writer
564
+ description: Generates comprehensive unit and integration tests
565
+ allowed-tools: Read, Write, Edit, Glob, Grep, Bash
566
+ ---
567
+
568
+ # Test Writer Agent
569
+
570
+ You are an expert test writer. Generate comprehensive tests.
571
+
572
+ ## Test Categories
573
+
574
+ ### Unit Tests
575
+ - Test individual functions in isolation
576
+ - Mock dependencies appropriately
577
+ - Cover edge cases and error conditions
578
+
579
+ ### Integration Tests
580
+ - Test component interactions
581
+ - Test API endpoints
582
+ - Test database operations
583
+
584
+ ## Best Practices
585
+
586
+ 1. Follow the project's existing test patterns
587
+ 2. Use descriptive test names (should... when... given...)
588
+ 3. Aim for high coverage of critical paths
589
+ 4. Include both positive and negative test cases
590
+
591
+ ## Framework Detection
592
+
593
+ - JavaScript/TypeScript: Look for jest, vitest, mocha
594
+ - Python: Look for pytest, unittest
595
+ - Go: Use standard testing package
596
+
597
+ $ARGUMENTS`,
598
+ },
599
+ {
600
+ name: 'security-auditor',
601
+ description: 'Audits code for security vulnerabilities and OWASP issues',
602
+ triggers: ['security', 'vulnerabilities', 'audit security', 'owasp', 'secure', 'penetration'],
603
+ techStack: ['*'],
604
+ category: 'security',
605
+ content: `---
606
+ name: security-auditor
607
+ description: Audits code for security vulnerabilities and OWASP compliance
608
+ allowed-tools: Read, Glob, Grep, Bash
609
+ ---
610
+
611
+ # Security Auditor Agent
612
+
613
+ You are a security expert. Audit code for vulnerabilities.
614
+
615
+ ## OWASP Top 10 Check
616
+
617
+ 1. **Injection** - SQL, NoSQL, OS, LDAP injection
618
+ 2. **Broken Authentication** - Session management issues
619
+ 3. **Sensitive Data Exposure** - Unencrypted secrets, logs
620
+ 4. **XML External Entities** - XXE attacks
621
+ 5. **Broken Access Control** - Missing authorization checks
622
+ 6. **Security Misconfiguration** - Debug modes, default credentials
623
+ 7. **XSS** - Cross-site scripting vulnerabilities
624
+ 8. **Insecure Deserialization** - Untrusted data deserialization
625
+ 9. **Vulnerable Components** - Outdated dependencies
626
+ 10. **Insufficient Logging** - Missing audit trails
627
+
628
+ ## Additional Checks
629
+
630
+ - Hardcoded secrets (API keys, passwords)
631
+ - Insecure random number generation
632
+ - Path traversal vulnerabilities
633
+ - CORS misconfigurations
634
+
635
+ ## Output Format
636
+
637
+ Rate each finding by severity: CRITICAL, HIGH, MEDIUM, LOW
638
+
639
+ $ARGUMENTS`,
640
+ },
641
+ {
642
+ name: 'docs-generator',
643
+ description: 'Generates documentation, READMEs, and JSDoc comments',
644
+ triggers: ['document', 'documentation', 'readme', 'jsdoc', 'typedoc', 'api docs'],
645
+ techStack: ['*'],
646
+ category: 'documentation',
647
+ content: `---
648
+ name: docs-generator
649
+ description: Generates comprehensive documentation
650
+ allowed-tools: Read, Write, Edit, Glob, Grep
651
+ ---
652
+
653
+ # Documentation Generator Agent
654
+
655
+ You are a technical writer. Generate clear documentation.
656
+
657
+ ## Documentation Types
658
+
659
+ ### README.md
660
+ - Project overview
661
+ - Installation instructions
662
+ - Usage examples
663
+ - API reference
664
+ - Contributing guidelines
665
+
666
+ ### API Documentation
667
+ - Endpoint descriptions
668
+ - Request/response examples
669
+ - Authentication details
670
+ - Error codes
671
+
672
+ ### Code Documentation
673
+ - JSDoc/TSDoc comments
674
+ - Function descriptions
675
+ - Parameter documentation
676
+ - Return value documentation
677
+
678
+ ## Best Practices
679
+
680
+ 1. Use clear, concise language
681
+ 2. Include practical examples
682
+ 3. Keep documentation up-to-date with code
683
+ 4. Use consistent formatting
684
+
685
+ $ARGUMENTS`,
686
+ },
687
+ {
688
+ name: 'refactorer',
689
+ description: 'Refactors code for better readability, maintainability, and performance',
690
+ triggers: ['refactor', 'clean up', 'improve code', 'simplify', 'restructure', 'optimize'],
691
+ techStack: ['*'],
692
+ category: 'refactoring',
693
+ content: `---
694
+ name: refactorer
695
+ description: Refactors code following best practices
696
+ allowed-tools: Read, Write, Edit, Glob, Grep, LSP
697
+ ---
698
+
699
+ # Refactoring Agent
700
+
701
+ You are a refactoring expert. Improve code quality.
702
+
703
+ ## Refactoring Goals
704
+
705
+ ### Readability
706
+ - Clear variable/function names
707
+ - Single responsibility principle
708
+ - Consistent formatting
709
+
710
+ ### DRY (Don't Repeat Yourself)
711
+ - Extract common code into functions
712
+ - Create reusable utilities
713
+ - Use inheritance/composition appropriately
714
+
715
+ ### SOLID Principles
716
+ - Single Responsibility
717
+ - Open/Closed
718
+ - Liskov Substitution
719
+ - Interface Segregation
720
+ - Dependency Inversion
721
+
722
+ ### Performance
723
+ - Optimize hot paths
724
+ - Reduce unnecessary computations
725
+ - Improve algorithmic complexity
726
+
727
+ ## Process
728
+
729
+ 1. Identify code smells
730
+ 2. Plan incremental changes
731
+ 3. Refactor step by step
732
+ 4. Verify behavior after each step
733
+
734
+ $ARGUMENTS`,
735
+ },
736
+ ];
737
+ const HOOK_KNOWLEDGE_BASE = [
738
+ {
739
+ name: 'lint-on-edit',
740
+ event: 'PostToolUse',
741
+ matcher: 'Edit',
742
+ command: 'npm run lint -- --fix "$TOOL_INPUT_file_path" 2>/dev/null || true',
743
+ description: 'Auto-fix linting issues after editing files',
744
+ triggers: ['lint errors', 'eslint', 'formatting issues', 'prettier'],
745
+ },
746
+ {
747
+ name: 'type-check-on-edit',
748
+ event: 'PostToolUse',
749
+ matcher: 'Edit',
750
+ command: 'npx tsc --noEmit 2>&1 | head -20 || true',
751
+ description: 'Run TypeScript type checking after edits',
752
+ triggers: ['type errors', 'typescript', 'tsc', 'type mismatch'],
753
+ },
754
+ {
755
+ name: 'test-related-on-edit',
756
+ event: 'PostToolUse',
757
+ matcher: 'Edit',
758
+ command: 'npm test -- --findRelatedTests "$TOOL_INPUT_file_path" --passWithNoTests 2>/dev/null || true',
759
+ description: 'Run related tests after editing files',
760
+ triggers: ['test failures', 'broken tests', 'test coverage'],
761
+ },
762
+ {
763
+ name: 'format-on-write',
764
+ event: 'PostToolUse',
765
+ matcher: 'Write',
766
+ command: 'npx prettier --write "$TOOL_INPUT_file_path" 2>/dev/null || true',
767
+ description: 'Auto-format newly written files',
768
+ triggers: ['formatting', 'prettier', 'code style'],
769
+ },
770
+ {
771
+ name: 'build-check',
772
+ event: 'PostToolUse',
773
+ matcher: 'Edit',
774
+ command: 'npm run build 2>&1 | tail -10 || true',
775
+ description: 'Check if build succeeds after changes',
776
+ triggers: ['build errors', 'compilation', 'bundle'],
777
+ },
778
+ ];
779
+ const MCP_KNOWLEDGE_BASE = [
780
+ {
781
+ name: 'postgres',
782
+ description: 'Direct PostgreSQL access for queries and schema inspection',
783
+ triggers: ['postgres', 'postgresql', 'database', 'sql query', 'pg_dump', 'psql'],
784
+ npmPackage: '@modelcontextprotocol/server-postgres',
785
+ setupInstructions: `Add to your MCP settings:
786
+ {
787
+ "mcpServers": {
788
+ "postgres": {
789
+ "command": "npx",
790
+ "args": ["-y", "@modelcontextprotocol/server-postgres", "postgresql://user:pass@localhost/db"]
791
+ }
792
+ }
793
+ }`,
794
+ },
795
+ {
796
+ name: 'github',
797
+ description: 'GitHub integration for issues, PRs, and repo management',
798
+ triggers: ['github api', 'issues', 'pull requests', 'gh pr', 'repository'],
799
+ npmPackage: '@modelcontextprotocol/server-github',
800
+ setupInstructions: `Add to your MCP settings:
801
+ {
802
+ "mcpServers": {
803
+ "github": {
804
+ "command": "npx",
805
+ "args": ["-y", "@modelcontextprotocol/server-github"],
806
+ "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "your-token" }
807
+ }
808
+ }
809
+ }`,
810
+ },
811
+ {
812
+ name: 'filesystem',
813
+ description: 'Controlled filesystem access with explicit allowed directories',
814
+ triggers: ['file access', 'read files', 'directory', 'filesystem'],
815
+ npmPackage: '@modelcontextprotocol/server-filesystem',
816
+ setupInstructions: `Add to your MCP settings:
817
+ {
818
+ "mcpServers": {
819
+ "filesystem": {
820
+ "command": "npx",
821
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/dir"]
822
+ }
823
+ }
824
+ }`,
825
+ },
826
+ {
827
+ name: 'memory',
828
+ description: 'Persistent memory storage for context across sessions',
829
+ triggers: ['remember', 'memory', 'store context', 'knowledge graph'],
830
+ npmPackage: '@modelcontextprotocol/server-memory',
831
+ setupInstructions: `Add to your MCP settings:
832
+ {
833
+ "mcpServers": {
834
+ "memory": {
835
+ "command": "npx",
836
+ "args": ["-y", "@modelcontextprotocol/server-memory"]
837
+ }
838
+ }
839
+ }`,
840
+ },
841
+ {
842
+ name: 'slack',
843
+ description: 'Slack integration for messaging and channel management',
844
+ triggers: ['slack', 'slack api', 'send message', 'channel'],
845
+ npmPackage: '@modelcontextprotocol/server-slack',
846
+ setupInstructions: `Add to your MCP settings:
847
+ {
848
+ "mcpServers": {
849
+ "slack": {
850
+ "command": "npx",
851
+ "args": ["-y", "@modelcontextprotocol/server-slack"],
852
+ "env": { "SLACK_BOT_TOKEN": "xoxb-your-token" }
853
+ }
854
+ }
855
+ }`,
856
+ },
857
+ ];
858
+ const CLAUDE_MD_PATTERNS = [
859
+ {
860
+ pattern: /we\s+use\s+(postgres(?:ql)?|mysql|mongodb|redis|sqlite)/i,
861
+ category: 'tech-stack',
862
+ section: '## Database',
863
+ extractContent: (match) => `- ${match[1]} for data storage`,
864
+ },
865
+ {
866
+ pattern: /we\s+use\s+(react|vue|angular|svelte|next\.?js|nuxt)/i,
867
+ category: 'tech-stack',
868
+ section: '## Frontend Framework',
869
+ extractContent: (match) => `- ${match[1]} for frontend`,
870
+ },
871
+ {
872
+ pattern: /we\s+use\s+(jest|vitest|pytest|mocha|cypress|playwright)\s+for\s+test/i,
873
+ category: 'tech-stack',
874
+ section: '## Testing',
875
+ extractContent: (match) => `- ${match[1]} for testing`,
876
+ },
877
+ {
878
+ pattern: /we\s+(always|prefer|use)\s+(2|4|tab)\s*(-|\s)?space\s+indent/i,
879
+ category: 'conventions',
880
+ section: '## Code Style',
881
+ extractContent: (match) => `- Use ${match[2]}-space indentation`,
882
+ },
883
+ {
884
+ pattern: /we\s+(use|follow|prefer)\s+(kebab|camel|snake|pascal)\s*(-|\s)?case/i,
885
+ category: 'conventions',
886
+ section: '## Naming Conventions',
887
+ extractContent: (match) => `- Use ${match[2]}-case for naming`,
888
+ },
889
+ {
890
+ pattern: /don't\s+use\s+(\w+)/i,
891
+ category: 'preferences',
892
+ section: '## Preferences',
893
+ extractContent: (match) => `- Avoid using ${match[1]}`,
894
+ },
895
+ {
896
+ pattern: /prefer\s+(\w+)\s+over\s+(\w+)/i,
897
+ category: 'preferences',
898
+ section: '## Preferences',
899
+ extractContent: (match) => `- Prefer ${match[1]} over ${match[2]}`,
900
+ },
901
+ ];
902
+ // ============================================================================
903
+ // Constants
904
+ // ============================================================================
905
+ const ADVISOR_STATE_FILE = 'advisor-state.json';
906
+ const DEFAULT_CONFIG = {
907
+ minPromptOccurrences: 3,
908
+ minErrorRate: 0.3,
909
+ minClarificationOccurrences: 2,
910
+ minSessionsForModelAnalysis: 5,
911
+ lookbackDays: 14,
912
+ confidenceThreshold: 0.5,
913
+ };
914
+ // Stop words for prompt normalization
915
+ const STOP_WORDS = new Set([
916
+ 'a', 'an', 'the', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
917
+ 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could', 'should',
918
+ 'may', 'might', 'must', 'shall', 'can', 'need', 'dare', 'ought', 'used',
919
+ 'to', 'of', 'in', 'for', 'on', 'with', 'at', 'by', 'from', 'as', 'into',
920
+ 'through', 'during', 'before', 'after', 'above', 'below', 'between',
921
+ 'and', 'but', 'or', 'nor', 'so', 'yet', 'both', 'either', 'neither',
922
+ 'not', 'only', 'own', 'same', 'than', 'too', 'very', 'just', 'also',
923
+ 'this', 'that', 'these', 'those', 'i', 'me', 'my', 'we', 'our', 'you', 'your',
924
+ 'he', 'him', 'his', 'she', 'her', 'it', 'its', 'they', 'them', 'their',
925
+ 'what', 'which', 'who', 'whom', 'when', 'where', 'why', 'how',
926
+ 'all', 'each', 'every', 'any', 'some', 'no', 'none', 'one', 'two',
927
+ 'please', 'thanks', 'thank', 'hey', 'hi', 'hello', 'ok', 'okay',
928
+ ]);
929
+ // ============================================================================
930
+ // INTELLIGENT FILTERING - Patterns that indicate NON-user-intent messages
931
+ // ============================================================================
932
+ // Patterns that indicate this is a TOOL RESULT, not a user request
933
+ const TOOL_RESULT_PATTERNS = [
934
+ // File paths as first content
935
+ /^\/[a-zA-Z]/, // Starts with absolute path
936
+ /^[a-zA-Z]:\\/, // Windows path
937
+ /^~\//, // Home directory path
938
+ /^\.\.\//, // Relative path
939
+ /^\.\//, // Current directory path
940
+ // Tool result markers
941
+ /^\[?tool_result/i,
942
+ /^\[?tool_use/i,
943
+ /^Tool result/i,
944
+ /^Error:/,
945
+ /^error\[/i,
946
+ /^warning\[/i,
947
+ /^note:/i,
948
+ // System messages
949
+ /<system-reminder>/,
950
+ /<\/system-reminder>/,
951
+ /^<[a-z-]+>/, // XML-like tags
952
+ // File contents indicators
953
+ /^```/, // Code block
954
+ /^import\s+/, // Import statement
955
+ /^export\s+/, // Export statement
956
+ /^const\s+\w+\s*=/, // Const declaration
957
+ /^let\s+\w+\s*=/, // Let declaration
958
+ /^var\s+\w+\s*=/, // Var declaration
959
+ /^function\s+\w+/, // Function declaration
960
+ /^class\s+\w+/, // Class declaration
961
+ /^interface\s+\w+/, // Interface declaration
962
+ /^type\s+\w+\s*=/, // Type declaration
963
+ /^package\s+/, // Package declaration
964
+ /^#!\//, // Shebang
965
+ /^\s*\d+[→│|]\s*/, // Line numbers (from file reads)
966
+ // Command outputs
967
+ /^total\s+\d+/, // ls output
968
+ /^drwx/, // Directory listing
969
+ /^-rw/, // File listing
970
+ /^commit\s+[a-f0-9]{7,40}/i, // Git commit
971
+ /^Author:/, // Git log
972
+ /^Date:/, // Git log
973
+ /^Merge:/, // Git merge
974
+ /^diff --git/, // Git diff
975
+ /^@@\s+-\d+,\d+\s+\+\d+,\d+\s+@@/, // Diff hunk
976
+ /^\+\+\+\s+/, // Diff header
977
+ /^---\s+/, // Diff header
978
+ /^npm\s+(WARN|ERR!)/, // npm output
979
+ /^added\s+\d+\s+packages?/, // npm install output
980
+ /^up to date/, // npm output
981
+ /^audited\s+\d+/, // npm audit
982
+ /^\d+\s+packages?\s+are looking/, // npm funding
983
+ /^found\s+\d+\s+vulnerabilities?/i, // npm audit
984
+ /^Successfully/i, // Success messages
985
+ /^Created\s+/, // File creation output
986
+ /^Deleted\s+/, // File deletion output
987
+ /^Modified\s+/, // File modification output
988
+ /^Reading\s+/, // Reading output
989
+ /^Writing\s+/, // Writing output
990
+ /^Building\s+/, // Build output
991
+ /^Compiling\s+/, // Compile output
992
+ /^Bundling\s+/, // Bundle output
993
+ /^✓\s+/, // Success checkmark
994
+ /^✗\s+/, // Failure mark
995
+ /^•\s+/, // Bullet point output
996
+ /^>\s+\w+@/, // npm script output
997
+ /^vite\s+v\d/, // Vite output
998
+ /^dist\//, // Build output paths
999
+ /^chunks?\s+/i, // Build chunks
1000
+ /^assets?\s+/i, // Build assets
1001
+ /^gzip:/i, // Compression info
1002
+ /^\(node:\d+\)/, // Node.js output
1003
+ /^at\s+\w+\s+\(/, // Stack trace
1004
+ /^ at\s+/, // Stack trace indent
1005
+ /^\s+\d+\s*\|\s*/, // Error location
1006
+ /^Process exited/, // Process output
1007
+ /^Exit code:/, // Exit code
1008
+ /^PASS\s+/, // Test output
1009
+ /^FAIL\s+/, // Test output
1010
+ /^Test Suites?:/, // Jest output
1011
+ /^Tests?:/, // Test output
1012
+ /^Snapshots?:/, // Jest output
1013
+ /^Time:/, // Test time
1014
+ /^Ran all test/, // Test summary
1015
+ /^Coverage:/, // Coverage output
1016
+ // JSON/data structures
1017
+ /^\s*\{/, // JSON object start
1018
+ /^\s*\[/, // JSON array start
1019
+ // HTML/XML
1020
+ /^<!DOCTYPE/i,
1021
+ /^<html/i,
1022
+ /^<head/i,
1023
+ /^<body/i,
1024
+ /^<meta/i,
1025
+ /^<link/i,
1026
+ /^<script/i,
1027
+ /^<style/i,
1028
+ /^<div/i,
1029
+ ];
1030
+ // Patterns that indicate short/trivial messages (not skill-worthy)
1031
+ const TRIVIAL_PATTERNS = [
1032
+ /^(ok|okay|yes|no|sure|thanks?|ty|thx|got it|understood|perfect|great|awesome|nice|cool|good|done|fixed|works?)\.?$/i,
1033
+ /^(y|n|yep|nope|yup|nah)\.?$/i,
1034
+ /^(sounds good|looks good|lgtm|makes sense)\.?$/i,
1035
+ /^(continue|go ahead|proceed|next)\.?$/i,
1036
+ /^👍|^👎|^✓|^✗|^❌|^✅/,
1037
+ ];
1038
+ // Patterns that indicate this is an AI response accidentally in user messages
1039
+ const AI_RESPONSE_PATTERNS = [
1040
+ /^(let me|i'll|i will|i can|i'm going to|here's|here is)/i,
1041
+ /^(certainly|absolutely|of course|sure thing)/i,
1042
+ /^(the file|the code|the function|the class|the component)/i,
1043
+ /^(i've|i have) (created|updated|modified|fixed|added|removed)/i,
1044
+ /^(now|first|next),?\s+(let me|i'll|i will)/i,
1045
+ /^(looking at|analyzing|examining|checking|reading)/i,
1046
+ ];
1047
+ // Patterns that indicate task/todo updates (system generated)
1048
+ const SYSTEM_GENERATED_PATTERNS = [
1049
+ /^todos?\s+(modified|updated|created|deleted)/i,
1050
+ /^task\s+(created|updated|completed|modified)/i,
1051
+ /^file\s+(created|updated|modified|deleted)\s+successfully/i,
1052
+ /^successfully\s+(created|updated|modified|deleted)/i,
1053
+ /^output\s+file/i,
1054
+ /^background\s+(task|shell|command)/i,
1055
+ /^running\s+in\s+background/i,
1056
+ /command\s+(running|completed|failed)/i,
1057
+ ];
1058
+ // ============================================================================
1059
+ // POSITIVE INDICATORS - Patterns that suggest genuine user intent
1060
+ // ============================================================================
1061
+ // Action verbs that suggest a user request
1062
+ const USER_INTENT_VERBS = [
1063
+ 'add', 'create', 'make', 'build', 'implement', 'write', 'generate',
1064
+ 'fix', 'repair', 'debug', 'solve', 'resolve', 'correct',
1065
+ 'update', 'change', 'modify', 'edit', 'refactor', 'improve', 'optimize',
1066
+ 'remove', 'delete', 'clean', 'clear',
1067
+ 'test', 'check', 'verify', 'validate', 'ensure',
1068
+ 'deploy', 'publish', 'release', 'ship',
1069
+ 'install', 'setup', 'configure', 'init',
1070
+ 'explain', 'describe', 'show', 'tell', 'help',
1071
+ 'find', 'search', 'look', 'locate',
1072
+ 'run', 'execute', 'start', 'stop',
1073
+ 'review', 'analyze', 'examine',
1074
+ ];
1075
+ // Question words that indicate user queries
1076
+ const QUESTION_INDICATORS = [
1077
+ 'how', 'what', 'why', 'where', 'when', 'which', 'who',
1078
+ 'can you', 'could you', 'would you', 'will you',
1079
+ 'is there', 'are there', 'do we', 'should we', 'shall we',
1080
+ ];
1081
+ // ============================================================================
1082
+ // Advisor Service
1083
+ // ============================================================================
1084
+ export class AdvisorService {
1085
+ claudeDir;
1086
+ sessionService;
1087
+ analyticsService;
1088
+ skillsService;
1089
+ hooksService;
1090
+ memoryService;
1091
+ agentsService;
1092
+ config;
1093
+ constructor(claudeDir, sessionService, analyticsService, skillsService, hooksService, memoryService, agentsService, config) {
1094
+ this.claudeDir = claudeDir;
1095
+ this.sessionService = sessionService;
1096
+ this.analyticsService = analyticsService;
1097
+ this.skillsService = skillsService;
1098
+ this.hooksService = hooksService;
1099
+ this.memoryService = memoryService;
1100
+ this.agentsService = agentsService;
1101
+ this.config = { ...DEFAULT_CONFIG, ...config };
1102
+ }
1103
+ // --------------------------------------------------------------------------
1104
+ // CORE INTELLIGENCE: Is this a genuine user request?
1105
+ // --------------------------------------------------------------------------
1106
+ /**
1107
+ * Determines if a message is a genuine user request that could become a skill.
1108
+ * This is the CRITICAL function that filters out tool results, code, etc.
1109
+ */
1110
+ isGenuineUserRequest(content) {
1111
+ if (!content)
1112
+ return false;
1113
+ const trimmed = content.trim();
1114
+ // Length checks
1115
+ if (trimmed.length < 15)
1116
+ return false; // Too short
1117
+ if (trimmed.length > 500)
1118
+ return false; // Likely file content
1119
+ // Check for tool result patterns (MOST COMMON FALSE POSITIVE)
1120
+ for (const pattern of TOOL_RESULT_PATTERNS) {
1121
+ if (pattern.test(trimmed)) {
1122
+ return false;
1123
+ }
1124
+ }
1125
+ // Check for trivial responses
1126
+ for (const pattern of TRIVIAL_PATTERNS) {
1127
+ if (pattern.test(trimmed)) {
1128
+ return false;
1129
+ }
1130
+ }
1131
+ // Check for AI response patterns
1132
+ for (const pattern of AI_RESPONSE_PATTERNS) {
1133
+ if (pattern.test(trimmed)) {
1134
+ return false;
1135
+ }
1136
+ }
1137
+ // Check for system-generated patterns
1138
+ for (const pattern of SYSTEM_GENERATED_PATTERNS) {
1139
+ if (pattern.test(trimmed)) {
1140
+ return false;
1141
+ }
1142
+ }
1143
+ // Check for code indicators (multiple lines with code patterns)
1144
+ const lines = trimmed.split('\n');
1145
+ if (lines.length > 5) {
1146
+ // Multiple lines is usually file content
1147
+ return false;
1148
+ }
1149
+ // Check if content looks like code
1150
+ if (this.looksLikeCode(trimmed)) {
1151
+ return false;
1152
+ }
1153
+ // Check if content looks like a file path
1154
+ if (this.looksLikeFilePath(trimmed)) {
1155
+ return false;
1156
+ }
1157
+ // Check if content has too many special characters (likely code or data)
1158
+ const specialCharRatio = (trimmed.match(/[{}\[\]<>()=;:,`'"]/g) || []).length / trimmed.length;
1159
+ if (specialCharRatio > 0.15) {
1160
+ return false;
1161
+ }
1162
+ // POSITIVE CHECK: Does this look like a user request?
1163
+ const lowerContent = trimmed.toLowerCase();
1164
+ // Check for action verbs at the start
1165
+ for (const verb of USER_INTENT_VERBS) {
1166
+ if (lowerContent.startsWith(verb + ' ') || lowerContent.startsWith(verb + '\n')) {
1167
+ return true;
1168
+ }
1169
+ // Also check for "please <verb>" or "can you <verb>"
1170
+ if (lowerContent.includes(`please ${verb}`) || lowerContent.includes(`can you ${verb}`)) {
1171
+ return true;
1172
+ }
1173
+ }
1174
+ // Check for question patterns
1175
+ for (const q of QUESTION_INDICATORS) {
1176
+ if (lowerContent.startsWith(q + ' ') || lowerContent.includes(' ' + q + ' ')) {
1177
+ return true;
1178
+ }
1179
+ }
1180
+ // Check for imperative sentence structure (starts with verb-like word)
1181
+ const firstWord = lowerContent.split(/\s+/)[0];
1182
+ if (firstWord && USER_INTENT_VERBS.some(v => firstWord.startsWith(v))) {
1183
+ return true;
1184
+ }
1185
+ // If the message ends with a question mark and isn't too long, likely a user question
1186
+ if (trimmed.endsWith('?') && trimmed.length < 200) {
1187
+ return true;
1188
+ }
1189
+ // Default: if it passed all negative filters but no strong positive signal,
1190
+ // only accept if it reads like natural language (high word/char ratio, few special chars)
1191
+ const words = trimmed.split(/\s+/).filter(w => w.length > 0);
1192
+ const avgWordLength = words.reduce((sum, w) => sum + w.length, 0) / words.length;
1193
+ // Natural language typically has average word length 4-8
1194
+ if (avgWordLength >= 3 && avgWordLength <= 10 && words.length >= 3 && specialCharRatio < 0.05) {
1195
+ return true;
1196
+ }
1197
+ return false;
1198
+ }
1199
+ looksLikeCode(content) {
1200
+ const codeIndicators = [
1201
+ /^(import|export|const|let|var|function|class|interface|type)\s/m,
1202
+ /[{};]\s*$/m,
1203
+ /^\s*(if|else|for|while|switch|try|catch)\s*\(/m,
1204
+ /=>\s*{/,
1205
+ /\(\)\s*{/,
1206
+ /^\s*return\s/m,
1207
+ /^\s*@\w+/m, // Decorators
1208
+ /^\s*#include/m,
1209
+ /^\s*#define/m,
1210
+ /^\s*#pragma/m,
1211
+ /\{\{.*\}\}/, // Template syntax
1212
+ /^\s*<\w+\s+/m, // JSX/HTML tags
1213
+ /<\/\w+>\s*$/m, // Closing tags
1214
+ ];
1215
+ for (const pattern of codeIndicators) {
1216
+ if (pattern.test(content)) {
1217
+ return true;
1218
+ }
1219
+ }
1220
+ // Check for high density of brackets/braces
1221
+ const brackets = (content.match(/[{}\[\]()<>]/g) || []).length;
1222
+ if (brackets > content.length * 0.1) {
1223
+ return true;
1224
+ }
1225
+ return false;
1226
+ }
1227
+ looksLikeFilePath(content) {
1228
+ const trimmed = content.trim();
1229
+ // Check for file path patterns
1230
+ if (/^(\/|~\/|\.\/|\.\.\/)/.test(trimmed))
1231
+ return true;
1232
+ if (/^[a-zA-Z]:\\/.test(trimmed))
1233
+ return true; // Windows
1234
+ if (/^\w+\/\w+/.test(trimmed) && trimmed.includes('/'))
1235
+ return true; // Relative paths
1236
+ // Check if most of the content is path-like
1237
+ const pathChars = (trimmed.match(/[\/\\._-]/g) || []).length;
1238
+ if (pathChars > trimmed.length * 0.2 && trimmed.length < 200) {
1239
+ return true;
1240
+ }
1241
+ return false;
1242
+ }
1243
+ // --------------------------------------------------------------------------
1244
+ // State Management
1245
+ // --------------------------------------------------------------------------
1246
+ getStatePath() {
1247
+ return path.join(this.claudeDir.root, ADVISOR_STATE_FILE);
1248
+ }
1249
+ async getState() {
1250
+ try {
1251
+ const content = await fs.readFile(this.getStatePath(), 'utf-8');
1252
+ return JSON.parse(content);
1253
+ }
1254
+ catch {
1255
+ return {
1256
+ recommendations: [],
1257
+ lastAnalyzedAt: null,
1258
+ dismissedIds: [],
1259
+ appliedIds: [],
1260
+ };
1261
+ }
1262
+ }
1263
+ async saveState(state) {
1264
+ await fs.writeFile(this.getStatePath(), JSON.stringify(state, null, 2));
1265
+ }
1266
+ // --------------------------------------------------------------------------
1267
+ // Public API
1268
+ // --------------------------------------------------------------------------
1269
+ async getRecommendations() {
1270
+ const state = await this.getState();
1271
+ return state.recommendations.filter(r => r.status === 'pending' && !state.dismissedIds.includes(r.id));
1272
+ }
1273
+ async getAllRecommendations() {
1274
+ const state = await this.getState();
1275
+ return state.recommendations;
1276
+ }
1277
+ async dismissRecommendation(id) {
1278
+ const state = await this.getState();
1279
+ const rec = state.recommendations.find(r => r.id === id);
1280
+ if (rec) {
1281
+ rec.status = 'dismissed';
1282
+ rec.dismissedAt = new Date().toISOString();
1283
+ if (!state.dismissedIds.includes(id)) {
1284
+ state.dismissedIds.push(id);
1285
+ }
1286
+ await this.saveState(state);
1287
+ }
1288
+ }
1289
+ async applyRecommendation(id) {
1290
+ const state = await this.getState();
1291
+ const rec = state.recommendations.find(r => r.id === id);
1292
+ if (!rec) {
1293
+ return { success: false, error: 'Recommendation not found' };
1294
+ }
1295
+ try {
1296
+ switch (rec.type) {
1297
+ case 'skill':
1298
+ await this.applySkillRecommendation(rec);
1299
+ break;
1300
+ case 'agent':
1301
+ await this.applyAgentRecommendation(rec);
1302
+ break;
1303
+ case 'hook':
1304
+ await this.applyHookRecommendation(rec);
1305
+ break;
1306
+ case 'claude-md':
1307
+ await this.applyClaudeMdRecommendation(rec);
1308
+ break;
1309
+ case 'mcp-server':
1310
+ // MCP recommendations are informational - user must manually configure
1311
+ // We just mark it as applied
1312
+ break;
1313
+ case 'settings':
1314
+ // Settings recommendations are informational for now
1315
+ break;
1316
+ case 'model':
1317
+ // Model recommendations are informational only
1318
+ break;
1319
+ }
1320
+ rec.status = 'applied';
1321
+ rec.appliedAt = new Date().toISOString();
1322
+ if (!state.appliedIds.includes(id)) {
1323
+ state.appliedIds.push(id);
1324
+ }
1325
+ await this.saveState(state);
1326
+ return { success: true };
1327
+ }
1328
+ catch (err) {
1329
+ return { success: false, error: String(err) };
1330
+ }
1331
+ }
1332
+ async runAnalysis(force = false) {
1333
+ const state = await this.getState();
1334
+ // Check if we should run (not more than once per hour unless forced)
1335
+ if (!force && state.lastAnalyzedAt) {
1336
+ const lastRun = new Date(state.lastAnalyzedAt).getTime();
1337
+ const hourAgo = Date.now() - 60 * 60 * 1000;
1338
+ if (lastRun > hourAgo) {
1339
+ return {
1340
+ newRecommendations: 0,
1341
+ totalRecommendations: state.recommendations.filter(r => r.status === 'pending').length,
1342
+ };
1343
+ }
1344
+ }
1345
+ console.log('[Advisor] Starting analysis...');
1346
+ const startTime = Date.now();
1347
+ // Gather data
1348
+ const cutoffDate = new Date();
1349
+ cutoffDate.setDate(cutoffDate.getDate() - this.config.lookbackDays);
1350
+ const cutoffStr = cutoffDate.toISOString();
1351
+ const { sessions } = await this.sessionService.listSessions({ limit: 500 });
1352
+ const recentSessions = sessions.filter(s => s.startedAt >= cutoffStr);
1353
+ console.log(`[Advisor] Analyzing ${recentSessions.length} sessions from last ${this.config.lookbackDays} days`);
1354
+ // Load full session details for analysis
1355
+ const sessionDetails = await Promise.all(recentSessions.slice(0, 100).map(s => this.sessionService.getSession(s.id)));
1356
+ const validSessions = sessionDetails.filter(s => s !== null);
1357
+ // Run all detectors
1358
+ const newRecommendations = [];
1359
+ // 1. Repeated prompts → Skills
1360
+ const promptPatterns = this.detectRepeatedPrompts(validSessions);
1361
+ console.log(`[Advisor] Found ${promptPatterns.length} potential prompt patterns`);
1362
+ for (const pattern of promptPatterns) {
1363
+ if (pattern.count >= this.config.minPromptOccurrences) {
1364
+ const rec = this.createSkillRecommendation(pattern);
1365
+ if (rec && !this.isDuplicate(state.recommendations, rec)) {
1366
+ newRecommendations.push(rec);
1367
+ }
1368
+ }
1369
+ }
1370
+ // 2. Error patterns → Hooks (simplified for now)
1371
+ // Planned for v0.2.0: hook detection — analyze error patterns to recommend hooks
1372
+ // 3. Clarification loops → CLAUDE.md (simplified for now)
1373
+ // Planned for v0.2.0: clarification detection — identify repeated clarifications to recommend CLAUDE.md entries
1374
+ // Merge new recommendations
1375
+ state.recommendations.push(...newRecommendations);
1376
+ state.lastAnalyzedAt = new Date().toISOString();
1377
+ await this.saveState(state);
1378
+ const elapsed = Date.now() - startTime;
1379
+ console.log(`[Advisor] Analysis complete in ${elapsed}ms. Found ${newRecommendations.length} new recommendations.`);
1380
+ return {
1381
+ newRecommendations: newRecommendations.length,
1382
+ totalRecommendations: state.recommendations.filter(r => r.status === 'pending').length,
1383
+ };
1384
+ }
1385
+ // --------------------------------------------------------------------------
1386
+ // Pattern Detectors
1387
+ // --------------------------------------------------------------------------
1388
+ detectRepeatedPrompts(sessions) {
1389
+ const patternMap = new Map();
1390
+ let totalChecked = 0;
1391
+ let totalAccepted = 0;
1392
+ for (const session of sessions) {
1393
+ for (const msg of session.messages || []) {
1394
+ if (msg.type !== 'user')
1395
+ continue;
1396
+ const content = msg.content;
1397
+ totalChecked++;
1398
+ // Use the intelligent filter
1399
+ if (!this.isGenuineUserRequest(content)) {
1400
+ continue;
1401
+ }
1402
+ totalAccepted++;
1403
+ const normalized = this.normalizePrompt(content);
1404
+ if (normalized.length < 10)
1405
+ continue;
1406
+ const fingerprint = this.generateFingerprint(normalized);
1407
+ const keywords = this.extractKeywords(content);
1408
+ // Additional keyword quality check
1409
+ const goodKeywords = keywords.filter(kw => this.isGoodKeyword(kw));
1410
+ if (goodKeywords.length < 2)
1411
+ continue;
1412
+ const existing = patternMap.get(fingerprint);
1413
+ if (existing) {
1414
+ existing.count++;
1415
+ if (existing.examples.length < 5) {
1416
+ existing.examples.push(content.slice(0, 300));
1417
+ }
1418
+ existing.timestamps.push(msg.timestamp);
1419
+ existing.sessions.add(session.id);
1420
+ // Merge keywords
1421
+ for (const kw of goodKeywords) {
1422
+ if (!existing.keywords.includes(kw)) {
1423
+ existing.keywords.push(kw);
1424
+ }
1425
+ }
1426
+ }
1427
+ else {
1428
+ patternMap.set(fingerprint, {
1429
+ fingerprint,
1430
+ normalizedText: normalized,
1431
+ keywords: goodKeywords.slice(0, 10),
1432
+ count: 1,
1433
+ examples: [content.slice(0, 300)],
1434
+ timestamps: [msg.timestamp],
1435
+ sessions: new Set([session.id]),
1436
+ projectPaths: new Set([session.projectPath]),
1437
+ });
1438
+ }
1439
+ }
1440
+ }
1441
+ console.log(`[Advisor] Checked ${totalChecked} user messages, accepted ${totalAccepted} as genuine requests`);
1442
+ // Also check for similar patterns (not exact match)
1443
+ const patterns = Array.from(patternMap.values());
1444
+ const merged = this.mergeSimilarPatterns(patterns);
1445
+ return merged
1446
+ .filter(p => p.count >= 2 && p.sessions.size >= 2)
1447
+ .sort((a, b) => b.count - a.count)
1448
+ .slice(0, 50); // Limit to top 50
1449
+ }
1450
+ // --------------------------------------------------------------------------
1451
+ // Agent Opportunity Detection
1452
+ // --------------------------------------------------------------------------
1453
+ detectAgentOpportunities(sessions) {
1454
+ const taskCategories = new Map();
1455
+ for (const session of sessions) {
1456
+ for (const msg of session.messages || []) {
1457
+ if (msg.type !== 'user')
1458
+ continue;
1459
+ const content = (msg.content || '').toLowerCase();
1460
+ if (!this.isGenuineUserRequest(msg.content || ''))
1461
+ continue;
1462
+ // Check each agent template for matching triggers
1463
+ for (const template of AGENT_KNOWLEDGE_BASE) {
1464
+ for (const trigger of template.triggers) {
1465
+ if (content.includes(trigger.toLowerCase())) {
1466
+ const existing = taskCategories.get(template.name);
1467
+ if (existing) {
1468
+ existing.count++;
1469
+ existing.sessions.add(session.id);
1470
+ if (existing.examples.length < 5) {
1471
+ existing.examples.push((msg.content || '').slice(0, 150));
1472
+ }
1473
+ }
1474
+ else {
1475
+ taskCategories.set(template.name, {
1476
+ agentName: template.name,
1477
+ category: template.category,
1478
+ count: 1,
1479
+ sessions: new Set([session.id]),
1480
+ examples: [(msg.content || '').slice(0, 150)],
1481
+ triggers: template.triggers,
1482
+ });
1483
+ }
1484
+ break; // Only count once per template per message
1485
+ }
1486
+ }
1487
+ }
1488
+ }
1489
+ }
1490
+ console.log(`[Advisor] Found ${taskCategories.size} agent opportunity patterns`);
1491
+ // Return patterns that meet threshold
1492
+ return Array.from(taskCategories.values())
1493
+ .filter(p => p.count >= 3 && p.sessions.size >= 2)
1494
+ .sort((a, b) => b.count - a.count);
1495
+ }
1496
+ // --------------------------------------------------------------------------
1497
+ // CLAUDE.md Opportunity Detection
1498
+ // --------------------------------------------------------------------------
1499
+ detectClaudeMdOpportunities(sessions) {
1500
+ const contextStatements = new Map();
1501
+ for (const session of sessions) {
1502
+ for (const msg of session.messages || []) {
1503
+ if (msg.type !== 'user')
1504
+ continue;
1505
+ const content = msg.content || '';
1506
+ // Check against CLAUDE.md patterns
1507
+ for (const pattern of CLAUDE_MD_PATTERNS) {
1508
+ const match = content.match(pattern.pattern);
1509
+ if (match) {
1510
+ const key = `${pattern.section}:${match[0].toLowerCase()}`;
1511
+ const existing = contextStatements.get(key);
1512
+ if (existing) {
1513
+ existing.count++;
1514
+ if (existing.examples.length < 5) {
1515
+ existing.examples.push(content.slice(0, 150));
1516
+ }
1517
+ }
1518
+ else {
1519
+ contextStatements.set(key, {
1520
+ count: 1,
1521
+ examples: [content.slice(0, 150)],
1522
+ category: pattern.category,
1523
+ section: pattern.section,
1524
+ content: pattern.extractContent(match, content),
1525
+ });
1526
+ }
1527
+ }
1528
+ }
1529
+ // Detect repeated context statements
1530
+ // Pattern: "we use X", "our Y is Z", "always/never do X"
1531
+ const contextPatterns = [
1532
+ /\bwe\s+use\s+(.+?)(?:\s+for|\s+to|\s+in|\s*$)/i,
1533
+ /\bour\s+(\w+)\s+is\s+(.+?)(?:\s+and|\s*$)/i,
1534
+ /\balways\s+(.+?)(?:\s+when|\s*$)/i,
1535
+ /\bnever\s+(.+?)(?:\s+because|\s*$)/i,
1536
+ ];
1537
+ for (const pattern of contextPatterns) {
1538
+ const match = content.match(pattern);
1539
+ if (match && match[0].length < 100) {
1540
+ const key = `context:${match[0].toLowerCase().slice(0, 50)}`;
1541
+ const existing = contextStatements.get(key);
1542
+ if (existing) {
1543
+ existing.count++;
1544
+ if (existing.examples.length < 5) {
1545
+ existing.examples.push(content.slice(0, 150));
1546
+ }
1547
+ }
1548
+ else {
1549
+ contextStatements.set(key, {
1550
+ count: 1,
1551
+ examples: [content.slice(0, 150)],
1552
+ category: 'context',
1553
+ section: '## Project Context',
1554
+ content: match[0],
1555
+ });
1556
+ }
1557
+ }
1558
+ }
1559
+ }
1560
+ }
1561
+ console.log(`[Advisor] Found ${contextStatements.size} CLAUDE.md opportunity patterns`);
1562
+ // Convert to ClaudeMdPattern and filter by threshold
1563
+ const patterns = [];
1564
+ for (const [key, data] of contextStatements.entries()) {
1565
+ if (data.count >= 2) {
1566
+ patterns.push({
1567
+ key,
1568
+ category: data.category,
1569
+ count: data.count,
1570
+ examples: data.examples,
1571
+ suggestedSection: data.section,
1572
+ suggestedContent: data.content,
1573
+ });
1574
+ }
1575
+ }
1576
+ return patterns.sort((a, b) => b.count - a.count).slice(0, 20);
1577
+ }
1578
+ // --------------------------------------------------------------------------
1579
+ // Hook Opportunity Detection
1580
+ // --------------------------------------------------------------------------
1581
+ detectHookOpportunities(sessions) {
1582
+ const hookPatterns = [];
1583
+ const errorCounts = new Map();
1584
+ for (const session of sessions) {
1585
+ for (const msg of session.messages || []) {
1586
+ // Look for tool errors and failures
1587
+ if (msg.type === 'tool-result' && msg.content) {
1588
+ const content = msg.content.toLowerCase();
1589
+ // Detect lint/format errors
1590
+ if (content.includes('eslint') || content.includes('lint error') || content.includes('prettier')) {
1591
+ const key = 'lint-errors';
1592
+ const existing = errorCounts.get(key);
1593
+ if (existing) {
1594
+ existing.count++;
1595
+ if (existing.examples.length < 3)
1596
+ existing.examples.push(msg.content.slice(0, 100));
1597
+ }
1598
+ else {
1599
+ errorCounts.set(key, { count: 1, examples: [msg.content.slice(0, 100)] });
1600
+ }
1601
+ }
1602
+ // Detect type errors
1603
+ if (content.includes('type error') || content.includes('typescript') || content.includes('tsc')) {
1604
+ const key = 'type-errors';
1605
+ const existing = errorCounts.get(key);
1606
+ if (existing) {
1607
+ existing.count++;
1608
+ if (existing.examples.length < 3)
1609
+ existing.examples.push(msg.content.slice(0, 100));
1610
+ }
1611
+ else {
1612
+ errorCounts.set(key, { count: 1, examples: [msg.content.slice(0, 100)] });
1613
+ }
1614
+ }
1615
+ // Detect test failures
1616
+ if (content.includes('test failed') || content.includes('failing test') || content.includes('jest')) {
1617
+ const key = 'test-failures';
1618
+ const existing = errorCounts.get(key);
1619
+ if (existing) {
1620
+ existing.count++;
1621
+ if (existing.examples.length < 3)
1622
+ existing.examples.push(msg.content.slice(0, 100));
1623
+ }
1624
+ else {
1625
+ errorCounts.set(key, { count: 1, examples: [msg.content.slice(0, 100)] });
1626
+ }
1627
+ }
1628
+ // Detect build errors
1629
+ if (content.includes('build failed') || content.includes('compilation error') || content.includes('bundle error')) {
1630
+ const key = 'build-errors';
1631
+ const existing = errorCounts.get(key);
1632
+ if (existing) {
1633
+ existing.count++;
1634
+ if (existing.examples.length < 3)
1635
+ existing.examples.push(msg.content.slice(0, 100));
1636
+ }
1637
+ else {
1638
+ errorCounts.set(key, { count: 1, examples: [msg.content.slice(0, 100)] });
1639
+ }
1640
+ }
1641
+ }
1642
+ }
1643
+ }
1644
+ console.log(`[Advisor] Found ${errorCounts.size} hook opportunity patterns`);
1645
+ // Map error patterns to hook templates
1646
+ const errorToHook = {
1647
+ 'lint-errors': 'lint-on-edit',
1648
+ 'type-errors': 'type-check-on-edit',
1649
+ 'test-failures': 'test-related-on-edit',
1650
+ 'build-errors': 'build-check',
1651
+ };
1652
+ for (const [errorKey, data] of errorCounts.entries()) {
1653
+ if (data.count >= 3) {
1654
+ const hookName = errorToHook[errorKey];
1655
+ const template = HOOK_KNOWLEDGE_BASE.find(h => h.name === hookName);
1656
+ if (template) {
1657
+ hookPatterns.push({
1658
+ event: template.event,
1659
+ matcher: template.matcher,
1660
+ command: template.command,
1661
+ reason: `Detected ${data.count} ${errorKey.replace('-', ' ')} across sessions`,
1662
+ count: data.count,
1663
+ examples: data.examples,
1664
+ });
1665
+ }
1666
+ }
1667
+ }
1668
+ return hookPatterns;
1669
+ }
1670
+ // --------------------------------------------------------------------------
1671
+ // MCP Server Opportunity Detection
1672
+ // --------------------------------------------------------------------------
1673
+ detectMCPOpportunities(sessions) {
1674
+ const mcpOpportunities = new Map();
1675
+ for (const session of sessions) {
1676
+ for (const msg of session.messages || []) {
1677
+ if (msg.type !== 'user')
1678
+ continue;
1679
+ const content = (msg.content || '').toLowerCase();
1680
+ // Check each MCP template for matching triggers
1681
+ for (const template of MCP_KNOWLEDGE_BASE) {
1682
+ for (const trigger of template.triggers) {
1683
+ if (content.includes(trigger.toLowerCase())) {
1684
+ const existing = mcpOpportunities.get(template.name);
1685
+ if (existing) {
1686
+ existing.count++;
1687
+ if (existing.examples.length < 5) {
1688
+ existing.examples.push((msg.content || '').slice(0, 150));
1689
+ }
1690
+ }
1691
+ else {
1692
+ mcpOpportunities.set(template.name, {
1693
+ count: 1,
1694
+ examples: [(msg.content || '').slice(0, 150)],
1695
+ });
1696
+ }
1697
+ break;
1698
+ }
1699
+ }
1700
+ }
1701
+ }
1702
+ }
1703
+ console.log(`[Advisor] Found ${mcpOpportunities.size} MCP opportunity patterns`);
1704
+ // Convert to MCPPattern and filter
1705
+ const patterns = [];
1706
+ for (const [serverName, data] of mcpOpportunities.entries()) {
1707
+ const template = MCP_KNOWLEDGE_BASE.find(t => t.name === serverName);
1708
+ if (data.count >= 3 && template) {
1709
+ patterns.push({
1710
+ serverName,
1711
+ triggers: template.triggers,
1712
+ count: data.count,
1713
+ examples: data.examples,
1714
+ });
1715
+ }
1716
+ }
1717
+ return patterns.sort((a, b) => b.count - a.count);
1718
+ }
1719
+ // --------------------------------------------------------------------------
1720
+ // Keyword Quality Check
1721
+ // --------------------------------------------------------------------------
1722
+ isGoodKeyword(keyword) {
1723
+ // Reject keywords that are:
1724
+ // - Too short
1725
+ if (keyword.length < 3)
1726
+ return false;
1727
+ // - Numbers or hex strings
1728
+ if (/^[0-9a-f]+$/i.test(keyword))
1729
+ return false;
1730
+ // - File extensions
1731
+ if (/^\.\w+$/.test(keyword))
1732
+ return false;
1733
+ // - Common technical noise
1734
+ const noise = new Set([
1735
+ 'const', 'let', 'var', 'function', 'class', 'type', 'interface',
1736
+ 'import', 'export', 'from', 'return', 'true', 'false', 'null', 'undefined',
1737
+ 'string', 'number', 'boolean', 'object', 'array', 'void',
1738
+ 'async', 'await', 'try', 'catch', 'throw', 'new', 'delete',
1739
+ 'public', 'private', 'protected', 'static', 'readonly',
1740
+ 'josharsh', 'users', 'development', 'staff', 'drwxr', 'drwx', // User-specific noise
1741
+ 'node_modules', 'dist', 'build', 'src', 'lib', 'bin',
1742
+ 'json', 'yaml', 'toml', 'xml', 'html', 'css', 'tsx', 'jsx',
1743
+ 'http', 'https', 'localhost', 'port',
1744
+ 'todo', 'fixme', 'hack', 'xxx', 'note',
1745
+ 'code', 'file', 'path', 'dir', 'folder',
1746
+ 'successfully', 'error', 'warning', 'info', 'debug',
1747
+ 'total', 'count', 'index', 'length', 'size',
1748
+ ]);
1749
+ if (noise.has(keyword.toLowerCase()))
1750
+ return false;
1751
+ // - Paths or path segments
1752
+ if (keyword.includes('/') || keyword.includes('\\'))
1753
+ return false;
1754
+ // - Looks like a hash or UUID
1755
+ if (/^[a-f0-9]{8,}$/i.test(keyword))
1756
+ return false;
1757
+ return true;
1758
+ }
1759
+ // --------------------------------------------------------------------------
1760
+ // Recommendation Creators
1761
+ // --------------------------------------------------------------------------
1762
+ createAgentRecommendation(pattern) {
1763
+ const template = AGENT_KNOWLEDGE_BASE.find(t => t.name === pattern.agentName);
1764
+ if (!template)
1765
+ return null;
1766
+ return {
1767
+ id: `agent-${crypto.randomUUID().slice(0, 8)}`,
1768
+ type: 'agent',
1769
+ title: `Create "${pattern.agentName}" agent`,
1770
+ description: `You've performed ${pattern.category}-related tasks ${pattern.count} times across ${pattern.sessions.size} sessions. A dedicated agent could handle this workflow.`,
1771
+ confidence: Math.min(0.95, 0.6 + (pattern.count - 3) * 0.05 + (pattern.sessions.size - 2) * 0.1),
1772
+ evidence: [
1773
+ `Found ${pattern.count} ${pattern.category} task requests`,
1774
+ `Across ${pattern.sessions.size} different sessions`,
1775
+ `Category: ${pattern.category}`,
1776
+ ],
1777
+ action: {
1778
+ type: 'agent',
1779
+ agentName: pattern.agentName,
1780
+ agentContent: template.content,
1781
+ template: template.name,
1782
+ },
1783
+ examples: pattern.examples,
1784
+ createdAt: new Date().toISOString(),
1785
+ status: 'pending',
1786
+ };
1787
+ }
1788
+ createClaudeMdRecommendation(pattern) {
1789
+ return {
1790
+ id: `claude-md-${crypto.randomUUID().slice(0, 8)}`,
1791
+ type: 'claude-md',
1792
+ title: `Add to CLAUDE.md: ${pattern.suggestedSection}`,
1793
+ description: `You've mentioned "${pattern.suggestedContent}" ${pattern.count} times. Adding it to CLAUDE.md will save you from repeating this context.`,
1794
+ confidence: Math.min(0.9, 0.5 + pattern.count * 0.1),
1795
+ evidence: [
1796
+ `Mentioned ${pattern.count} times`,
1797
+ `Category: ${pattern.category}`,
1798
+ `Example: "${pattern.examples[0]?.slice(0, 80)}..."`,
1799
+ ],
1800
+ action: {
1801
+ type: 'claude-md',
1802
+ section: pattern.suggestedSection,
1803
+ addition: pattern.suggestedContent,
1804
+ },
1805
+ examples: pattern.examples,
1806
+ createdAt: new Date().toISOString(),
1807
+ status: 'pending',
1808
+ };
1809
+ }
1810
+ createHookRecommendation(pattern) {
1811
+ return {
1812
+ id: `hook-${crypto.randomUUID().slice(0, 8)}`,
1813
+ type: 'hook',
1814
+ title: `Add "${pattern.event}" hook`,
1815
+ description: pattern.reason,
1816
+ confidence: Math.min(0.85, 0.5 + pattern.count * 0.1),
1817
+ evidence: [
1818
+ `Detected ${pattern.count} times`,
1819
+ `Event: ${pattern.event}`,
1820
+ pattern.matcher ? `Matcher: ${pattern.matcher}` : 'No matcher',
1821
+ ],
1822
+ action: {
1823
+ type: 'hook',
1824
+ event: pattern.event,
1825
+ matcher: pattern.matcher,
1826
+ command: pattern.command,
1827
+ },
1828
+ examples: pattern.examples,
1829
+ createdAt: new Date().toISOString(),
1830
+ status: 'pending',
1831
+ };
1832
+ }
1833
+ createMCPRecommendation(pattern) {
1834
+ const template = MCP_KNOWLEDGE_BASE.find(t => t.name === pattern.serverName);
1835
+ if (!template)
1836
+ return null;
1837
+ return {
1838
+ id: `mcp-${crypto.randomUUID().slice(0, 8)}`,
1839
+ type: 'mcp-server',
1840
+ title: `Add "${pattern.serverName}" MCP server`,
1841
+ description: `You've referenced ${pattern.serverName}-related functionality ${pattern.count} times. An MCP server could provide native integration.`,
1842
+ confidence: Math.min(0.8, 0.4 + pattern.count * 0.1),
1843
+ evidence: [
1844
+ `Mentioned ${pattern.count} times`,
1845
+ `Triggers: ${pattern.triggers.slice(0, 3).join(', ')}`,
1846
+ template.npmPackage ? `Package: ${template.npmPackage}` : 'Custom server needed',
1847
+ ],
1848
+ action: {
1849
+ type: 'mcp-server',
1850
+ serverName: pattern.serverName,
1851
+ description: template.description,
1852
+ setupInstructions: template.setupInstructions,
1853
+ npmPackage: template.npmPackage,
1854
+ },
1855
+ examples: pattern.examples,
1856
+ createdAt: new Date().toISOString(),
1857
+ status: 'pending',
1858
+ };
1859
+ }
1860
+ createSkillRecommendation(pattern) {
1861
+ if (pattern.keywords.length < 2)
1862
+ return null;
1863
+ const skillName = this.generateSkillName(pattern.keywords);
1864
+ // Skip if skill name is too generic or looks bad
1865
+ if (skillName.length < 5 || skillName.includes('file') || skillName.includes('users')) {
1866
+ return null;
1867
+ }
1868
+ const skillContent = this.generateSkillContent(pattern);
1869
+ return {
1870
+ id: `skill-${crypto.randomUUID().slice(0, 8)}`,
1871
+ type: 'skill',
1872
+ title: `Create "${skillName}" skill`,
1873
+ description: `You've asked similar questions ${pattern.count} times across ${pattern.sessions.size} sessions. Creating a skill would let you invoke this workflow with /${skillName}.`,
1874
+ confidence: Math.min(0.95, 0.5 + (pattern.count - 2) * 0.05 + (pattern.sessions.size - 1) * 0.1),
1875
+ evidence: [
1876
+ `Found ${pattern.count} similar prompts`,
1877
+ `Across ${pattern.sessions.size} different sessions`,
1878
+ `Keywords: ${pattern.keywords.slice(0, 5).join(', ')}`,
1879
+ ],
1880
+ action: {
1881
+ type: 'skill',
1882
+ skillName,
1883
+ skillContent,
1884
+ },
1885
+ examples: pattern.examples,
1886
+ createdAt: new Date().toISOString(),
1887
+ status: 'pending',
1888
+ };
1889
+ }
1890
+ // --------------------------------------------------------------------------
1891
+ // Proactive Recommendations (Knowledge-Based)
1892
+ // --------------------------------------------------------------------------
1893
+ /**
1894
+ * Detects the tech stack from user's projects
1895
+ */
1896
+ async detectTechStack() {
1897
+ const techStack = new Set();
1898
+ const projects = await this.sessionService.listProjects();
1899
+ for (const project of projects.slice(0, 10)) { // Check top 10 projects
1900
+ const projectPath = project.path;
1901
+ if (!projectPath || projectPath === 'unknown')
1902
+ continue;
1903
+ try {
1904
+ // Check for package.json (Node.js/JS projects)
1905
+ try {
1906
+ const pkgPath = path.join(projectPath, 'package.json');
1907
+ const pkgContent = await fs.readFile(pkgPath, 'utf-8');
1908
+ const pkg = JSON.parse(pkgContent);
1909
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1910
+ // Detect frameworks
1911
+ if (deps['next'])
1912
+ techStack.add('nextjs');
1913
+ if (deps['react'])
1914
+ techStack.add('react');
1915
+ if (deps['vue'])
1916
+ techStack.add('vue');
1917
+ if (deps['svelte'])
1918
+ techStack.add('svelte');
1919
+ if (deps['astro'])
1920
+ techStack.add('astro');
1921
+ if (deps['vite'])
1922
+ techStack.add('vite');
1923
+ if (deps['express'])
1924
+ techStack.add('express');
1925
+ if (deps['fastify'])
1926
+ techStack.add('fastify');
1927
+ if (deps['hono'])
1928
+ techStack.add('hono');
1929
+ if (deps['typescript'])
1930
+ techStack.add('typescript');
1931
+ if (deps['jest'] || deps['vitest'])
1932
+ techStack.add('testing');
1933
+ if (deps['eslint'])
1934
+ techStack.add('eslint');
1935
+ if (deps['playwright'])
1936
+ techStack.add('playwright');
1937
+ techStack.add('node');
1938
+ techStack.add('javascript');
1939
+ }
1940
+ catch { }
1941
+ // Check for Python
1942
+ try {
1943
+ await fs.access(path.join(projectPath, 'pyproject.toml'));
1944
+ techStack.add('python');
1945
+ }
1946
+ catch { }
1947
+ try {
1948
+ await fs.access(path.join(projectPath, 'requirements.txt'));
1949
+ techStack.add('python');
1950
+ }
1951
+ catch { }
1952
+ // Check for Rust
1953
+ try {
1954
+ await fs.access(path.join(projectPath, 'Cargo.toml'));
1955
+ techStack.add('rust');
1956
+ }
1957
+ catch { }
1958
+ // Check for Go
1959
+ try {
1960
+ await fs.access(path.join(projectPath, 'go.mod'));
1961
+ techStack.add('go');
1962
+ }
1963
+ catch { }
1964
+ // Check for Vercel/Netlify
1965
+ try {
1966
+ await fs.access(path.join(projectPath, 'vercel.json'));
1967
+ techStack.add('vercel');
1968
+ }
1969
+ catch { }
1970
+ try {
1971
+ await fs.access(path.join(projectPath, 'netlify.toml'));
1972
+ techStack.add('netlify');
1973
+ }
1974
+ catch { }
1975
+ }
1976
+ catch {
1977
+ // Project path not accessible
1978
+ }
1979
+ }
1980
+ return techStack;
1981
+ }
1982
+ /**
1983
+ * Analyzes usage patterns to find relevant triggers
1984
+ */
1985
+ analyzeUsagePatterns(sessions) {
1986
+ const triggerCounts = new Map();
1987
+ for (const session of sessions) {
1988
+ for (const msg of session.messages || []) {
1989
+ if (msg.type !== 'user')
1990
+ continue;
1991
+ const content = (msg.content || '').toLowerCase();
1992
+ // Count trigger words
1993
+ for (const template of SKILL_KNOWLEDGE_BASE) {
1994
+ for (const trigger of template.triggers) {
1995
+ if (content.includes(trigger)) {
1996
+ const key = template.name;
1997
+ triggerCounts.set(key, (triggerCounts.get(key) || 0) + 1);
1998
+ }
1999
+ }
2000
+ }
2001
+ }
2002
+ }
2003
+ return triggerCounts;
2004
+ }
2005
+ /**
2006
+ * Gets list of skills the user already has
2007
+ */
2008
+ async getExistingSkills() {
2009
+ try {
2010
+ const skills = await this.skillsService.listSkills();
2011
+ return new Set(skills.map(s => s.name.toLowerCase()));
2012
+ }
2013
+ catch {
2014
+ return new Set();
2015
+ }
2016
+ }
2017
+ /**
2018
+ * Gets list of agents the user already has
2019
+ */
2020
+ async getExistingAgents() {
2021
+ try {
2022
+ const agents = await this.agentsService.listAgents();
2023
+ return new Set(agents.map(a => a.name.toLowerCase()));
2024
+ }
2025
+ catch {
2026
+ return new Set();
2027
+ }
2028
+ }
2029
+ /**
2030
+ * Generate proactive recommendations based on tech stack and best practices
2031
+ */
2032
+ async generateProactiveRecommendations(sessions, existingRecommendationNames) {
2033
+ console.log('[Advisor] Generating proactive recommendations...');
2034
+ const techStack = await this.detectTechStack();
2035
+ console.log(`[Advisor] Detected tech stack: ${Array.from(techStack).join(', ')}`);
2036
+ const triggerCounts = this.analyzeUsagePatterns(sessions);
2037
+ const existingSkills = await this.getExistingSkills();
2038
+ const recommendations = [];
2039
+ for (const template of SKILL_KNOWLEDGE_BASE) {
2040
+ // Skip if user already has this skill
2041
+ if (existingSkills.has(template.name.toLowerCase())) {
2042
+ continue;
2043
+ }
2044
+ // Skip if we already recommended this
2045
+ if (existingRecommendationNames.has(template.name.toLowerCase())) {
2046
+ continue;
2047
+ }
2048
+ // Check if relevant to user's tech stack
2049
+ const isRelevant = template.techStack.includes('*') ||
2050
+ template.techStack.some(tech => techStack.has(tech));
2051
+ if (!isRelevant)
2052
+ continue;
2053
+ // Calculate confidence based on trigger matches
2054
+ const triggerCount = triggerCounts.get(template.name) || 0;
2055
+ let confidence = template.confidence;
2056
+ // Boost confidence if user has mentioned related triggers
2057
+ if (triggerCount > 0) {
2058
+ confidence = Math.min(0.98, confidence + Math.min(0.2, triggerCount * 0.02));
2059
+ }
2060
+ // Determine evidence
2061
+ const evidence = [];
2062
+ if (techStack.size > 0) {
2063
+ const relevantTech = template.techStack.filter(t => techStack.has(t));
2064
+ if (relevantTech.length > 0) {
2065
+ evidence.push(`Matches your tech stack: ${relevantTech.join(', ')}`);
2066
+ }
2067
+ }
2068
+ if (triggerCount > 0) {
2069
+ evidence.push(`You've mentioned related topics ${triggerCount} times`);
2070
+ }
2071
+ evidence.push(`Category: ${template.category}`);
2072
+ evidence.push('Recommended best practice');
2073
+ // Only include high-relevance recommendations
2074
+ if (confidence >= 0.7 || triggerCount >= 3) {
2075
+ recommendations.push({
2076
+ id: `skill-proactive-${crypto.randomUUID().slice(0, 8)}`,
2077
+ type: 'skill',
2078
+ title: `Add "${template.name}" skill`,
2079
+ description: template.description,
2080
+ confidence,
2081
+ evidence,
2082
+ action: {
2083
+ type: 'skill',
2084
+ skillName: template.name,
2085
+ skillContent: template.content,
2086
+ },
2087
+ examples: [], // No examples for proactive recommendations
2088
+ createdAt: new Date().toISOString(),
2089
+ status: 'pending',
2090
+ });
2091
+ }
2092
+ }
2093
+ // Sort by confidence
2094
+ recommendations.sort((a, b) => b.confidence - a.confidence);
2095
+ console.log(`[Advisor] Generated ${recommendations.length} proactive recommendations`);
2096
+ return recommendations.slice(0, 10); // Limit to top 10
2097
+ }
2098
+ // --------------------------------------------------------------------------
2099
+ // Apply Recommendations
2100
+ // --------------------------------------------------------------------------
2101
+ async applySkillRecommendation(rec) {
2102
+ const action = rec.action;
2103
+ await this.skillsService.createSkill(action.skillName, action.skillContent);
2104
+ }
2105
+ async applyAgentRecommendation(rec) {
2106
+ const action = rec.action;
2107
+ await this.agentsService.createAgent(action.agentName, action.agentContent);
2108
+ }
2109
+ async applyHookRecommendation(rec) {
2110
+ const action = rec.action;
2111
+ await this.hooksService.createHook({
2112
+ event: action.event,
2113
+ matcher: action.matcher,
2114
+ command: action.command,
2115
+ enabled: true,
2116
+ });
2117
+ }
2118
+ async applyClaudeMdRecommendation(rec) {
2119
+ const action = rec.action;
2120
+ const currentMemory = await this.memoryService.getMemory();
2121
+ // Check if section already exists
2122
+ const sectionHeader = action.section;
2123
+ const hasSection = currentMemory && currentMemory.includes(sectionHeader);
2124
+ let newMemory;
2125
+ if (hasSection) {
2126
+ // Append to existing section
2127
+ const sectionIndex = currentMemory.indexOf(sectionHeader);
2128
+ const afterSection = currentMemory.slice(sectionIndex + sectionHeader.length);
2129
+ const nextSectionMatch = afterSection.match(/\n##\s/);
2130
+ const insertPoint = nextSectionMatch
2131
+ ? sectionIndex + sectionHeader.length + (nextSectionMatch.index ?? afterSection.length)
2132
+ : currentMemory.length;
2133
+ newMemory =
2134
+ currentMemory.slice(0, insertPoint).trimEnd() +
2135
+ '\n' + action.addition +
2136
+ currentMemory.slice(insertPoint);
2137
+ }
2138
+ else if (currentMemory) {
2139
+ // Add new section
2140
+ newMemory = `${currentMemory.trimEnd()}\n\n${sectionHeader}\n${action.addition}`;
2141
+ }
2142
+ else {
2143
+ // Create new memory file
2144
+ newMemory = `# Project Context\n\n${sectionHeader}\n${action.addition}`;
2145
+ }
2146
+ await this.memoryService.updateMemory(newMemory);
2147
+ }
2148
+ // --------------------------------------------------------------------------
2149
+ // Helper Methods
2150
+ // --------------------------------------------------------------------------
2151
+ normalizePrompt(text) {
2152
+ return text
2153
+ .toLowerCase()
2154
+ .replace(/[^\w\s]/g, ' ')
2155
+ .split(/\s+/)
2156
+ .filter(word => word.length > 2 && !STOP_WORDS.has(word))
2157
+ .join(' ')
2158
+ .trim();
2159
+ }
2160
+ generateFingerprint(normalized) {
2161
+ // Simple fingerprint using sorted significant words
2162
+ const words = normalized.split(/\s+/).slice(0, 10).sort();
2163
+ return crypto.createHash('md5').update(words.join('|')).digest('hex').slice(0, 12);
2164
+ }
2165
+ extractKeywords(text) {
2166
+ const words = text
2167
+ .toLowerCase()
2168
+ .replace(/[^\w\s]/g, ' ')
2169
+ .split(/\s+/)
2170
+ .filter(word => word.length > 3 && !STOP_WORDS.has(word));
2171
+ // Count frequency
2172
+ const freq = new Map();
2173
+ for (const word of words) {
2174
+ freq.set(word, (freq.get(word) || 0) + 1);
2175
+ }
2176
+ // Return top keywords
2177
+ return Array.from(freq.entries())
2178
+ .sort((a, b) => b[1] - a[1])
2179
+ .slice(0, 15)
2180
+ .map(([word]) => word);
2181
+ }
2182
+ mergeSimilarPatterns(patterns) {
2183
+ // Simple merge: if patterns share >50% keywords, merge them
2184
+ const merged = [];
2185
+ const used = new Set();
2186
+ for (let i = 0; i < patterns.length; i++) {
2187
+ if (used.has(i))
2188
+ continue;
2189
+ const pattern = { ...patterns[i] };
2190
+ pattern.sessions = new Set(pattern.sessions);
2191
+ pattern.projectPaths = new Set(pattern.projectPaths);
2192
+ for (let j = i + 1; j < patterns.length; j++) {
2193
+ if (used.has(j))
2194
+ continue;
2195
+ const other = patterns[j];
2196
+ const shared = pattern.keywords.filter(k => other.keywords.includes(k)).length;
2197
+ const similarity = shared / Math.max(pattern.keywords.length, other.keywords.length);
2198
+ if (similarity > 0.4) {
2199
+ // Merge
2200
+ pattern.count += other.count;
2201
+ for (const s of other.sessions)
2202
+ pattern.sessions.add(s);
2203
+ for (const p of other.projectPaths)
2204
+ pattern.projectPaths.add(p);
2205
+ pattern.examples.push(...other.examples);
2206
+ pattern.timestamps.push(...other.timestamps);
2207
+ used.add(j);
2208
+ }
2209
+ }
2210
+ merged.push(pattern);
2211
+ }
2212
+ return merged;
2213
+ }
2214
+ generateSkillName(keywords) {
2215
+ // Take first 2-3 meaningful keywords that are good
2216
+ const goodKeywords = keywords.filter(kw => this.isGoodKeyword(kw)).slice(0, 3);
2217
+ const name = goodKeywords
2218
+ .join('-')
2219
+ .toLowerCase()
2220
+ .replace(/[^a-z0-9-]/g, '')
2221
+ .slice(0, 32);
2222
+ return name || 'custom-workflow';
2223
+ }
2224
+ generateSkillContent(pattern) {
2225
+ const name = this.generateSkillName(pattern.keywords);
2226
+ const description = `Automates: ${pattern.examples[0]?.slice(0, 100) || pattern.keywords.join(', ')}`;
2227
+ return `---
2228
+ name: ${name}
2229
+ description: ${description}
2230
+ user-invocable: true
2231
+ ---
2232
+
2233
+ # ${name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')}
2234
+
2235
+ This skill was auto-generated based on your repeated prompts.
2236
+
2237
+ ## What it does
2238
+
2239
+ ${pattern.examples[0] || 'Executes the workflow you commonly request.'}
2240
+
2241
+ ## Usage
2242
+
2243
+ Invoke with \`/${name}\` followed by any specific parameters.
2244
+
2245
+ ## Examples
2246
+
2247
+ ${pattern.examples.slice(0, 3).map((ex, i) => `${i + 1}. ${ex.slice(0, 150)}...`).join('\n')}
2248
+ `;
2249
+ }
2250
+ isDuplicate(existing, newRec) {
2251
+ return existing.some(r => {
2252
+ if (r.type !== newRec.type)
2253
+ return false;
2254
+ if (r.status === 'applied' || r.status === 'dismissed')
2255
+ return true;
2256
+ // Check similarity of title/action
2257
+ if (r.title === newRec.title)
2258
+ return true;
2259
+ // Type-specific checks
2260
+ if (r.type === 'skill' && newRec.type === 'skill') {
2261
+ const rAction = r.action;
2262
+ const nAction = newRec.action;
2263
+ return rAction.skillName === nAction.skillName;
2264
+ }
2265
+ return false;
2266
+ });
2267
+ }
2268
+ // --------------------------------------------------------------------------
2269
+ // AI-Powered Pattern Evaluation
2270
+ // --------------------------------------------------------------------------
2271
+ async isClaudeAvailable() {
2272
+ try {
2273
+ await execAsync('which claude');
2274
+ return true;
2275
+ }
2276
+ catch {
2277
+ return false;
2278
+ }
2279
+ }
2280
+ /**
2281
+ * Uses Claude to intelligently evaluate detected patterns and determine
2282
+ * which ones represent meaningful, automatable workflows.
2283
+ */
2284
+ async evaluatePatternsWithAI(patterns) {
2285
+ if (patterns.length === 0)
2286
+ return [];
2287
+ const claudeAvailable = await this.isClaudeAvailable();
2288
+ if (!claudeAvailable) {
2289
+ console.log('[Advisor] Claude CLI not available, skipping AI evaluation');
2290
+ return [];
2291
+ }
2292
+ console.log(`[Advisor] Evaluating ${patterns.length} patterns with AI...`);
2293
+ // Prepare compact pattern data to include directly in prompt
2294
+ const patternsData = patterns.map((p, i) => ({
2295
+ index: i,
2296
+ keywords: p.keywords.slice(0, 6),
2297
+ examples: p.examples.slice(0, 2).map(e => e.slice(0, 150)), // Keep examples short
2298
+ count: p.count,
2299
+ sessions: p.sessions.size,
2300
+ }));
2301
+ // Build the AI evaluation prompt with data embedded
2302
+ const promptFilePath = path.join(this.claudeDir.root, 'advisor-prompt-temp.txt');
2303
+ const prompt = this.buildAIEvaluationPrompt(patternsData);
2304
+ await fs.writeFile(promptFilePath, prompt);
2305
+ try {
2306
+ // Call Claude CLI
2307
+ const { stdout, stderr } = await execAsync(`cat "${promptFilePath}" | claude -p --output-format text`, {
2308
+ cwd: process.cwd(),
2309
+ env: { ...process.env, NO_COLOR: '1', FORCE_COLOR: '0' },
2310
+ maxBuffer: 10 * 1024 * 1024,
2311
+ timeout: 180000, // 3 minutes for larger analysis
2312
+ });
2313
+ if (stderr) {
2314
+ console.log('[Advisor] Claude stderr:', stderr.slice(0, 200));
2315
+ }
2316
+ // Save response for debugging if parsing fails
2317
+ const debugFilePath = path.join(this.claudeDir.root, 'advisor-ai-response-debug.txt');
2318
+ await fs.writeFile(debugFilePath, stdout);
2319
+ // Parse the response
2320
+ const evaluations = this.parseAIResponse(stdout, patterns.length);
2321
+ console.log(`[Advisor] AI evaluated ${evaluations.length} patterns`);
2322
+ // Cleanup temp files (keep debug file for inspection)
2323
+ await fs.unlink(promptFilePath).catch(() => { });
2324
+ return evaluations;
2325
+ }
2326
+ catch (err) {
2327
+ console.error('[Advisor] AI evaluation failed:', err?.message || err);
2328
+ // Cleanup on error
2329
+ await fs.unlink(promptFilePath).catch(() => { });
2330
+ return [];
2331
+ }
2332
+ }
2333
+ buildAIEvaluationPrompt(patternsData) {
2334
+ const patternsJson = JSON.stringify(patternsData, null, 2);
2335
+ return `You are an expert developer workflow analyst. Evaluate these usage patterns and determine which represent meaningful, automatable workflows.
2336
+
2337
+ ## PATTERNS TO EVALUATE
2338
+
2339
+ ${patternsJson}
2340
+
2341
+ Each pattern has:
2342
+ - index: pattern number (use this in patternIndex)
2343
+ - keywords: extracted from user prompts
2344
+ - examples: actual prompts the user typed
2345
+ - count: how many times similar prompts appeared
2346
+ - sessions: across how many sessions
2347
+
2348
+ ## YOUR TASK
2349
+
2350
+ For each pattern, determine:
2351
+ 1. Is this a GENUINE workflow a developer would want to automate?
2352
+ 2. What's the best skill name (action-verb first, kebab-case, 2-4 words)?
2353
+ 3. Should it be accepted or rejected?
2354
+
2355
+ ## SKILL NAMING RULES
2356
+
2357
+ GOOD names (action + target):
2358
+ - "deploy-vercel", "run-tests", "create-component", "review-pr", "fix-types", "commit-changes"
2359
+
2360
+ BAD names (REJECT these patterns):
2361
+ - Random words: "continue-tier-engineer", "sure-ahead-best"
2362
+ - Path fragments: "file-users-josharsh"
2363
+ - Meaningless: "most-page-update", "code-export-type"
2364
+
2365
+ ## ACCEPT patterns where examples show clear intent:
2366
+ - "deploy this to vercel" → deploy-vercel
2367
+ - "run the tests" → run-tests
2368
+ - "create a component for..." → create-component
2369
+
2370
+ ## REJECT patterns that are:
2371
+ - Random keyword combinations
2372
+ - Conversation fragments ("yes continue", "thanks")
2373
+ - Code/path fragments
2374
+ - Vague requests
2375
+
2376
+ ## OUTPUT FORMAT
2377
+
2378
+ Return ONLY this JSON (no markdown, no explanation):
2379
+
2380
+ {"evaluations":[{"patternIndex":0,"isValidWorkflow":true,"confidence":0.9,"reasoning":"Clear deployment workflow","suggestedSkillName":"deploy-vercel","suggestedDescription":"Deploy to Vercel","category":"deployment","skillContent":"---\\ndescription: Deploy to Vercel\\nuser-invocable: true\\n---\\n\\n# Deploy to Vercel\\n\\nDeploy the current project.\\n\\n## Steps\\n1. Run vercel CLI\\n2. Return deployment URL\\n\\n$ARGUMENTS"},{"patternIndex":1,"isValidWorkflow":false,"confidence":0.95,"reasoning":"Random word combination","suggestedSkillName":null,"suggestedDescription":null,"category":"invalid","skillContent":null}],"summary":"1 valid, 1 rejected"}
2381
+
2382
+ IMPORTANT: Output ONLY the JSON object. No other text.`;
2383
+ }
2384
+ parseAIResponse(response, expectedCount) {
2385
+ try {
2386
+ console.log(`[Advisor] Raw response length: ${response.length} chars`);
2387
+ console.log(`[Advisor] Response preview: ${response.slice(0, 300)}...`);
2388
+ // Clean up response - remove any markdown code blocks
2389
+ let cleaned = response.trim();
2390
+ // Handle various markdown code block formats
2391
+ // Remove ```json at start
2392
+ cleaned = cleaned.replace(/^```json\s*/i, '');
2393
+ // Remove ``` at start (without json)
2394
+ cleaned = cleaned.replace(/^```\s*/, '');
2395
+ // Remove ``` at end
2396
+ cleaned = cleaned.replace(/\s*```\s*$/, '');
2397
+ cleaned = cleaned.trim();
2398
+ // Try to find JSON object in response - use a more robust approach
2399
+ // Look for the outermost { ... } that contains "evaluations"
2400
+ let jsonStart = -1;
2401
+ let braceCount = 0;
2402
+ let jsonEnd = -1;
2403
+ for (let i = 0; i < cleaned.length; i++) {
2404
+ if (cleaned[i] === '{') {
2405
+ if (jsonStart === -1)
2406
+ jsonStart = i;
2407
+ braceCount++;
2408
+ }
2409
+ else if (cleaned[i] === '}') {
2410
+ braceCount--;
2411
+ if (braceCount === 0 && jsonStart !== -1) {
2412
+ jsonEnd = i + 1;
2413
+ // Check if this contains "evaluations"
2414
+ const candidate = cleaned.slice(jsonStart, jsonEnd);
2415
+ if (candidate.includes('"evaluations"')) {
2416
+ break;
2417
+ }
2418
+ else {
2419
+ // Reset and keep looking
2420
+ jsonStart = -1;
2421
+ }
2422
+ }
2423
+ }
2424
+ }
2425
+ if (jsonStart === -1 || jsonEnd === -1) {
2426
+ // Fallback to regex
2427
+ const jsonMatch = cleaned.match(/\{[\s\S]*"evaluations"[\s\S]*\}/);
2428
+ if (!jsonMatch) {
2429
+ console.error('[Advisor] No JSON with "evaluations" found in AI response');
2430
+ console.error('[Advisor] Cleaned response preview:', cleaned.slice(0, 500));
2431
+ return [];
2432
+ }
2433
+ cleaned = jsonMatch[0];
2434
+ }
2435
+ else {
2436
+ cleaned = cleaned.slice(jsonStart, jsonEnd);
2437
+ }
2438
+ console.log(`[Advisor] Extracted JSON length: ${cleaned.length} chars`);
2439
+ const parsed = JSON.parse(cleaned);
2440
+ if (!parsed.evaluations || !Array.isArray(parsed.evaluations)) {
2441
+ console.error('[Advisor] Invalid AI response structure - no evaluations array');
2442
+ console.error('[Advisor] Parsed keys:', Object.keys(parsed));
2443
+ return [];
2444
+ }
2445
+ console.log(`[Advisor] Found ${parsed.evaluations.length} evaluations in response`);
2446
+ // Validate and filter evaluations
2447
+ const valid = parsed.evaluations.filter(e => typeof e.patternIndex === 'number' &&
2448
+ typeof e.isValidWorkflow === 'boolean' &&
2449
+ e.patternIndex >= 0 &&
2450
+ e.patternIndex < expectedCount);
2451
+ console.log(`[Advisor] ${valid.length} evaluations passed validation`);
2452
+ return valid;
2453
+ }
2454
+ catch (err) {
2455
+ console.error('[Advisor] Failed to parse AI response:', err?.message || err);
2456
+ return [];
2457
+ }
2458
+ }
2459
+ /**
2460
+ * Creates an AI-enhanced recommendation from a pattern and its evaluation.
2461
+ */
2462
+ createAIEnhancedRecommendation(pattern, evaluation) {
2463
+ if (!evaluation.isValidWorkflow || !evaluation.suggestedSkillName) {
2464
+ return null;
2465
+ }
2466
+ const skillName = evaluation.suggestedSkillName
2467
+ .toLowerCase()
2468
+ .replace(/[^a-z0-9-]/g, '-')
2469
+ .replace(/-+/g, '-')
2470
+ .slice(0, 32);
2471
+ if (skillName.length < 3)
2472
+ return null;
2473
+ // Use AI-generated content or fall back to template
2474
+ const skillContent = evaluation.skillContent || this.generateSkillContent(pattern);
2475
+ return {
2476
+ id: `skill-${crypto.randomUUID().slice(0, 8)}`,
2477
+ type: 'skill',
2478
+ title: `Create "${skillName}" skill`,
2479
+ description: evaluation.suggestedDescription ||
2480
+ `You've asked similar questions ${pattern.count} times across ${pattern.sessions.size} sessions. Creating a skill would let you invoke this workflow with /${skillName}.`,
2481
+ confidence: Math.min(0.98, evaluation.confidence),
2482
+ evidence: [
2483
+ `Found ${pattern.count} similar prompts`,
2484
+ `Across ${pattern.sessions.size} different sessions`,
2485
+ `AI Category: ${evaluation.category}`,
2486
+ `Keywords: ${pattern.keywords.slice(0, 5).join(', ')}`,
2487
+ ],
2488
+ action: {
2489
+ type: 'skill',
2490
+ skillName,
2491
+ skillContent,
2492
+ },
2493
+ examples: pattern.examples,
2494
+ createdAt: new Date().toISOString(),
2495
+ status: 'pending',
2496
+ };
2497
+ }
2498
+ /**
2499
+ * Enhanced analysis that uses AI to filter and improve recommendations.
2500
+ */
2501
+ async runAnalysisWithAI(force = false) {
2502
+ const state = await this.getState();
2503
+ // Check if we should run
2504
+ if (!force && state.lastAnalyzedAt) {
2505
+ const lastRun = new Date(state.lastAnalyzedAt).getTime();
2506
+ const hourAgo = Date.now() - 60 * 60 * 1000;
2507
+ if (lastRun > hourAgo) {
2508
+ return {
2509
+ newRecommendations: 0,
2510
+ totalRecommendations: state.recommendations.filter(r => r.status === 'pending').length,
2511
+ aiEvaluated: false,
2512
+ };
2513
+ }
2514
+ }
2515
+ console.log('[Advisor] Starting AI-powered analysis...');
2516
+ const startTime = Date.now();
2517
+ // Gather data
2518
+ const cutoffDate = new Date();
2519
+ cutoffDate.setDate(cutoffDate.getDate() - this.config.lookbackDays);
2520
+ const cutoffStr = cutoffDate.toISOString();
2521
+ const { sessions } = await this.sessionService.listSessions({ limit: 500 });
2522
+ const recentSessions = sessions.filter(s => s.startedAt >= cutoffStr);
2523
+ console.log(`[Advisor] Analyzing ${recentSessions.length} sessions from last ${this.config.lookbackDays} days`);
2524
+ // Load session details
2525
+ const sessionDetails = await Promise.all(recentSessions.slice(0, 100).map(s => this.sessionService.getSession(s.id)));
2526
+ const validSessions = sessionDetails.filter(s => s !== null);
2527
+ // Phase 1: Heuristic pattern detection (fast)
2528
+ const promptPatterns = this.detectRepeatedPrompts(validSessions);
2529
+ console.log(`[Advisor] Heuristics found ${promptPatterns.length} potential patterns`);
2530
+ // Filter to top candidates for AI evaluation
2531
+ const candidates = promptPatterns
2532
+ .filter(p => p.count >= this.config.minPromptOccurrences)
2533
+ .slice(0, 20); // Limit to avoid huge AI calls
2534
+ if (candidates.length === 0) {
2535
+ state.lastAnalyzedAt = new Date().toISOString();
2536
+ await this.saveState(state);
2537
+ return {
2538
+ newRecommendations: 0,
2539
+ totalRecommendations: state.recommendations.filter(r => r.status === 'pending').length,
2540
+ aiEvaluated: false,
2541
+ };
2542
+ }
2543
+ // Phase 2: AI evaluation (quality filter)
2544
+ const evaluations = await this.evaluatePatternsWithAI(candidates);
2545
+ const aiEvaluated = evaluations.length > 0;
2546
+ const newRecommendations = [];
2547
+ if (aiEvaluated) {
2548
+ // Use AI evaluations
2549
+ for (const evaluation of evaluations) {
2550
+ if (!evaluation.isValidWorkflow)
2551
+ continue;
2552
+ const pattern = candidates[evaluation.patternIndex];
2553
+ if (!pattern)
2554
+ continue;
2555
+ const rec = this.createAIEnhancedRecommendation(pattern, evaluation);
2556
+ if (rec && !this.isDuplicate(state.recommendations, rec)) {
2557
+ newRecommendations.push(rec);
2558
+ }
2559
+ }
2560
+ console.log(`[Advisor] AI approved ${newRecommendations.length} pattern-based recommendations`);
2561
+ }
2562
+ else {
2563
+ // Fallback to heuristic-only recommendations
2564
+ console.log('[Advisor] Falling back to heuristic recommendations');
2565
+ for (const pattern of candidates) {
2566
+ const rec = this.createSkillRecommendation(pattern);
2567
+ if (rec && !this.isDuplicate(state.recommendations, rec)) {
2568
+ newRecommendations.push(rec);
2569
+ }
2570
+ }
2571
+ }
2572
+ // Phase 2.5: Agent Recommendations
2573
+ const agentPatterns = this.detectAgentOpportunities(validSessions);
2574
+ const existingAgents = await this.getExistingAgents();
2575
+ for (const pattern of agentPatterns) {
2576
+ if (existingAgents.has(pattern.agentName.toLowerCase()))
2577
+ continue;
2578
+ const rec = this.createAgentRecommendation(pattern);
2579
+ if (rec && !this.isDuplicate(state.recommendations, rec) && !this.isDuplicate(newRecommendations, rec)) {
2580
+ newRecommendations.push(rec);
2581
+ }
2582
+ }
2583
+ console.log(`[Advisor] Created ${agentPatterns.length} agent recommendations`);
2584
+ // Phase 2.6: CLAUDE.md Recommendations
2585
+ const claudeMdPatterns = this.detectClaudeMdOpportunities(validSessions);
2586
+ for (const pattern of claudeMdPatterns) {
2587
+ const rec = this.createClaudeMdRecommendation(pattern);
2588
+ if (rec && !this.isDuplicate(state.recommendations, rec) && !this.isDuplicate(newRecommendations, rec)) {
2589
+ newRecommendations.push(rec);
2590
+ }
2591
+ }
2592
+ console.log(`[Advisor] Created ${claudeMdPatterns.length} CLAUDE.md recommendations`);
2593
+ // Phase 2.7: Hook Recommendations
2594
+ const hookPatterns = this.detectHookOpportunities(validSessions);
2595
+ for (const pattern of hookPatterns) {
2596
+ const rec = this.createHookRecommendation(pattern);
2597
+ if (rec && !this.isDuplicate(state.recommendations, rec) && !this.isDuplicate(newRecommendations, rec)) {
2598
+ newRecommendations.push(rec);
2599
+ }
2600
+ }
2601
+ console.log(`[Advisor] Created ${hookPatterns.length} hook recommendations`);
2602
+ // Phase 2.8: MCP Server Recommendations
2603
+ const mcpPatterns = this.detectMCPOpportunities(validSessions);
2604
+ for (const pattern of mcpPatterns) {
2605
+ const rec = this.createMCPRecommendation(pattern);
2606
+ if (rec && !this.isDuplicate(state.recommendations, rec) && !this.isDuplicate(newRecommendations, rec)) {
2607
+ newRecommendations.push(rec);
2608
+ }
2609
+ }
2610
+ console.log(`[Advisor] Created ${mcpPatterns.length} MCP server recommendations`);
2611
+ // Phase 3: Proactive recommendations (knowledge-based)
2612
+ // Get existing recommendation names to avoid duplicates
2613
+ const existingRecNames = new Set([...state.recommendations, ...newRecommendations]
2614
+ .filter(r => r.action.type === 'skill')
2615
+ .map(r => r.action.skillName.toLowerCase()));
2616
+ const proactiveRecs = await this.generateProactiveRecommendations(validSessions, existingRecNames);
2617
+ for (const rec of proactiveRecs) {
2618
+ if (!this.isDuplicate(state.recommendations, rec) && !this.isDuplicate(newRecommendations, rec)) {
2619
+ newRecommendations.push(rec);
2620
+ }
2621
+ }
2622
+ console.log(`[Advisor] Total new recommendations: ${newRecommendations.length} (including ${proactiveRecs.length} proactive)`);
2623
+ // Save state
2624
+ state.recommendations.push(...newRecommendations);
2625
+ state.lastAnalyzedAt = new Date().toISOString();
2626
+ await this.saveState(state);
2627
+ const elapsed = Date.now() - startTime;
2628
+ console.log(`[Advisor] Analysis complete in ${elapsed}ms. Found ${newRecommendations.length} new recommendations.`);
2629
+ return {
2630
+ newRecommendations: newRecommendations.length,
2631
+ totalRecommendations: state.recommendations.filter(r => r.status === 'pending').length,
2632
+ aiEvaluated,
2633
+ };
2634
+ }
2635
+ }
2636
+ //# sourceMappingURL=advisor.js.map