@magic-ingredients/tiny-brain-local 0.19.0 → 0.20.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.
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../../src/core/mcp-server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA0BH,OAAO,KAAK,EAA4B,QAAQ,EAAkC,MAAM,oCAAoC,CAAC;AAE7H,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAQ5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,2CAA2C,CAAC;AAE9E;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,OAAO,CAAC,EAAE;QACR,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IAEF,KAAK,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,QAAQ,CAAC;KACrB,CAAC;CACH;AAED;;GAEG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,sBAAsB,CAA0D;IACxF,OAAO,CAAC,mBAAmB,CAA2G;IACtI,OAAO,CAAC,UAAU,CAA6E;IAC/F,OAAO,CAAC,iBAAiB,CAAkC;IAC3D,OAAO,CAAC,WAAW,CAAiB;IACpC,OAAO,CAAC,SAAS,CAAC,CAAY;IAC9B,OAAO,CAAC,gBAAgB,CAAC,CAAmB;gBAEhC,MAAM,GAAE,SAAc;IAwFlC,OAAO,CAAC,aAAa;IAarB;;OAEG;IACH,SAAS,IAAI,SAAS;IAItB;;OAEG;IACH,mBAAmB,IAAI,gBAAgB,GAAG,SAAS;IAInD;;OAEG;IACH,oBAAoB,IAAI,iBAAiB,GAAG,IAAI;IAIhD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAsBjC;;;;;;;OAOG;YACW,sBAAsB;IA+DpC;;OAEG;YACW,iBAAiB;IAW/B;;;OAGG;YACW,oBAAoB;IA6BlC;;OAEG;YACW,oBAAoB;IAkHlC;;;;;;OAMG;YACW,yBAAyB;IA+CvC;;OAEG;YACW,kBAAkB;IAsBhC;;OAEG;YACW,wBAAwB;IA8BtC;;OAEG;YACW,mBAAmB;IA8BjC;;OAEG;IACG,OAAO,CAAC,SAAS,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAK5C;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;YAiBjB,eAAe;YAMf,cAAc;YA+Fd,iBAAiB;YAMjB,eAAe;YAqEf,iBAAiB;IAS/B;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAoB7B;;OAEG;IACH,OAAO,CAAC,oBAAoB;CAkD7B"}
1
+ {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../../src/core/mcp-server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA0BH,OAAO,KAAK,EAA4B,QAAQ,EAAkC,MAAM,oCAAoC,CAAC;AAE7H,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAQ5E,OAAO,EAAE,iBAAiB,EAAE,MAAM,2CAA2C,CAAC;AAE9E;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,OAAO,CAAC,EAAE;QACR,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IAEF,KAAK,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,QAAQ,CAAC;KACrB,CAAC;CACH;AAED;;GAEG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,sBAAsB,CAA0D;IACxF,OAAO,CAAC,mBAAmB,CAA2G;IACtI,OAAO,CAAC,UAAU,CAA6E;IAC/F,OAAO,CAAC,iBAAiB,CAAkC;IAC3D,OAAO,CAAC,WAAW,CAAiB;IACpC,OAAO,CAAC,SAAS,CAAC,CAAY;IAC9B,OAAO,CAAC,gBAAgB,CAAC,CAAmB;gBAEhC,MAAM,GAAE,SAAc;IAwFlC,OAAO,CAAC,aAAa;IAarB;;OAEG;IACH,SAAS,IAAI,SAAS;IAItB;;OAEG;IACH,mBAAmB,IAAI,gBAAgB,GAAG,SAAS;IAInD;;OAEG;IACH,oBAAoB,IAAI,iBAAiB,GAAG,IAAI;IAIhD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAsBjC;;;;;;;OAOG;YACW,sBAAsB;IA+DpC;;OAEG;YACW,iBAAiB;IAW/B;;;OAGG;YACW,oBAAoB;IA6BlC;;OAEG;YACW,oBAAoB;IAwGlC;;;;;;OAMG;YACW,yBAAyB;IA+CvC;;OAEG;YACW,kBAAkB;IAsBhC;;OAEG;YACW,wBAAwB;IA8BtC;;OAEG;YACW,mBAAmB;IA8BjC;;OAEG;IACG,OAAO,CAAC,SAAS,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAK5C;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;YAiBjB,eAAe;YAMf,cAAc;YA+Fd,iBAAiB;YAMjB,eAAe;YAqEf,iBAAiB;IAS/B;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAoB7B;;OAEG;IACH,OAAO,CAAC,oBAAoB;CAkD7B"}
@@ -8,7 +8,7 @@ import { existsSync, cpSync, readdirSync, readFileSync, writeFileSync, mkdirSync
8
8
  import { execSync } from 'child_process';
9
9
  import { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js';
10
10
  import { ListToolsRequestSchema, CallToolRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, SetLevelRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
11
- import { isDevelopment as checkIsDevelopment } from '@magic-ingredients/tiny-brain-core';
11
+ import { isDevelopment as checkIsDevelopment, detectClaudeCliStatus } from '@magic-ingredients/tiny-brain-core';
12
12
  import { AuthTokenService } from '../services/remote/auth-token-service.js';
13
13
  import { CredentialStorageService } from '../services/credential-storage.service.js';
14
14
  import { SystemPersonaService } from '../services/remote/system-persona-service.js';
@@ -276,12 +276,11 @@ export class MCPServer {
276
276
  hasClientSecret: !!this.config.account?.clientSecret,
277
277
  configKeys: Object.keys(this.config)
278
278
  });
279
- // Create credential service to check for credentials and LLM API key
279
+ // Create credential service to check for credentials
280
280
  const credentialService = new CredentialStorageService();
281
- // Check LLM API key availability (for dashboard to disable/enable skill invocation buttons)
282
- const hasLlmApiKey = await credentialService.hasLlmApiKey();
283
- // Create getter function for the actual API key (for skill invocation)
284
- const getLlmApiKey = () => credentialService.getLlmApiKey();
281
+ // Check Claude CLI availability (for dashboard to disable/enable skill invocation buttons)
282
+ const cliStatus = await detectClaudeCliStatus(this.logger);
283
+ const hasClaudeCli = cliStatus.authenticated;
285
284
  // Check for remote auth credentials
286
285
  if (!this.config.account?.clientId || !this.config.account?.clientSecret) {
287
286
  this.logger.info('No remote credentials configured in MCP config, checking tiny-brain CLI config...');
@@ -298,13 +297,11 @@ export class MCPServer {
298
297
  }
299
298
  else {
300
299
  this.logger.info('No credentials found in CLI config either, running in local-only mode');
301
- // Still set libraryAuth with hasLlmApiKey for dashboard
302
300
  this.baseContext.libraryAuth = {
303
301
  clientId: undefined,
304
302
  hasStoredSecret: false,
305
- hasLlmApiKey,
303
+ hasClaudeCli,
306
304
  token: undefined,
307
- getLlmApiKey
308
305
  };
309
306
  return;
310
307
  }
@@ -312,13 +309,11 @@ export class MCPServer {
312
309
  catch (error) {
313
310
  this.logger.warn('Error reading CLI config:', error);
314
311
  this.logger.info('Running in local-only mode');
315
- // Still set libraryAuth with hasLlmApiKey for dashboard
316
312
  this.baseContext.libraryAuth = {
317
313
  clientId: undefined,
318
314
  hasStoredSecret: false,
319
- hasLlmApiKey,
315
+ hasClaudeCli,
320
316
  token: undefined,
321
- getLlmApiKey
322
317
  };
323
318
  return;
324
319
  }
@@ -343,9 +338,8 @@ export class MCPServer {
343
338
  this.baseContext.libraryAuth = {
344
339
  clientId: this.config.account?.clientId,
345
340
  hasStoredSecret: true,
346
- hasLlmApiKey,
341
+ hasClaudeCli,
347
342
  token: token.token,
348
- getLlmApiKey
349
343
  };
350
344
  }
351
345
  else {
@@ -356,22 +350,19 @@ export class MCPServer {
356
350
  this.baseContext.libraryAuth = {
357
351
  clientId: this.config.account?.clientId,
358
352
  hasStoredSecret: !!this.config.account?.clientSecret,
359
- hasLlmApiKey,
353
+ hasClaudeCli,
360
354
  token: undefined,
361
- getLlmApiKey
362
355
  };
363
356
  }
364
357
  }
365
358
  catch (error) {
366
359
  this.logger.error('Unexpected error during remote authentication', error);
367
360
  // Continue in local mode - don't fail the entire initialization
368
- // Still set libraryAuth with hasLlmApiKey for dashboard
369
361
  this.baseContext.libraryAuth = {
370
362
  clientId: this.config.account?.clientId,
371
363
  hasStoredSecret: !!this.config.account?.clientSecret,
372
- hasLlmApiKey,
364
+ hasClaudeCli,
373
365
  token: undefined,
374
- getLlmApiKey
375
366
  };
376
367
  }
377
368
  }
@@ -29,6 +29,7 @@ export interface AnalyseResult {
29
29
  };
30
30
  skillPermissionsAdded?: string[];
31
31
  writtenTechContexts?: string[];
32
+ agentsMdStatus?: 'created' | 'updated' | 'up-to-date';
32
33
  }
33
34
  /**
34
35
  * Service for managing repository analysis
@@ -45,6 +46,20 @@ export declare class AnalyseService {
45
46
  * Perform repository analysis with unified behavior for initialized/uninitialized repos
46
47
  */
47
48
  performAnalysis(options?: AnalyseOptions): Promise<AnalyseResult>;
49
+ /**
50
+ * Generate AGENTS.md file in the repository root
51
+ * For monorepos, also generates per-package AGENTS.md files
52
+ * Uses AgentsMdService from tiny-brain-core
53
+ */
54
+ private generateAgentsMd;
55
+ /**
56
+ * Read package.json from each workspace directory to collect names and descriptions
57
+ */
58
+ private collectPackageDescriptions;
59
+ /**
60
+ * Generate AGENTS.md for each workspace package in a monorepo
61
+ */
62
+ private generatePerPackageAgentsMd;
48
63
  /**
49
64
  * Register repository in dashboard for visibility
50
65
  * Called after successful analysis regardless of invocation method
@@ -95,6 +110,10 @@ export declare class AnalyseService {
95
110
  * Format analysis output for display
96
111
  */
97
112
  formatAnalysisOutput(result: AnalyseResult): string;
113
+ /**
114
+ * Append rich metadata sections to output lines
115
+ */
116
+ private appendRichMetadataOutput;
98
117
  /**
99
118
  * Compare two tech stacks and return differences
100
119
  * Handles null previous analysis (first run)
@@ -1 +1 @@
1
- {"version":3,"file":"analyse-service.d.ts","sourceRoot":"","sources":["../../src/services/analyse-service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAEL,KAAK,YAAY,EAEjB,KAAK,aAAa,EAMnB,MAAM,oCAAoC,CAAC;AAS5C,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAGD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,YAAY,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,OAAO,CAAC,EAAE;QACR,cAAc,EAAE,MAAM,EAAE,CAAC;QACzB,gBAAgB,EAAE,MAAM,EAAE,CAAC;KAC5B,CAAC;IACF,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC;AAGD;;;GAGG;AACH,qBAAa,cAAc;IAMb,OAAO,CAAC,OAAO;IAL3B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,kBAAkB,CAAqB;gBAE3B,OAAO,EAAE,cAAc;IAO3C;;OAEG;IACG,eAAe,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC;IAwJ3E;;;OAGG;YACW,kBAAkB;IAoBhC;;OAEG;YACW,eAAe;IAc7B;;;OAGG;YACW,sBAAsB;IAwBpC;;;OAGG;YACW,sBAAsB;IAwBpC;;;OAGG;YACW,0BAA0B;IAwBxC;;;OAGG;YACW,wBAAwB;IAwBtC;;;;OAIG;YACW,kBAAkB;IA+EhC;;;;OAIG;YACW,sBAAsB;IAiEpC;;;OAGG;YACW,yBAAyB;IAkCvC;;OAEG;IACH,oBAAoB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM;IAiHnD;;;OAGG;IACH,iBAAiB,CAAC,gBAAgB,EAAE,YAAY,GAAG,IAAI,EAAE,eAAe,EAAE,YAAY,GAAG,aAAa;IA8BtG;;;;;OAKG;IACH,OAAO,CAAC,oBAAoB;IAY5B;;;;;OAKG;IACH,OAAO,CAAC,eAAe;CAWxB"}
1
+ {"version":3,"file":"analyse-service.d.ts","sourceRoot":"","sources":["../../src/services/analyse-service.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAEL,KAAK,YAAY,EAEjB,KAAK,aAAa,EAQnB,MAAM,oCAAoC,CAAC;AAS5C,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAGD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,YAAY,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,OAAO,CAAC,EAAE;QACR,cAAc,EAAE,MAAM,EAAE,CAAC;QACzB,gBAAgB,EAAE,MAAM,EAAE,CAAC;KAC5B,CAAC;IACF,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC/B,cAAc,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,YAAY,CAAC;CACvD;AAGD;;;GAGG;AACH,qBAAa,cAAc;IAMb,OAAO,CAAC,OAAO;IAL3B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,kBAAkB,CAAqB;gBAE3B,OAAO,EAAE,cAAc;IAO3C;;OAEG;IACG,eAAe,CAAC,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,aAAa,CAAC;IAyK3E;;;;OAIG;YACW,gBAAgB;IAmD9B;;OAEG;YACW,0BAA0B;IAyBxC;;OAEG;YACW,0BAA0B;IA+BxC;;;OAGG;YACW,kBAAkB;IAoBhC;;OAEG;YACW,eAAe;IAc7B;;;OAGG;YACW,sBAAsB;IAwBpC;;;OAGG;YACW,sBAAsB;IAwBpC;;;OAGG;YACW,0BAA0B;IAwBxC;;;OAGG;YACW,wBAAwB;IAwBtC;;;;OAIG;YACW,kBAAkB;IA+EhC;;;;OAIG;YACW,sBAAsB;IAiEpC;;;OAGG;YACW,yBAAyB;IAkCvC;;OAEG;IACH,oBAAoB,CAAC,MAAM,EAAE,aAAa,GAAG,MAAM;IAgInD;;OAEG;IACH,OAAO,CAAC,wBAAwB;IA2ChC;;;OAGG;IACH,iBAAiB,CAAC,gBAAgB,EAAE,YAAY,GAAG,IAAI,EAAE,eAAe,EAAE,YAAY,GAAG,aAAa;IA8BtG;;;;;OAKG;IACH,OAAO,CAAC,oBAAoB;IAY5B;;;;;OAKG;IACH,OAAO,CAAC,eAAe;CAWxB"}
@@ -4,7 +4,7 @@
4
4
  * Handles repository tech stack analysis.
5
5
  * Focused solely on analysis - no context file management.
6
6
  */
7
- import { analyseRepository, formatTechStackChanges, detectTechStackChanges, ConfigService, RepoConfigService, LibraryClient, TechContextService, } from '@magic-ingredients/tiny-brain-core';
7
+ import { analyseRepository, formatTechStackChanges, detectTechStackChanges, ConfigService, RepoConfigService, LibraryClient, TechContextService, AgentsMdService, } from '@magic-ingredients/tiny-brain-core';
8
8
  import { RepoService } from './repo-service.js';
9
9
  import { fileURLToPath } from 'url';
10
10
  import { dirname, join } from 'path';
@@ -60,6 +60,13 @@ export class AnalyseService {
60
60
  primaryLanguage: currentAnalysis.primaryLanguage,
61
61
  documentationPattern: currentAnalysis.documentationPattern,
62
62
  documentationLocations: currentAnalysis.documentationLocations,
63
+ // Rich metadata
64
+ packageManager: currentAnalysis.packageManager,
65
+ scripts: currentAnalysis.scripts,
66
+ linting: currentAnalysis.linting,
67
+ monorepo: currentAnalysis.monorepo,
68
+ projectName: currentAnalysis.projectName,
69
+ sourceDirectories: currentAnalysis.sourceDirectories,
63
70
  };
64
71
  const stackChanged = await this.techContextService.hasStackChanged(analysisInput);
65
72
  const existingAnalysis = await this.techContextService.readAnalysis();
@@ -79,6 +86,12 @@ export class AnalyseService {
79
86
  if (!dryRun) {
80
87
  await this.techContextService.syncAgents(enableAgentic);
81
88
  }
89
+ // Generate AGENTS.md (if config allows)
90
+ let agentsMdStatus;
91
+ const manageAgentsMd = await this.configService.isManageAgentsMdEnabled();
92
+ if (!dryRun && manageAgentsMd) {
93
+ agentsMdStatus = await this.generateAgentsMd(currentAnalysis);
94
+ }
82
95
  // Write workflow sections to CLAUDE.md based on config flags (SDD, TDD)
83
96
  if (!dryRun) {
84
97
  await this.repoService.writeWorkflowSections({ contextPath });
@@ -105,7 +118,8 @@ export class AnalyseService {
105
118
  contextBlockVersion,
106
119
  contextBlockUpdated,
107
120
  skillPermissionsAdded: skillPermissionsAdded.length > 0 ? skillPermissionsAdded : undefined,
108
- writtenTechContexts
121
+ writtenTechContexts,
122
+ agentsMdStatus,
109
123
  };
110
124
  }
111
125
  if (!stackChanged) {
@@ -125,7 +139,8 @@ export class AnalyseService {
125
139
  contextBlockVersion,
126
140
  contextBlockUpdated,
127
141
  skillPermissionsAdded: skillPermissionsAdded.length > 0 ? skillPermissionsAdded : undefined,
128
- writtenTechContexts
142
+ writtenTechContexts,
143
+ agentsMdStatus,
129
144
  };
130
145
  }
131
146
  // Changes detected - compute diff for display purposes
@@ -163,9 +178,108 @@ export class AnalyseService {
163
178
  techStackRemoved: techStackDiff.removed
164
179
  },
165
180
  skillPermissionsAdded: skillPermissionsAdded.length > 0 ? skillPermissionsAdded : undefined,
166
- writtenTechContexts
181
+ writtenTechContexts,
182
+ agentsMdStatus,
167
183
  };
168
184
  }
185
+ /**
186
+ * Generate AGENTS.md file in the repository root
187
+ * For monorepos, also generates per-package AGENTS.md files
188
+ * Uses AgentsMdService from tiny-brain-core
189
+ */
190
+ async generateAgentsMd(analysis) {
191
+ const repoRoot = process.cwd();
192
+ const agentsMdService = new AgentsMdService(repoRoot);
193
+ const agentsMdPath = join(repoRoot, 'AGENTS.md');
194
+ // Read existing AGENTS.md if present
195
+ let existingContent;
196
+ try {
197
+ existingContent = await readFile(agentsMdPath, 'utf-8');
198
+ }
199
+ catch {
200
+ // File doesn't exist
201
+ }
202
+ // Extract README excerpt
203
+ const readmeExcerpt = await agentsMdService.extractReadmeExcerpt();
204
+ // Collect package descriptions for monorepo
205
+ let packageDescriptions;
206
+ if (analysis.monorepo?.packages && analysis.monorepo.packages.length > 0) {
207
+ packageDescriptions = await this.collectPackageDescriptions(repoRoot, analysis.monorepo.packages);
208
+ }
209
+ // Generate root content
210
+ const result = await agentsMdService.generateContent({
211
+ analysis,
212
+ readmeExcerpt,
213
+ repoPath: repoRoot,
214
+ existingContent,
215
+ packageDescriptions,
216
+ });
217
+ if (!existingContent) {
218
+ await writeFile(agentsMdPath, result.content, 'utf-8');
219
+ this.context.logger.info('Created AGENTS.md');
220
+ }
221
+ else if (!result.contentChanged) {
222
+ this.context.logger.info('AGENTS.md is up to date');
223
+ }
224
+ else {
225
+ await writeFile(agentsMdPath, result.content, 'utf-8');
226
+ this.context.logger.info('Updated AGENTS.md');
227
+ }
228
+ // Generate per-package AGENTS.md for monorepo
229
+ if (analysis.monorepo?.packages && analysis.monorepo.packages.length > 0) {
230
+ await this.generatePerPackageAgentsMd(repoRoot, analysis, agentsMdService);
231
+ }
232
+ if (!existingContent)
233
+ return 'created';
234
+ if (!result.contentChanged)
235
+ return 'up-to-date';
236
+ return 'updated';
237
+ }
238
+ /**
239
+ * Read package.json from each workspace directory to collect names and descriptions
240
+ */
241
+ async collectPackageDescriptions(repoRoot, packages) {
242
+ const descriptions = [];
243
+ for (const pkgPath of packages) {
244
+ const fullPath = join(repoRoot, pkgPath);
245
+ const pkgJsonPath = join(fullPath, 'package.json');
246
+ try {
247
+ const raw = await readFile(pkgJsonPath, 'utf-8');
248
+ const pkgJson = JSON.parse(raw);
249
+ descriptions.push({
250
+ name: pkgJson.name,
251
+ path: pkgPath,
252
+ description: pkgJson.description,
253
+ });
254
+ }
255
+ catch {
256
+ descriptions.push({ path: pkgPath });
257
+ }
258
+ }
259
+ return descriptions;
260
+ }
261
+ /**
262
+ * Generate AGENTS.md for each workspace package in a monorepo
263
+ */
264
+ async generatePerPackageAgentsMd(repoRoot, analysis, agentsMdService) {
265
+ const packages = analysis.monorepo?.packages ?? [];
266
+ for (const pkgPath of packages) {
267
+ const fullPkgPath = join(repoRoot, pkgPath);
268
+ const pkgAgentsMdPath = join(fullPkgPath, 'AGENTS.md');
269
+ let existingPkgContent;
270
+ try {
271
+ existingPkgContent = await readFile(pkgAgentsMdPath, 'utf-8');
272
+ }
273
+ catch {
274
+ // File doesn't exist
275
+ }
276
+ const pkgResult = await agentsMdService.generatePackageContent(fullPkgPath, analysis, existingPkgContent);
277
+ if (!existingPkgContent || pkgResult.contentChanged) {
278
+ await writeFile(pkgAgentsMdPath, pkgResult.content, 'utf-8');
279
+ this.context.logger.debug(`Wrote AGENTS.md for ${pkgPath}`);
280
+ }
281
+ }
282
+ }
169
283
  /**
170
284
  * Register repository in dashboard for visibility
171
285
  * Called after successful analysis regardless of invocation method
@@ -506,6 +620,9 @@ export class AnalyseService {
506
620
  lines.push(` Frameworks: ${result.analysis.frameworks.join(', ')}`);
507
621
  lines.push(` Build Tools: ${result.analysis.buildTools.join(', ')}`);
508
622
  lines.push(` Testing Tools: ${result.analysis.testingTools.join(', ')}`);
623
+ if (result.analysis.packageManager) {
624
+ lines.push(` Package Manager: ${result.analysis.packageManager}`);
625
+ }
509
626
  if (result.analysis.documentationPattern) {
510
627
  lines.push(`\n📚 Documentation: ${result.analysis.documentationPattern}`);
511
628
  if (result.analysis.documentationLocations) {
@@ -578,8 +695,64 @@ export class AnalyseService {
578
695
  lines.push(' None');
579
696
  }
580
697
  }
698
+ // Rich metadata sections (shown in all cases when data exists)
699
+ this.appendRichMetadataOutput(lines, result.analysis);
700
+ // AGENTS.md status (shown in all cases)
701
+ if (result.agentsMdStatus) {
702
+ const statusLabel = result.agentsMdStatus === 'created' ? 'Created'
703
+ : result.agentsMdStatus === 'updated' ? 'Updated'
704
+ : 'Up to date';
705
+ lines.push(`\nAGENTS.md: ${statusLabel}`);
706
+ }
581
707
  return lines.join('\n');
582
708
  }
709
+ /**
710
+ * Append rich metadata sections to output lines
711
+ */
712
+ appendRichMetadataOutput(lines, analysis) {
713
+ // Package manager (shown when not already in tech stack section)
714
+ if (analysis.packageManager) {
715
+ lines.push(`\n Package Manager: ${analysis.packageManager}`);
716
+ }
717
+ // Commands section
718
+ const scripts = analysis.scripts;
719
+ if (scripts && (scripts.test?.command || scripts.build?.command || scripts.dev?.command || scripts.lint?.command || scripts.e2e?.command)) {
720
+ lines.push('\n📋 Commands:');
721
+ if (scripts.test?.command)
722
+ lines.push(` Test: ${scripts.test.command}`);
723
+ if (scripts.build?.command)
724
+ lines.push(` Build: ${scripts.build.command}`);
725
+ if (scripts.dev?.command)
726
+ lines.push(` Dev: ${scripts.dev.command}`);
727
+ if (scripts.lint?.command)
728
+ lines.push(` Lint: ${scripts.lint.command}`);
729
+ if (scripts.e2e?.command)
730
+ lines.push(` E2E: ${scripts.e2e.command}`);
731
+ }
732
+ // Linting section
733
+ if (analysis.linting) {
734
+ lines.push('\n🔍 Linting:');
735
+ if (analysis.linting.tool) {
736
+ lines.push(` Tool: ${analysis.linting.tool}`);
737
+ }
738
+ if (analysis.linting.plugins && analysis.linting.plugins.length > 0) {
739
+ lines.push(` Plugins: ${analysis.linting.plugins.join(', ')}`);
740
+ }
741
+ }
742
+ // Monorepo section
743
+ if (analysis.monorepo) {
744
+ lines.push('\n📦 Monorepo:');
745
+ if (analysis.monorepo.tool) {
746
+ lines.push(` Tool: ${analysis.monorepo.tool}`);
747
+ }
748
+ if (analysis.monorepo.packages && analysis.monorepo.packages.length > 0) {
749
+ lines.push(' Packages:');
750
+ for (const pkg of analysis.monorepo.packages) {
751
+ lines.push(` - ${pkg}`);
752
+ }
753
+ }
754
+ }
755
+ }
583
756
  /**
584
757
  * Compare two tech stacks and return differences
585
758
  * Handles null previous analysis (first run)
@@ -1 +1 @@
1
- {"version":3,"file":"repo-service.d.ts","sourceRoot":"","sources":["../../src/services/repo-service.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAIlE,MAAM,WAAW,4BAA4B;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,qBAAa,WAAW;IAMV,OAAO,CAAC,OAAO;IAL3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAA2B;IACnE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAyB;IAE/D,OAAO,CAAC,aAAa,CAAgB;gBAEjB,OAAO,EAAE,cAAc;IAS3C;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAQzB;;OAEG;IACG,4BAA4B,CAChC,eAAe,GAAE,MAAoB,GACpC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAmCzB;;;;OAIG;IACH,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAOvD;;OAEG;IACH,cAAc,IAAI,OAAO;IAUzB;;;OAGG;IACG,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC;IAcjD;;OAEG;IACH,kBAAkB,IAAI,MAAM;IAa5B;;;;;;;;OAQG;IACG,qBAAqB,CAAC,OAAO,GAAE,4BAAiC,GAAG,OAAO,CAAC,IAAI,CAAC;IA6FtF;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IAyBhC;;OAEG;IACH,OAAO,CAAC,0BAA0B;IA4GlC;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAoChC;;OAEG;IACH,OAAO,CAAC,6BAA6B;IAwErC;;OAEG;IACH,OAAO,CAAC,gCAAgC;CAyCzC"}
1
+ {"version":3,"file":"repo-service.d.ts","sourceRoot":"","sources":["../../src/services/repo-service.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAIlE,MAAM,WAAW,4BAA4B;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,qBAAa,WAAW;IAMV,OAAO,CAAC,OAAO;IAL3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAA2B;IACnE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAAyB;IAE/D,OAAO,CAAC,aAAa,CAAgB;gBAEjB,OAAO,EAAE,cAAc;IAS3C;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAQzB;;OAEG;IACG,4BAA4B,CAChC,eAAe,GAAE,MAAoB,GACpC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAmCzB;;;;OAIG;IACH,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAOvD;;OAEG;IACH,cAAc,IAAI,OAAO;IAUzB;;;OAGG;IACG,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC;IAcjD;;OAEG;IACH,kBAAkB,IAAI,MAAM;IAa5B;;;;;;;;OAQG;IACG,qBAAqB,CAAC,OAAO,GAAE,4BAAiC,GAAG,OAAO,CAAC,IAAI,CAAC;IA6FtF;;;OAGG;IACH,OAAO,CAAC,wBAAwB;IA0BhC;;OAEG;IACH,OAAO,CAAC,0BAA0B;IA6GlC;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAoChC;;OAEG;IACH,OAAO,CAAC,6BAA6B;IAwErC;;OAEG;IACH,OAAO,CAAC,gCAAgC;CAyCzC"}
@@ -229,7 +229,8 @@ export class RepoService {
229
229
  formatRepoContextSection(enableAgenticCoding) {
230
230
  let content = `## Repository Context
231
231
 
232
- Before starting work, read \`.tiny-brain/analysis.json\` for the detected tech stack, test patterns, and documentation locations.
232
+ Before starting work, read \`AGENTS.md\` for comprehensive project context (tech stack, commands, structure).
233
+ Also read \`.tiny-brain/analysis.json\` for detailed detection data and test patterns.
233
234
 
234
235
  `;
235
236
  if (enableAgenticCoding) {
@@ -292,6 +293,7 @@ Description of changes...
292
293
  | \`fix:\` | Bug fixes | Yes |
293
294
  | \`refactor:\` | Code improvement | Yes |
294
295
  | \`chore:\` | Maintenance (untracked) | No |
296
+ | \`untracked:\` | Work not related to any PRD or Fix | No |
295
297
 
296
298
  **WARNING:** The commit-msg hook will reject commits missing required headers.
297
299
 
@@ -500,8 +500,8 @@ export class AsTool {
500
500
  if (libraryAuth?.token) {
501
501
  formatted += ` 🔑 Connected to Tiny Brain Remote\n`;
502
502
  }
503
- if (libraryAuth?.hasLlmApiKey) {
504
- formatted += ` 🔑 LLM API key configured\n`;
503
+ if (libraryAuth?.hasClaudeCli) {
504
+ formatted += ` 🤖 Claude CLI authenticated\n`;
505
505
  }
506
506
  // Only show pre-flight and agent-first workflow if agents are installed
507
507
  if (hasAgents) {
@@ -19,5 +19,10 @@ export declare class QualityTool {
19
19
  private static handleCompare;
20
20
  private static handlePlan;
21
21
  private static handlePlanDetails;
22
+ private static handleDetectAnalyzers;
23
+ private static handleRunAnalyzers;
24
+ private static handleAssembleRun;
25
+ private static handleImplementPlan;
26
+ private static handleMergeResults;
22
27
  }
23
28
  //# sourceMappingURL=quality.tool.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"quality.tool.d.ts","sourceRoot":"","sources":["../../../src/tools/quality/quality.tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,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;AA4CrE;;GAEG;AACH,qBAAa,WAAW;IACtB,MAAM,CAAC,iBAAiB,IAAI,OAAO;WA2ItB,OAAO,CAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,UAAU,CAAC;mBAsCD,UAAU;mBAqCV,aAAa;mBA2Bb,aAAa;mBA2Eb,aAAa;mBAsEb,UAAU;mBA0CV,iBAAiB;CA+DvC"}
1
+ {"version":3,"file":"quality.tool.d.ts","sourceRoot":"","sources":["../../../src/tools/quality/quality.tool.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,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;AA0DrE;;GAEG;AACH,qBAAa,WAAW;IACtB,MAAM,CAAC,iBAAiB,IAAI,OAAO;WAiLtB,OAAO,CAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,UAAU,CAAC;mBAgDD,UAAU;mBAqCV,aAAa;mBA2Bb,aAAa;mBA2Eb,aAAa;mBAsEb,UAAU;mBA0CV,iBAAiB;mBAgEjB,qBAAqB;mBA6BrB,kBAAkB;mBAgDlB,iBAAiB;mBAmDjB,mBAAmB;IAiDxC,OAAO,CAAC,MAAM,CAAC,kBAAkB;CAoBlC"}
@@ -8,12 +8,16 @@ import { z } from 'zod';
8
8
  import { promises as fs } from 'fs';
9
9
  import path from 'path';
10
10
  import { createSuccessResult, createErrorResult } from '../index.js';
11
- import { QualityService, QualityIssueSchema, SaveQualityRunInputSchema, } from '@magic-ingredients/tiny-brain-core';
11
+ import { QualityService, AnalyzerDetectionService, AnalyzerExecutorService, AssemblyService, mergeResults, QualityIssueSchema, SaveQualityRunInputSchema, generateRunId, runIdToPath, } from '@magic-ingredients/tiny-brain-core';
12
12
  /**
13
13
  * Zod schema for QualityTool arguments
14
14
  */
15
15
  const QualityArgsSchema = z.object({
16
- operation: z.enum(['save', 'history', 'details', 'compare', 'plan', 'plan-details']),
16
+ operation: z.enum([
17
+ 'save', 'history', 'details', 'compare', 'plan', 'plan-details',
18
+ 'detect-analysers', 'run-analysers', 'merge-results', 'assemble-run',
19
+ 'implement-plan',
20
+ ]),
17
21
  // For 'save' operation
18
22
  score: z.number().min(0).max(100).optional(),
19
23
  grade: z.enum(['A', 'B', 'C', 'D', 'F']).optional(),
@@ -38,6 +42,9 @@ const QualityArgsSchema = z.object({
38
42
  targetGrade: z.string().optional(),
39
43
  // For 'plan-details' operation
40
44
  planId: z.string().optional(),
45
+ // For 'merge-results' operation
46
+ analyzerIssues: z.array(QualityIssueSchema).optional(),
47
+ llmIssues: z.array(QualityIssueSchema).optional(),
41
48
  });
42
49
  /**
43
50
  * MCP Tool for quality analysis persistence
@@ -57,6 +64,11 @@ Persists and retrieves quality analysis results. Analysis is performed by agents
57
64
  • compare: Compare two runs to see new, resolved, and persistent issues
58
65
  • plan: Generate a Quality Improvement Plan from a saved run
59
66
  • plan-details: Retrieve a saved Quality Improvement Plan by ID
67
+ • detect-analysers: Scan repo for configured static analyzers
68
+ • run-analysers: Execute detected analyzers and return normalized issues
69
+ • merge-results: Merge analyzer + LLM issues with deduplication
70
+ • assemble-run: Read all intermediate files, merge, score, and save final report
71
+ • implement-plan: Generate fix documents from a saved Quality Improvement Plan
60
72
 
61
73
  💡 WORKFLOW:
62
74
  1. Quality-coordinator agent performs analysis
@@ -69,7 +81,11 @@ Persists and retrieves quality analysis results. Analysis is performed by agents
69
81
  properties: {
70
82
  operation: {
71
83
  type: 'string',
72
- enum: ['save', 'history', 'details', 'compare', 'plan', 'plan-details'],
84
+ enum: [
85
+ 'save', 'history', 'details', 'compare', 'plan', 'plan-details',
86
+ 'detect-analysers', 'run-analysers', 'merge-results', 'assemble-run',
87
+ 'implement-plan',
88
+ ],
73
89
  description: 'The quality operation to perform',
74
90
  },
75
91
  // For 'save' operation
@@ -151,7 +167,7 @@ Persists and retrieves quality analysis results. Analysis is performed by agents
151
167
  // For 'details' operation
152
168
  runId: {
153
169
  type: 'string',
154
- description: 'Run ID to get details for (required for details)',
170
+ description: 'Run ID (required for details, assemble-run; optional for run-analysers to reuse Phase 1 directory)',
155
171
  },
156
172
  // For 'compare' operation
157
173
  baseRunId: {
@@ -176,6 +192,35 @@ Persists and retrieves quality analysis results. Analysis is performed by agents
176
192
  type: 'string',
177
193
  description: 'Plan ID to retrieve details for (required for plan-details)',
178
194
  },
195
+ // For 'merge-results' operation
196
+ analyzerIssues: {
197
+ type: 'array',
198
+ description: 'Array of analyzer issues (required for merge-results)',
199
+ items: {
200
+ type: 'object',
201
+ properties: {
202
+ category: { type: 'string' },
203
+ severity: { type: 'string' },
204
+ file: { type: 'string' },
205
+ message: { type: 'string' },
206
+ },
207
+ required: ['category', 'severity', 'file', 'message'],
208
+ },
209
+ },
210
+ llmIssues: {
211
+ type: 'array',
212
+ description: 'Array of LLM investigation issues (required for merge-results)',
213
+ items: {
214
+ type: 'object',
215
+ properties: {
216
+ category: { type: 'string' },
217
+ severity: { type: 'string' },
218
+ file: { type: 'string' },
219
+ message: { type: 'string' },
220
+ },
221
+ required: ['category', 'severity', 'file', 'message'],
222
+ },
223
+ },
179
224
  },
180
225
  required: ['operation'],
181
226
  },
@@ -202,6 +247,16 @@ Persists and retrieves quality analysis results. Analysis is performed by agents
202
247
  return await QualityTool.handlePlan(validatedArgs, service);
203
248
  case 'plan-details':
204
249
  return await QualityTool.handlePlanDetails(validatedArgs, context.repositoryRoot);
250
+ case 'detect-analysers':
251
+ return await QualityTool.handleDetectAnalyzers(context.repositoryRoot);
252
+ case 'run-analysers':
253
+ return await QualityTool.handleRunAnalyzers(validatedArgs, context.repositoryRoot);
254
+ case 'merge-results':
255
+ return QualityTool.handleMergeResults(validatedArgs);
256
+ case 'assemble-run':
257
+ return await QualityTool.handleAssembleRun(validatedArgs, context.repositoryRoot);
258
+ case 'implement-plan':
259
+ return await QualityTool.handleImplementPlan(validatedArgs, service);
205
260
  default:
206
261
  return createErrorResult(`Unknown operation: ${validatedArgs.operation}`);
207
262
  }
@@ -458,4 +513,150 @@ Persists and retrieves quality analysis results. Analysis is performed by agents
458
513
  return createErrorResult(`Improvement plan not found: ${args.planId}`);
459
514
  }
460
515
  }
516
+ static async handleDetectAnalyzers(repositoryRoot) {
517
+ const service = new AnalyzerDetectionService(repositoryRoot);
518
+ const analyzers = await service.detectAnalyzers();
519
+ if (analyzers.length === 0) {
520
+ return createSuccessResult('No static analyzers detected in this repository.\n\nSupported analyzers: ESLint, TypeScript, npm audit, RuboCop, ruff.');
521
+ }
522
+ const lines = [
523
+ `🔍 Detected ${analyzers.length} analyzer(s):`,
524
+ '',
525
+ ];
526
+ for (const analyzer of analyzers) {
527
+ lines.push(`- **${analyzer.name}** (${analyzer.analyzerId})`);
528
+ lines.push(` Configs: ${analyzer.configPaths.join(', ')}`);
529
+ lines.push(` Categories: ${analyzer.categories.join(', ')}`);
530
+ }
531
+ lines.push('');
532
+ lines.push(JSON.stringify(analyzers, null, 2));
533
+ return createSuccessResult(lines.join('\n'));
534
+ }
535
+ static async handleRunAnalyzers(args, repositoryRoot) {
536
+ const runId = args.runId ?? generateRunId();
537
+ const runDir = path.join(repositoryRoot, 'docs', 'quality', 'runs', runIdToPath(runId));
538
+ const outputPath = path.join(runDir, 'analysis.json');
539
+ const outputDir = path.join(runDir, 'analysers');
540
+ const detectionService = new AnalyzerDetectionService(repositoryRoot);
541
+ const analyzers = await detectionService.detectAnalyzers();
542
+ const emptyResults = {
543
+ issues: [],
544
+ executions: [],
545
+ totalDurationMs: 0,
546
+ summary: { total: 0, succeeded: 0, failed: 0, timedOut: 0, skipped: 0 },
547
+ };
548
+ if (analyzers.length === 0) {
549
+ await fs.mkdir(runDir, { recursive: true });
550
+ await fs.writeFile(outputPath, JSON.stringify(emptyResults, null, 2), 'utf-8');
551
+ return createSuccessResult(`🔧 No analyzers detected. Empty results written to file.\n Run ID: ${runId}\n Output: ${outputPath}`);
552
+ }
553
+ const executorService = new AnalyzerExecutorService(repositoryRoot);
554
+ const results = await executorService.executeAnalyzers(analyzers, outputDir);
555
+ // Write merged/normalized result to the auto-generated run directory
556
+ await fs.mkdir(runDir, { recursive: true });
557
+ await fs.writeFile(outputPath, JSON.stringify(results, null, 2), 'utf-8');
558
+ const lines = [
559
+ `🔧 Executed ${results.summary.total} analyzer(s):`,
560
+ ` Succeeded: ${results.summary.succeeded}`,
561
+ ` Failed: ${results.summary.failed}`,
562
+ ` Total issues: ${results.issues.length}`,
563
+ ` Duration: ${results.totalDurationMs}ms`,
564
+ ` Run ID: ${runId}`,
565
+ ` Output: ${outputPath}`,
566
+ ` Raw files: ${outputDir}/`,
567
+ ];
568
+ return createSuccessResult(lines.join('\n'));
569
+ }
570
+ static async handleAssembleRun(args, repositoryRoot) {
571
+ if (!args.runId) {
572
+ return createErrorResult('runId is required for assemble-run operation (YYYY-MM-DD date prefix)');
573
+ }
574
+ const assemblyService = new AssemblyService(repositoryRoot);
575
+ const result = await assemblyService.assembleRun(args.runId, args.baseRunId);
576
+ const lines = [
577
+ '✅ Quality run assembled successfully!',
578
+ '',
579
+ `📊 Run ID: ${result.runId}`,
580
+ `📈 Score: ${result.score}/100 (Grade: ${result.grade})`,
581
+ `🔍 Issues: ${result.issueCount}`,
582
+ `📁 File: ${result.filePath}`,
583
+ ];
584
+ if (result.planId) {
585
+ lines.push(`📋 Plan: ${result.planId}`);
586
+ lines.push(`📁 Plan File: ${result.planFilePath}`);
587
+ }
588
+ if (result.incremental) {
589
+ lines.push('');
590
+ lines.push('### Incremental Analysis');
591
+ lines.push(` Base run: ${result.baseRunId}`);
592
+ lines.push(` Files analyzed: ${result.filesAnalyzed}`);
593
+ lines.push(` Files carried forward: ${result.filesCarriedForward}`);
594
+ }
595
+ lines.push('');
596
+ lines.push('### Source Breakdown');
597
+ lines.push(` Analyzer: ${result.sourceBreakdown.analyzer}`);
598
+ lines.push(` Specialist: ${result.sourceBreakdown.llm}`);
599
+ lines.push(` Total: ${result.sourceBreakdown.total}`);
600
+ const categoryEntries = Object.entries(result.categoryBreakdown);
601
+ if (categoryEntries.length > 0) {
602
+ lines.push('');
603
+ lines.push('### Category Breakdown');
604
+ for (const [category, count] of categoryEntries) {
605
+ lines.push(` ${category}: ${count}`);
606
+ }
607
+ }
608
+ return createSuccessResult(lines.join('\n'));
609
+ }
610
+ static async handleImplementPlan(args, service) {
611
+ if (!args.planId) {
612
+ return createErrorResult('planId is required for implement-plan operation');
613
+ }
614
+ const plan = await service.loadImprovementPlan(args.planId);
615
+ if (!plan) {
616
+ return createErrorResult(`Improvement plan not found: ${args.planId}`);
617
+ }
618
+ const run = await service.getRunDetails(plan.sourceRunId);
619
+ if (!run) {
620
+ return createErrorResult(`Source quality run not found: ${plan.sourceRunId}`);
621
+ }
622
+ const fixIds = await service.generateFixesFromPlan(plan, run.issues);
623
+ if (fixIds.length === 0) {
624
+ return createSuccessResult('No initiatives found in plan. No fix documents created.');
625
+ }
626
+ const lines = [
627
+ `Created ${fixIds.length} fix document(s) from Quality Improvement Plan ${args.planId}`,
628
+ '',
629
+ '## Fixes Created',
630
+ '',
631
+ '| Fix ID | File |',
632
+ '|--------|------|',
633
+ ];
634
+ for (const fixId of fixIds) {
635
+ lines.push(`| ${fixId} | .tiny-brain/fixes/${fixId}.md |`);
636
+ }
637
+ lines.push('');
638
+ lines.push('## Next Steps');
639
+ lines.push('');
640
+ for (const fixId of fixIds) {
641
+ lines.push(`1. Run \`npx tiny-brain sync-file .tiny-brain/fixes/${fixId}.md\``);
642
+ }
643
+ lines.push('2. Start working on fixes using the /fix workflow');
644
+ lines.push('3. Track progress in the dashboard');
645
+ return createSuccessResult(lines.join('\n'));
646
+ }
647
+ static handleMergeResults(args) {
648
+ const analyzerIssues = args.analyzerIssues ?? [];
649
+ const llmIssues = args.llmIssues ?? [];
650
+ const result = mergeResults(analyzerIssues, llmIssues);
651
+ const lines = [
652
+ `🔀 Merged results:`,
653
+ ` Analyzer issues: ${result.sourceBreakdown.analyzer}`,
654
+ ` LLM issues: ${result.sourceBreakdown.llm}`,
655
+ ` Total (deduplicated): ${result.sourceBreakdown.total}`,
656
+ ` Duplicates removed: ${result.duplicatesRemoved}`,
657
+ '',
658
+ JSON.stringify(result, null, 2),
659
+ ];
660
+ return createSuccessResult(lines.join('\n'));
661
+ }
461
662
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magic-ingredients/tiny-brain-local",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "MCP server for Tiny Brain AI assistant",
5
5
  "type": "module",
6
6
  "main": "./dist/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.19.0",
34
+ "@magic-ingredients/tiny-brain-core": "^0.20.0",
35
35
  "@magic-ingredients/tiny-brain-dashboard": "file:../tiny-brain-dashboard",
36
36
  "@modelcontextprotocol/sdk": "^1.0.6",
37
37
  "chalk": "^5.3.0",