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.
- package/LICENSE +21 -0
- package/README.md +275 -0
- package/dist/__tests__/package.test.d.ts +2 -0
- package/dist/__tests__/package.test.d.ts.map +1 -0
- package/dist/__tests__/package.test.js +53 -0
- package/dist/__tests__/package.test.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +200 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +1207 -0
- package/dist/server/index.js.map +1 -0
- package/dist/services/advisor.d.ts +117 -0
- package/dist/services/advisor.d.ts.map +1 -0
- package/dist/services/advisor.js +2636 -0
- package/dist/services/advisor.js.map +1 -0
- package/dist/services/agent-generator.d.ts +71 -0
- package/dist/services/agent-generator.d.ts.map +1 -0
- package/dist/services/agent-generator.js +295 -0
- package/dist/services/agent-generator.js.map +1 -0
- package/dist/services/agents.d.ts +67 -0
- package/dist/services/agents.d.ts.map +1 -0
- package/dist/services/agents.js +405 -0
- package/dist/services/agents.js.map +1 -0
- package/dist/services/analytics.d.ts +145 -0
- package/dist/services/analytics.d.ts.map +1 -0
- package/dist/services/analytics.js +609 -0
- package/dist/services/analytics.js.map +1 -0
- package/dist/services/blueprints.d.ts +31 -0
- package/dist/services/blueprints.d.ts.map +1 -0
- package/dist/services/blueprints.js +317 -0
- package/dist/services/blueprints.js.map +1 -0
- package/dist/services/claude-dir.d.ts +50 -0
- package/dist/services/claude-dir.d.ts.map +1 -0
- package/dist/services/claude-dir.js +193 -0
- package/dist/services/claude-dir.js.map +1 -0
- package/dist/services/hooks.d.ts +38 -0
- package/dist/services/hooks.d.ts.map +1 -0
- package/dist/services/hooks.js +165 -0
- package/dist/services/hooks.js.map +1 -0
- package/dist/services/insights.d.ts +52 -0
- package/dist/services/insights.d.ts.map +1 -0
- package/dist/services/insights.js +1035 -0
- package/dist/services/insights.js.map +1 -0
- package/dist/services/memory.d.ts +14 -0
- package/dist/services/memory.d.ts.map +1 -0
- package/dist/services/memory.js +25 -0
- package/dist/services/memory.js.map +1 -0
- package/dist/services/plans.d.ts +20 -0
- package/dist/services/plans.d.ts.map +1 -0
- package/dist/services/plans.js +149 -0
- package/dist/services/plans.js.map +1 -0
- package/dist/services/project-intelligence.d.ts +75 -0
- package/dist/services/project-intelligence.d.ts.map +1 -0
- package/dist/services/project-intelligence.js +731 -0
- package/dist/services/project-intelligence.js.map +1 -0
- package/dist/services/search.d.ts +32 -0
- package/dist/services/search.d.ts.map +1 -0
- package/dist/services/search.js +203 -0
- package/dist/services/search.js.map +1 -0
- package/dist/services/sessions.d.ts +25 -0
- package/dist/services/sessions.d.ts.map +1 -0
- package/dist/services/sessions.js +248 -0
- package/dist/services/sessions.js.map +1 -0
- package/dist/services/skills.d.ts +30 -0
- package/dist/services/skills.d.ts.map +1 -0
- package/dist/services/skills.js +197 -0
- package/dist/services/skills.js.map +1 -0
- package/dist/services/stats.d.ts +23 -0
- package/dist/services/stats.d.ts.map +1 -0
- package/dist/services/stats.js +88 -0
- package/dist/services/stats.js.map +1 -0
- package/dist/services/teams.d.ts +115 -0
- package/dist/services/teams.d.ts.map +1 -0
- package/dist/services/teams.js +421 -0
- package/dist/services/teams.js.map +1 -0
- package/dist/services/tech-stack.d.ts +98 -0
- package/dist/services/tech-stack.d.ts.map +1 -0
- package/dist/services/tech-stack.js +1088 -0
- package/dist/services/tech-stack.js.map +1 -0
- package/dist/services/terminal.d.ts +75 -0
- package/dist/services/terminal.d.ts.map +1 -0
- package/dist/services/terminal.js +224 -0
- package/dist/services/terminal.js.map +1 -0
- package/dist/types.d.ts +1095 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/assets/index-BiH4Nhdk.css +1 -0
- package/dist/ui/assets/index-Brv-K8bd.css +1 -0
- package/dist/ui/assets/index-BwMBEdQz.js +3108 -0
- package/dist/ui/assets/index-BwMBEdQz.js.map +1 -0
- package/dist/ui/assets/index-CEWz7ABD.js +3108 -0
- package/dist/ui/assets/index-CEWz7ABD.js.map +1 -0
- package/dist/ui/assets/index-CIZ3vvc-.css +1 -0
- package/dist/ui/assets/index-CsU3cI0n.js +3108 -0
- package/dist/ui/assets/index-CsU3cI0n.js.map +1 -0
- package/dist/ui/assets/index-D3AY6iCS.js +3133 -0
- package/dist/ui/assets/index-D3AY6iCS.js.map +1 -0
- package/dist/ui/assets/index-D8lNZ0Ye.css +1 -0
- package/dist/ui/assets/index-DmgeppSA.js +3108 -0
- package/dist/ui/assets/index-DmgeppSA.js.map +1 -0
- package/dist/ui/favicon.svg +43 -0
- package/dist/ui/index.html +23 -0
- package/dist/utils/jsonl.d.ts +16 -0
- package/dist/utils/jsonl.d.ts.map +1 -0
- package/dist/utils/jsonl.js +51 -0
- package/dist/utils/jsonl.js.map +1 -0
- 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
|