@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.
Files changed (32) hide show
  1. package/dist/core/mcp-server.d.ts.map +1 -1
  2. package/dist/core/mcp-server.js +11 -8
  3. package/dist/core/repo-registration.d.ts +30 -0
  4. package/dist/core/repo-registration.d.ts.map +1 -0
  5. package/dist/core/repo-registration.js +34 -0
  6. package/dist/prompts/persona/persona.prompt.js +1 -1
  7. package/dist/prompts/planning/planning.prompt.d.ts +0 -8
  8. package/dist/prompts/planning/planning.prompt.d.ts.map +1 -1
  9. package/dist/prompts/planning/planning.prompt.js +0 -193
  10. package/dist/prompts/prompt-registry.d.ts.map +1 -1
  11. package/dist/prompts/prompt-registry.js +0 -2
  12. package/dist/services/analyse-service.d.ts +15 -29
  13. package/dist/services/analyse-service.d.ts.map +1 -1
  14. package/dist/services/analyse-service.js +99 -236
  15. package/dist/services/repo-service.d.ts +5 -0
  16. package/dist/services/repo-service.d.ts.map +1 -1
  17. package/dist/services/repo-service.js +67 -42
  18. package/dist/storage/local-filesystem-adapter.d.ts.map +1 -1
  19. package/dist/storage/local-filesystem-adapter.js +0 -3
  20. package/dist/tools/dashboard/dashboard.tool.d.ts +4 -3
  21. package/dist/tools/dashboard/dashboard.tool.d.ts.map +1 -1
  22. package/dist/tools/dashboard/dashboard.tool.js +53 -6
  23. package/dist/tools/plan/plan.tool.d.ts +0 -1
  24. package/dist/tools/plan/plan.tool.d.ts.map +1 -1
  25. package/dist/tools/plan/plan.tool.js +10 -56
  26. package/dist/tools/quality/quality.tool.js +1 -1
  27. package/dist/tools/recommendations/recommendations.tool.d.ts +13 -0
  28. package/dist/tools/recommendations/recommendations.tool.d.ts.map +1 -0
  29. package/dist/tools/recommendations/recommendations.tool.js +178 -0
  30. package/dist/tools/tool-registry.d.ts.map +1 -1
  31. package/dist/tools/tool-registry.js +4 -0
  32. 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 - Restart the dashboard server
5
+ * Dashboard Tool - Rebuild and restart the dashboard server
6
6
  *
7
- * Developer maintenance tool for force-reloading the dashboard,
8
- * e.g. after rebuilding packages.
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":"AAAA,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;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,MAAM,CAAC,iBAAiB,IAAI,OAAO;WAatB,OAAO,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC;CAiBzF"}
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 - Restart the dashboard server
4
+ * Dashboard Tool - Rebuild and restart the dashboard server
4
5
  *
5
- * Developer maintenance tool for force-reloading the dashboard,
6
- * e.g. after rebuilding packages.
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
- const result = await launcher.restart(context, {});
28
- return createSuccessResult(`Dashboard restarted successfully at ${result.url}`);
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(`Failed to restart dashboard: ${error instanceof Error ? error.message : 'Unknown error'}`);
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;AA2LrE,qBAAa,QAAQ;IACnB,MAAM,CAAC,iBAAiB,IAAI,OAAO;WA4LtB,OAAO,CAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,UAAU,CAAC;mBA2ED,YAAY;mBA+LZ,aAAa;mBAyCb,eAAe;mBA0Df,YAAY;mBA+CZ,YAAY;mBAsDZ,YAAY;mBAiCZ,UAAU;mBA2DV,aAAa;mBA2Cb,oBAAoB;mBA+CpB,mBAAmB;mBAsBnB,qBAAqB;mBAqCrB,SAAS;mBAgGT,eAAe;IAuFpC;;OAEG;mBACkB,UAAU;CA2DhC"}
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', 'archive', 'adr', 'create-adr', 'sync', 'start-dashboard', 'stop-dashboard', 'dashboard-status']),
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(['defined', 'tested', 'completed']).optional(),
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(['defined', 'tested', 'completed']).optional(),
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(['defined', 'tested', 'completed']).optional(),
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', 'archive', 'adr', 'create-adr', 'start-dashboard', 'stop-dashboard', 'dashboard-status'],
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: ['defined', 'tested', 'completed'] },
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: ['defined', 'tested', 'completed'] },
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: 'tested',
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 === 'tested' ? '🔄' : '📋';
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, archive, delete)',
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-file .tiny-brain/fixes/${fixId}.md\``);
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;AAeH,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;IAuC5B;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;CAoB9D;AAGD,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,8BAAqC,CAAC;AAChF,eAAO,MAAM,eAAe,gBAAyD,CAAC"}
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.20.1",
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.20.1",
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/node": "^20.14.2",
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"