@magic-ingredients/tiny-brain-local 0.20.1 → 0.21.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/mcp-server.d.ts.map +1 -1
- package/dist/core/mcp-server.js +11 -8
- package/dist/core/repo-registration.d.ts +30 -0
- package/dist/core/repo-registration.d.ts.map +1 -0
- package/dist/core/repo-registration.js +34 -0
- package/dist/prompts/persona/persona.prompt.js +1 -1
- package/dist/prompts/planning/planning.prompt.d.ts +0 -8
- package/dist/prompts/planning/planning.prompt.d.ts.map +1 -1
- package/dist/prompts/planning/planning.prompt.js +0 -193
- package/dist/prompts/prompt-registry.d.ts.map +1 -1
- package/dist/prompts/prompt-registry.js +0 -2
- package/dist/services/analyse-service.d.ts +15 -29
- package/dist/services/analyse-service.d.ts.map +1 -1
- package/dist/services/analyse-service.js +99 -236
- package/dist/services/repo-service.d.ts +5 -0
- package/dist/services/repo-service.d.ts.map +1 -1
- package/dist/services/repo-service.js +67 -42
- package/dist/storage/local-filesystem-adapter.d.ts.map +1 -1
- package/dist/storage/local-filesystem-adapter.js +0 -3
- package/dist/tools/dashboard/dashboard.tool.d.ts +4 -3
- package/dist/tools/dashboard/dashboard.tool.d.ts.map +1 -1
- package/dist/tools/dashboard/dashboard.tool.js +53 -6
- package/dist/tools/plan/plan.tool.d.ts +0 -1
- package/dist/tools/plan/plan.tool.d.ts.map +1 -1
- package/dist/tools/plan/plan.tool.js +10 -56
- package/dist/tools/quality/quality.tool.js +1 -1
- package/dist/tools/recommendations/recommendations.tool.d.ts +13 -0
- package/dist/tools/recommendations/recommendations.tool.d.ts.map +1 -0
- package/dist/tools/recommendations/recommendations.tool.js +178 -0
- package/dist/tools/tool-registry.d.ts.map +1 -1
- package/dist/tools/tool-registry.js +4 -0
- package/package.json +4 -10
|
@@ -125,9 +125,6 @@ export class LocalFilesystemStorageAdapter {
|
|
|
125
125
|
const personaDir = this.pathBuilder.buildPersonaDirectoryPath(personaId);
|
|
126
126
|
// Create main persona directory
|
|
127
127
|
await this.ensureDirectoryExists(personaDir);
|
|
128
|
-
// Create plans subdirectory
|
|
129
|
-
const plansDir = join(personaDir, 'plans');
|
|
130
|
-
await this.ensureDirectoryExists(plansDir);
|
|
131
128
|
}
|
|
132
129
|
async storeUserData(key, content, _userId) {
|
|
133
130
|
const filePath = this.pathBuilder.buildUserDataPath(key);
|
|
@@ -2,10 +2,11 @@ import { type ToolArguments, type ToolResult } from '../index.js';
|
|
|
2
2
|
import type { RequestContext } from '../../types/request-context.js';
|
|
3
3
|
import type { Tool as MCPTool } from '@modelcontextprotocol/sdk/types.js';
|
|
4
4
|
/**
|
|
5
|
-
* Dashboard Tool -
|
|
5
|
+
* Dashboard Tool - Rebuild and restart the dashboard server
|
|
6
6
|
*
|
|
7
|
-
* Developer maintenance tool
|
|
8
|
-
*
|
|
7
|
+
* Developer maintenance tool that runs `npm run rebuild_all` to rebuild
|
|
8
|
+
* all packages and rebundle the plugin, then kills the old dashboard
|
|
9
|
+
* process on port 8765 and restarts from the fresh bundle.
|
|
9
10
|
*/
|
|
10
11
|
export declare class DashboardTool {
|
|
11
12
|
static getToolDefinition(): MCPTool;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard.tool.d.ts","sourceRoot":"","sources":["../../../src/tools/dashboard/dashboard.tool.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dashboard.tool.d.ts","sourceRoot":"","sources":["../../../src/tools/dashboard/dashboard.tool.ts"],"names":[],"mappings":"AACA,OAAO,EAA0C,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAC1G,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAErE,OAAO,KAAK,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAE1E;;;;;;GAMG;AACH,qBAAa,aAAa;IACxB,MAAM,CAAC,iBAAiB,IAAI,OAAO;WAatB,OAAO,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC;CA8DzF"}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
1
2
|
import { createErrorResult, createSuccessResult } from '../index.js';
|
|
2
3
|
/**
|
|
3
|
-
* Dashboard Tool -
|
|
4
|
+
* Dashboard Tool - Rebuild and restart the dashboard server
|
|
4
5
|
*
|
|
5
|
-
* Developer maintenance tool
|
|
6
|
-
*
|
|
6
|
+
* Developer maintenance tool that runs `npm run rebuild_all` to rebuild
|
|
7
|
+
* all packages and rebundle the plugin, then kills the old dashboard
|
|
8
|
+
* process on port 8765 and restarts from the fresh bundle.
|
|
7
9
|
*/
|
|
8
10
|
export class DashboardTool {
|
|
9
11
|
static getToolDefinition() {
|
|
@@ -23,12 +25,57 @@ export class DashboardTool {
|
|
|
23
25
|
if (!launcher) {
|
|
24
26
|
return createErrorResult('Dashboard launcher is not available. The dashboard may not have been started.');
|
|
25
27
|
}
|
|
28
|
+
// Find the repo root (where package.json with rebuild_all lives)
|
|
29
|
+
let repoRoot;
|
|
26
30
|
try {
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
repoRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' }).trim();
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return createErrorResult('Could not determine repository root. Are you in a git repository?');
|
|
35
|
+
}
|
|
36
|
+
// Step 1: Rebuild all packages
|
|
37
|
+
const steps = [];
|
|
38
|
+
try {
|
|
39
|
+
execSync('npm run rebuild_all', {
|
|
40
|
+
cwd: repoRoot,
|
|
41
|
+
encoding: 'utf-8',
|
|
42
|
+
stdio: 'pipe',
|
|
43
|
+
timeout: 120_000,
|
|
44
|
+
});
|
|
45
|
+
steps.push('Rebuilt all packages (rebuild_all)');
|
|
29
46
|
}
|
|
30
47
|
catch (error) {
|
|
31
|
-
return createErrorResult(`
|
|
48
|
+
return createErrorResult(`rebuild_all failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
49
|
+
}
|
|
50
|
+
// Step 2: Stop the current dashboard and kill the port
|
|
51
|
+
try {
|
|
52
|
+
await launcher.stop();
|
|
53
|
+
steps.push('Stopped dashboard server');
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Ignore stop errors — port kill will clean up
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
execSync('lsof -ti:8765 | xargs kill 2>/dev/null || true', {
|
|
60
|
+
encoding: 'utf-8',
|
|
61
|
+
stdio: 'pipe',
|
|
62
|
+
});
|
|
63
|
+
steps.push('Killed process on port 8765');
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// No process on port — fine
|
|
67
|
+
}
|
|
68
|
+
// Step 3: Start fresh from the new bundle
|
|
69
|
+
try {
|
|
70
|
+
const result = await launcher.restart(context, {});
|
|
71
|
+
steps.push(`Dashboard started at ${result.url}`);
|
|
72
|
+
return createSuccessResult(steps.join('\n'));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// In-process restart failed (expected — bundle is stale in memory)
|
|
76
|
+
// The rebuild is done, user needs to restart the MCP server
|
|
77
|
+
steps.push('Note: MCP server must be restarted for code changes to take effect (the running process still has the old bundle in memory)');
|
|
78
|
+
return createSuccessResult(steps.join('\n'));
|
|
32
79
|
}
|
|
33
80
|
}
|
|
34
81
|
}
|
|
@@ -11,7 +11,6 @@ export declare class PlanTool {
|
|
|
11
11
|
private static handleStatus;
|
|
12
12
|
private static handleReport;
|
|
13
13
|
private static handleList;
|
|
14
|
-
private static handleArchive;
|
|
15
14
|
private static handleStartDashboard;
|
|
16
15
|
private static handleStopDashboard;
|
|
17
16
|
private static handleDashboardStatus;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.tool.d.ts","sourceRoot":"","sources":["../../../src/tools/plan/plan.tool.ts"],"names":[],"mappings":"AACA,OAAO,EAA0C,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"plan.tool.d.ts","sourceRoot":"","sources":["../../../src/tools/plan/plan.tool.ts"],"names":[],"mappings":"AACA,OAAO,EAA0C,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAsLrE,qBAAa,QAAQ;IACnB,MAAM,CAAC,iBAAiB,IAAI,OAAO;WAgLtB,OAAO,CAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,UAAU,CAAC;mBAyED,YAAY;mBA+LZ,aAAa;mBAyCb,eAAe;mBA0Df,YAAY;mBA+CZ,YAAY;mBAsDZ,YAAY;mBAiCZ,UAAU;mBA2DV,oBAAoB;mBA+CpB,mBAAmB;mBAsBnB,qBAAqB;mBAqCrB,SAAS;mBAgGT,eAAe;IAuFpC;;OAEG;mBACkB,UAAU;CA2DhC"}
|
|
@@ -9,7 +9,7 @@ import { exec } from 'child_process';
|
|
|
9
9
|
import { promisify } from 'util';
|
|
10
10
|
const execAsync = promisify(exec);
|
|
11
11
|
const PlanArgsSchema = z.object({
|
|
12
|
-
operation: z.enum(['accept', 'feature', 'implement', 'update', 'status', 'report', 'list', '
|
|
12
|
+
operation: z.enum(['accept', 'feature', 'implement', 'update', 'status', 'report', 'list', 'adr', 'create-adr', 'sync', 'start-dashboard', 'stop-dashboard', 'dashboard-status']),
|
|
13
13
|
// For 'accept' operation
|
|
14
14
|
planName: z.string().optional(),
|
|
15
15
|
planDetails: z.string().optional(),
|
|
@@ -39,12 +39,12 @@ const PlanArgsSchema = z.object({
|
|
|
39
39
|
updateFeature: z
|
|
40
40
|
.object({
|
|
41
41
|
featureNumber: z.number(),
|
|
42
|
-
status: z.enum(['
|
|
42
|
+
status: z.enum(['not_started', 'in_progress', 'completed', 'superseded']).optional(),
|
|
43
43
|
addTask: z.string().optional(),
|
|
44
44
|
addTasks: z.array(z.string()).optional(),
|
|
45
45
|
updateTask: z.object({
|
|
46
46
|
id: z.string(),
|
|
47
|
-
status: z.enum(['
|
|
47
|
+
status: z.enum(['not_started', 'in_progress', 'completed', 'superseded']).optional(),
|
|
48
48
|
testCommitSha: z.string().optional(),
|
|
49
49
|
testCommittedAt: z.string().optional(),
|
|
50
50
|
commitSha: z.string().optional(),
|
|
@@ -58,7 +58,7 @@ const PlanArgsSchema = z.object({
|
|
|
58
58
|
updateFeatures: z
|
|
59
59
|
.array(z.object({
|
|
60
60
|
featureNumber: z.number(),
|
|
61
|
-
status: z.enum(['
|
|
61
|
+
status: z.enum(['not_started', 'in_progress', 'completed', 'superseded']).optional(),
|
|
62
62
|
addTask: z.string().optional(),
|
|
63
63
|
addTasks: z.array(z.string()).optional(),
|
|
64
64
|
}))
|
|
@@ -71,11 +71,6 @@ const PlanArgsSchema = z.object({
|
|
|
71
71
|
saveToFile: z.boolean().optional(),
|
|
72
72
|
// For 'list' operation
|
|
73
73
|
listStatus: z.enum(['active', 'completed', 'archived', 'all']).optional(),
|
|
74
|
-
// For 'archive' operation
|
|
75
|
-
reason: z.string().optional(),
|
|
76
|
-
// For 'switch' operation
|
|
77
|
-
// planId is reused from update operation
|
|
78
|
-
// Note: switchToPlan automatically unarchives if needed
|
|
79
74
|
// For 'watch' operation
|
|
80
75
|
port: z.number().optional().default(8765),
|
|
81
76
|
autoOpen: z.boolean().optional().default(true),
|
|
@@ -190,7 +185,6 @@ export class PlanTool {
|
|
|
190
185
|
• status: Show current plan progress and summary
|
|
191
186
|
• report: Generate and display comprehensive markdown report
|
|
192
187
|
• list: List all plans (active, completed, archived)
|
|
193
|
-
• archive: Archive a completed plan
|
|
194
188
|
• adr: Analyze recent commits for ADR suggestions and prompt for creation
|
|
195
189
|
• create-adr: Create ADR file from suggestion (used after user confirmation)
|
|
196
190
|
• start-dashboard: Manually start the dashboard (usually auto-started)
|
|
@@ -236,7 +230,7 @@ export class PlanTool {
|
|
|
236
230
|
properties: {
|
|
237
231
|
operation: {
|
|
238
232
|
type: 'string',
|
|
239
|
-
enum: ['accept', 'feature', 'implement', 'update', 'status', 'report', 'list', '
|
|
233
|
+
enum: ['accept', 'feature', 'implement', 'update', 'status', 'report', 'list', 'adr', 'create-adr', 'start-dashboard', 'stop-dashboard', 'dashboard-status'],
|
|
240
234
|
description: 'The planning operation to perform',
|
|
241
235
|
},
|
|
242
236
|
// For 'accept' operation
|
|
@@ -294,13 +288,13 @@ export class PlanTool {
|
|
|
294
288
|
type: 'object',
|
|
295
289
|
properties: {
|
|
296
290
|
featureNumber: { type: 'number' },
|
|
297
|
-
status: { type: 'string', enum: ['
|
|
291
|
+
status: { type: 'string', enum: ['not_started', 'in_progress', 'completed', 'superseded'] },
|
|
298
292
|
addTask: { type: 'string' },
|
|
299
293
|
updateTask: {
|
|
300
294
|
type: 'object',
|
|
301
295
|
properties: {
|
|
302
296
|
id: { type: 'string' },
|
|
303
|
-
status: { type: 'string', enum: ['
|
|
297
|
+
status: { type: 'string', enum: ['not_started', 'in_progress', 'completed', 'superseded'] },
|
|
304
298
|
testCommitSha: { type: 'string' },
|
|
305
299
|
testCommittedAt: { type: 'string' },
|
|
306
300
|
commitSha: { type: 'string' },
|
|
@@ -330,17 +324,6 @@ export class PlanTool {
|
|
|
330
324
|
enum: ['active', 'completed', 'archived', 'all'],
|
|
331
325
|
description: 'Filter plans by status',
|
|
332
326
|
},
|
|
333
|
-
// For 'archive' operation
|
|
334
|
-
reason: {
|
|
335
|
-
type: 'string',
|
|
336
|
-
description: 'Reason for archiving',
|
|
337
|
-
},
|
|
338
|
-
// For 'switch' operation
|
|
339
|
-
// planId is reused from update operation
|
|
340
|
-
force: {
|
|
341
|
-
type: 'boolean',
|
|
342
|
-
description: 'Force switch to archived plans (optional)',
|
|
343
|
-
},
|
|
344
327
|
// For 'watch' operation
|
|
345
328
|
port: {
|
|
346
329
|
type: 'number',
|
|
@@ -400,8 +383,6 @@ export class PlanTool {
|
|
|
400
383
|
return await PlanTool.handleReport(validatedArgs, contextWithRepo);
|
|
401
384
|
case 'list':
|
|
402
385
|
return await PlanTool.handleList(validatedArgs, contextWithRepo);
|
|
403
|
-
case 'archive':
|
|
404
|
-
return await PlanTool.handleArchive(validatedArgs, contextWithRepo);
|
|
405
386
|
case 'adr':
|
|
406
387
|
return await PlanTool.handleAdr(validatedArgs, contextWithRepo);
|
|
407
388
|
case 'create-adr':
|
|
@@ -565,7 +546,7 @@ export class PlanTool {
|
|
|
565
546
|
},
|
|
566
547
|
{
|
|
567
548
|
featureNumber: 2,
|
|
568
|
-
status: '
|
|
549
|
+
status: 'in_progress',
|
|
569
550
|
addTasks: ['New task 1', 'New task 2'],
|
|
570
551
|
},
|
|
571
552
|
],
|
|
@@ -640,7 +621,7 @@ export class PlanTool {
|
|
|
640
621
|
...plan.features.map((feature, i) => {
|
|
641
622
|
const taskCount = feature.tasks?.length || 0;
|
|
642
623
|
const completedCount = feature.tasks?.filter(t => t.completedAt).length || 0;
|
|
643
|
-
const statusEmoji = feature.status === 'completed' ? '✅' : feature.status === '
|
|
624
|
+
const statusEmoji = feature.status === 'completed' ? '✅' : feature.status === 'in_progress' ? '🔄' : '📋';
|
|
644
625
|
return `${statusEmoji} **Feature ${i + 1}: ${feature.title}** (${completedCount}/${taskCount} tasks complete)`;
|
|
645
626
|
}),
|
|
646
627
|
``,
|
|
@@ -805,33 +786,6 @@ export class PlanTool {
|
|
|
805
786
|
return createErrorResult(`Failed to list plans: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
806
787
|
}
|
|
807
788
|
}
|
|
808
|
-
static async handleArchive(args, context) {
|
|
809
|
-
try {
|
|
810
|
-
// planId is required for archive operation
|
|
811
|
-
if (!args.planId) {
|
|
812
|
-
return createErrorResult('Plan ID is required for archive operation. Use "plan operation=list" to see available plans.');
|
|
813
|
-
}
|
|
814
|
-
// Create service with RequestContext
|
|
815
|
-
const planningService = new PlanningService(context);
|
|
816
|
-
// Load the specific plan to archive
|
|
817
|
-
const plan = await planningService.loadPlan({ planId: args.planId });
|
|
818
|
-
if (!plan) {
|
|
819
|
-
return createErrorResult(`Plan not found: ${args.planId}. Use "plan operation=list" to see available plans.`);
|
|
820
|
-
}
|
|
821
|
-
// Archive the plan with explicit args
|
|
822
|
-
const archived = await planningService.archivePlan({
|
|
823
|
-
planId: plan.id,
|
|
824
|
-
reason: args.reason
|
|
825
|
-
});
|
|
826
|
-
if (!archived) {
|
|
827
|
-
return createErrorResult('Failed to archive plan.');
|
|
828
|
-
}
|
|
829
|
-
return createSuccessResult(`✅ Archived plan: "${plan.title}"\nReason: ${args.reason || 'Completed'}`);
|
|
830
|
-
}
|
|
831
|
-
catch (error) {
|
|
832
|
-
return createErrorResult(`Failed to archive plan: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
789
|
static async handleStartDashboard(args, context) {
|
|
836
790
|
try {
|
|
837
791
|
// Check if dashboard is already running
|
|
@@ -856,7 +810,7 @@ export class PlanTool {
|
|
|
856
810
|
'',
|
|
857
811
|
'The dashboard provides:',
|
|
858
812
|
'• Real-time plan monitoring',
|
|
859
|
-
'• Plan management (create,
|
|
813
|
+
'• Plan management (create, delete)',
|
|
860
814
|
'• Persona overview',
|
|
861
815
|
'• Auto-refresh on changes',
|
|
862
816
|
'',
|
|
@@ -638,7 +638,7 @@ Persists and retrieves quality analysis results. Analysis is performed by agents
|
|
|
638
638
|
lines.push('## Next Steps');
|
|
639
639
|
lines.push('');
|
|
640
640
|
for (const fixId of fixIds) {
|
|
641
|
-
lines.push(`1. Run \`npx tiny-brain sync-
|
|
641
|
+
lines.push(`1. Run \`npx tiny-brain sync-progress .tiny-brain/fixes/${fixId}.md\``);
|
|
642
642
|
}
|
|
643
643
|
lines.push('2. Start working on fixes using the /fix workflow');
|
|
644
644
|
lines.push('3. Track progress in the dashboard');
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ToolResult } from '../index.js';
|
|
2
|
+
import type { RequestContext } from '../../types/request-context.js';
|
|
3
|
+
import { Tool as MCPTool } from '@modelcontextprotocol/sdk/types.js';
|
|
4
|
+
export declare class RecommendationsTool {
|
|
5
|
+
static getToolDefinition(): MCPTool;
|
|
6
|
+
static execute(args: Record<string, unknown>, context: RequestContext): Promise<ToolResult>;
|
|
7
|
+
private static handleList;
|
|
8
|
+
private static handleAccept;
|
|
9
|
+
private static handleDismiss;
|
|
10
|
+
private static handleRestore;
|
|
11
|
+
private static handleGraduated;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=recommendations.tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recommendations.tool.d.ts","sourceRoot":"","sources":["../../../src/tools/recommendations/recommendations.tool.ts"],"names":[],"mappings":"AACA,OAAO,EAA0C,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,oCAAoC,CAAC;AASrE,qBAAa,mBAAmB;IAC9B,MAAM,CAAC,iBAAiB,IAAI,OAAO;WA8BtB,OAAO,CAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,UAAU,CAAC;mBAkCD,UAAU;mBAgCV,YAAY;mBAqCZ,aAAa;mBAgCb,aAAa;mBAwCb,eAAe;CAkBrC"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createSuccessResult, createErrorResult } from '../index.js';
|
|
3
|
+
import { RecommendationService } from '@magic-ingredients/tiny-brain-core';
|
|
4
|
+
const RecommendationsArgsSchema = z.object({
|
|
5
|
+
operation: z.enum(['list', 'accept', 'dismiss', 'restore', 'graduated']),
|
|
6
|
+
items: z.array(z.string()).optional(),
|
|
7
|
+
});
|
|
8
|
+
export class RecommendationsTool {
|
|
9
|
+
static getToolDefinition() {
|
|
10
|
+
return {
|
|
11
|
+
name: 'recommendations',
|
|
12
|
+
description: `Manage skill, agent, and hook recommendations for your repository.
|
|
13
|
+
|
|
14
|
+
Operations:
|
|
15
|
+
- list: Show pending recommendations grouped by type
|
|
16
|
+
- accept: Accept recommendations by name (installs them)
|
|
17
|
+
- dismiss: Dismiss recommendations by name
|
|
18
|
+
- restore: Restore previously dismissed recommendations
|
|
19
|
+
- graduated: Show all accepted/installed recommendations with provenance`,
|
|
20
|
+
inputSchema: {
|
|
21
|
+
type: 'object',
|
|
22
|
+
properties: {
|
|
23
|
+
operation: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
enum: ['list', 'accept', 'dismiss', 'restore', 'graduated'],
|
|
26
|
+
description: 'The recommendations operation to perform',
|
|
27
|
+
},
|
|
28
|
+
items: {
|
|
29
|
+
type: 'array',
|
|
30
|
+
items: { type: 'string' },
|
|
31
|
+
description: 'Recommendation names (required for accept, dismiss, restore)',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
required: ['operation'],
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
static async execute(args, context) {
|
|
39
|
+
try {
|
|
40
|
+
const validatedArgs = RecommendationsArgsSchema.parse(args);
|
|
41
|
+
if (!context.repositoryRoot) {
|
|
42
|
+
return createErrorResult('Repository root is required for recommendations');
|
|
43
|
+
}
|
|
44
|
+
const service = new RecommendationService(context.repositoryRoot);
|
|
45
|
+
switch (validatedArgs.operation) {
|
|
46
|
+
case 'list':
|
|
47
|
+
return await RecommendationsTool.handleList(service);
|
|
48
|
+
case 'accept':
|
|
49
|
+
return await RecommendationsTool.handleAccept(validatedArgs, service);
|
|
50
|
+
case 'dismiss':
|
|
51
|
+
return await RecommendationsTool.handleDismiss(validatedArgs, service);
|
|
52
|
+
case 'restore':
|
|
53
|
+
return await RecommendationsTool.handleRestore(validatedArgs, service);
|
|
54
|
+
case 'graduated':
|
|
55
|
+
return await RecommendationsTool.handleGraduated(service);
|
|
56
|
+
default:
|
|
57
|
+
return createErrorResult(`Unknown operation: ${validatedArgs.operation}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (error instanceof z.ZodError) {
|
|
62
|
+
return createErrorResult(`Invalid arguments: ${error.errors.map((e) => e.message).join(', ')}`);
|
|
63
|
+
}
|
|
64
|
+
return createErrorResult(error instanceof Error ? error.message : 'Unknown error occurred');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
static async handleList(service) {
|
|
68
|
+
const pending = await service.getPendingRecommendations();
|
|
69
|
+
if (pending.length === 0) {
|
|
70
|
+
return createSuccessResult('No pending recommendations. Run `analyse` to fetch new recommendations.');
|
|
71
|
+
}
|
|
72
|
+
const grouped = {};
|
|
73
|
+
for (const rec of pending) {
|
|
74
|
+
const typeLabel = rec.type === 'skill' ? 'Skills' : rec.type === 'agent' ? 'Agents' : 'Hooks';
|
|
75
|
+
if (!grouped[typeLabel]) {
|
|
76
|
+
grouped[typeLabel] = [];
|
|
77
|
+
}
|
|
78
|
+
grouped[typeLabel].push(rec);
|
|
79
|
+
}
|
|
80
|
+
const lines = ['# Pending Recommendations', ''];
|
|
81
|
+
for (const [type, recs] of Object.entries(grouped)) {
|
|
82
|
+
lines.push(`## ${type}`, '');
|
|
83
|
+
for (const rec of recs) {
|
|
84
|
+
lines.push(`- **${rec.name}**: ${rec.description}`);
|
|
85
|
+
lines.push(` _Reason: ${rec.reason}_`);
|
|
86
|
+
}
|
|
87
|
+
lines.push('');
|
|
88
|
+
}
|
|
89
|
+
lines.push('---', '', 'Use `accept` with item names to install recommendations.');
|
|
90
|
+
return createSuccessResult(lines.join('\n'));
|
|
91
|
+
}
|
|
92
|
+
static async handleAccept(args, service) {
|
|
93
|
+
if (!args.items || args.items.length === 0) {
|
|
94
|
+
return createErrorResult('`items` is required for accept operation');
|
|
95
|
+
}
|
|
96
|
+
const recsFile = await service.readRecommendations();
|
|
97
|
+
if (!recsFile) {
|
|
98
|
+
return createErrorResult('No recommendations file found. Run `analyse` first.');
|
|
99
|
+
}
|
|
100
|
+
const results = ['# Accept Results', ''];
|
|
101
|
+
for (const name of args.items) {
|
|
102
|
+
const match = recsFile.recommendations.find((r) => r.name.toLowerCase() === name.toLowerCase());
|
|
103
|
+
if (!match) {
|
|
104
|
+
results.push(`- **${name}**: Not found (unrecognized name)`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
await service.acceptRecommendation(match);
|
|
109
|
+
results.push(`- **${match.name}**: Accepted`);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
const msg = error instanceof Error ? error.message : 'Unknown error';
|
|
113
|
+
results.push(`- **${match.name}**: Failed - ${msg}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return createSuccessResult(results.join('\n'));
|
|
117
|
+
}
|
|
118
|
+
static async handleDismiss(args, service) {
|
|
119
|
+
if (!args.items || args.items.length === 0) {
|
|
120
|
+
return createErrorResult('`items` is required for dismiss operation');
|
|
121
|
+
}
|
|
122
|
+
const recsFile = await service.readRecommendations();
|
|
123
|
+
if (!recsFile) {
|
|
124
|
+
return createErrorResult('No recommendations file found. Run `analyse` first.');
|
|
125
|
+
}
|
|
126
|
+
const results = ['# Dismiss Results', ''];
|
|
127
|
+
for (const name of args.items) {
|
|
128
|
+
const match = recsFile.recommendations.find((r) => r.name.toLowerCase() === name.toLowerCase());
|
|
129
|
+
if (!match) {
|
|
130
|
+
results.push(`- **${name}**: Not found (unrecognized name)`);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
await service.dismissRecommendation(match.id);
|
|
134
|
+
results.push(`- **${match.name}**: Dismissed`);
|
|
135
|
+
}
|
|
136
|
+
return createSuccessResult(results.join('\n'));
|
|
137
|
+
}
|
|
138
|
+
static async handleRestore(args, service) {
|
|
139
|
+
if (!args.items || args.items.length === 0) {
|
|
140
|
+
return createErrorResult('`items` is required for restore operation');
|
|
141
|
+
}
|
|
142
|
+
const recsFile = await service.readRecommendations();
|
|
143
|
+
if (!recsFile) {
|
|
144
|
+
return createErrorResult('No recommendations file found. Run `analyse` first.');
|
|
145
|
+
}
|
|
146
|
+
const dismissed = await service.readDismissedRecommendations();
|
|
147
|
+
const dismissedIds = new Set(dismissed.map((d) => d.recommendationId));
|
|
148
|
+
const results = ['# Restore Results', ''];
|
|
149
|
+
for (const name of args.items) {
|
|
150
|
+
const match = recsFile.recommendations.find((r) => r.name.toLowerCase() === name.toLowerCase());
|
|
151
|
+
if (!match) {
|
|
152
|
+
results.push(`- **${name}**: Not found (unrecognized name)`);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (!dismissedIds.has(match.id)) {
|
|
156
|
+
results.push(`- **${match.name}**: Not dismissed (nothing to restore)`);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
await service.restoreRecommendation(match.id);
|
|
160
|
+
results.push(`- **${match.name}**: Restored`);
|
|
161
|
+
}
|
|
162
|
+
return createSuccessResult(results.join('\n'));
|
|
163
|
+
}
|
|
164
|
+
static async handleGraduated(service) {
|
|
165
|
+
const installed = await service.readInstalledRecommendations();
|
|
166
|
+
if (installed.length === 0) {
|
|
167
|
+
return createSuccessResult('No graduated recommendations yet. Use `accept` to install recommendations.');
|
|
168
|
+
}
|
|
169
|
+
const lines = ['# Graduated Recommendations', ''];
|
|
170
|
+
for (const item of installed) {
|
|
171
|
+
const { origin } = item;
|
|
172
|
+
lines.push(`- **${origin.recommendationName}** (${origin.type})`);
|
|
173
|
+
lines.push(` Source: ${origin.source}`);
|
|
174
|
+
lines.push(` Accepted: ${origin.acceptedAt}`);
|
|
175
|
+
}
|
|
176
|
+
return createSuccessResult(lines.join('\n'));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-registry.d.ts","sourceRoot":"","sources":["../../src/tools/tool-registry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"tool-registry.d.ts","sourceRoot":"","sources":["../../src/tools/tool-registry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAgBH,OAAO,KAAK,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,qBAAa,YAAY;IACvB;;OAEG;IACH,MAAM,CAAC,QAAQ,IAAI,OAAO,EAAE;IA0C5B;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;CAqB9D;AAGD,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,8BAAqC,CAAC;AAChF,eAAO,MAAM,eAAe,gBAAyD,CAAC"}
|
|
@@ -16,6 +16,7 @@ import { AnalyseTool } from './analyse.tool.js';
|
|
|
16
16
|
import { QualityTool } from './quality/quality.tool.js';
|
|
17
17
|
import { ConfigTool } from './config/config.tool.js';
|
|
18
18
|
import { DashboardTool } from './dashboard/dashboard.tool.js';
|
|
19
|
+
import { RecommendationsTool } from './recommendations/recommendations.tool.js';
|
|
19
20
|
export class ToolRegistry {
|
|
20
21
|
/**
|
|
21
22
|
* Get all available tool definitions from all tools
|
|
@@ -39,6 +40,8 @@ export class ToolRegistry {
|
|
|
39
40
|
ConfigTool.getToolDefinition(),
|
|
40
41
|
// Dashboard tool - restart dashboard server (dev maintenance)
|
|
41
42
|
DashboardTool.getToolDefinition(),
|
|
43
|
+
// Recommendations tool - browse, accept, dismiss recommendations
|
|
44
|
+
RecommendationsTool.getToolDefinition(),
|
|
42
45
|
// Response tools (disabled for later release)
|
|
43
46
|
// AnalyseRequestTool.getToolDefinition(),
|
|
44
47
|
// ValidateResponseTool.getToolDefinition(),
|
|
@@ -67,6 +70,7 @@ export class ToolRegistry {
|
|
|
67
70
|
quality: QualityTool,
|
|
68
71
|
config: ConfigTool,
|
|
69
72
|
dev_restart_dashboard: DashboardTool,
|
|
73
|
+
recommendations: RecommendationsTool,
|
|
70
74
|
};
|
|
71
75
|
return toolMap[name];
|
|
72
76
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magic-ingredients/tiny-brain-local",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.1",
|
|
4
4
|
"description": "MCP server for Tiny Brain AI assistant",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"dev": "tsx src/index.ts",
|
|
22
22
|
"test": "vitest run",
|
|
23
23
|
"test:coverage": "vitest run --coverage",
|
|
24
|
-
"lint": "eslint src",
|
|
24
|
+
"lint": "eslint --cache src",
|
|
25
25
|
"typecheck": "tsc --noEmit",
|
|
26
26
|
"prepublishOnly": "npm run build",
|
|
27
27
|
"dxt:clean": "rm -rf dxt/node_modules dxt/package-lock.json dxt/dist dxt/server/index.js",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"dxt:init": "cd dxt && dxt init"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@magic-ingredients/tiny-brain-core": "^0.
|
|
34
|
+
"@magic-ingredients/tiny-brain-core": "^0.21.1",
|
|
35
35
|
"@magic-ingredients/tiny-brain-dashboard": "file:../tiny-brain-dashboard",
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.0.6",
|
|
37
37
|
"chalk": "^5.3.0",
|
|
@@ -42,13 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/minimatch": "^5.1.2",
|
|
45
|
-
"@types/
|
|
46
|
-
"@types/semver": "^7.7.1",
|
|
47
|
-
"@vitest/coverage-v8": "^2.1.8",
|
|
48
|
-
"eslint": "^8.57.0",
|
|
49
|
-
"tsx": "^4.19.2",
|
|
50
|
-
"typescript": "^5.4.5",
|
|
51
|
-
"vitest": "^2.1.8"
|
|
45
|
+
"@types/semver": "^7.7.1"
|
|
52
46
|
},
|
|
53
47
|
"engines": {
|
|
54
48
|
"node": ">=18.0.0"
|