@magic-ingredients/tiny-brain-local 0.18.1 → 0.19.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.
@@ -58,6 +58,11 @@ export declare class RepoService {
58
58
  * - Operational Tracking Directory (when SDD enabled)
59
59
  */
60
60
  writeWorkflowSections(options?: WriteWorkflowSectionsOptions): Promise<void>;
61
+ /**
62
+ * Format the Repository Context section
63
+ * When agentic coding is enabled, includes a mandatory agent delegation section
64
+ */
65
+ private formatRepoContextSection;
61
66
  /**
62
67
  * Format the Commit Message Format section
63
68
  */
@@ -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;IAwFtF;;OAEG;IACH,OAAO,CAAC,0BAA0B;IA4GlC;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAoChC;;OAEG;IACH,OAAO,CAAC,6BAA6B;IAwErC;;OAEG;IACH,OAAO,CAAC,gCAAgC;CAqCzC"}
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"}
@@ -147,12 +147,15 @@ export class RepoService {
147
147
  // Check config flags
148
148
  const enableSDD = await this.configService.isSDDEnabled();
149
149
  const enableTDD = await this.configService.isTDDEnabled();
150
+ const enableAgentic = await this.configService.isAgenticCodingEnabled();
150
151
  // If nothing is enabled, don't write anything
151
152
  if (!enableSDD && !enableTDD) {
152
153
  return;
153
154
  }
154
155
  // Build the workflow sections content
155
156
  let workflowContent = '';
157
+ // Repository Context (always when workflow sections are enabled)
158
+ workflowContent += this.formatRepoContextSection(enableAgentic);
156
159
  // Commit Message Format (when SDD enabled)
157
160
  if (enableSDD) {
158
161
  workflowContent += this.formatCommitMessageSection();
@@ -169,8 +172,9 @@ export class RepoService {
169
172
  if (enableSDD) {
170
173
  workflowContent += this.formatOperationalTrackingSection();
171
174
  }
172
- // Build the complete tiny-brain block
173
- const repoBlock = `${RepoService.REPO_BLOCK_START}\n\n${workflowContent}${RepoService.REPO_BLOCK_END}\n`;
175
+ // Build the complete tiny-brain block with version frontmatter
176
+ const version = this.getAnalysisVersion();
177
+ const repoBlock = `${RepoService.REPO_BLOCK_START}\n---\nversion: ${version}\n---\n\n${workflowContent}${RepoService.REPO_BLOCK_END}\n`;
174
178
  // Get full path
175
179
  const repoRoot = getRepoRoot();
176
180
  const fullPath = path.join(repoRoot, contextPath);
@@ -218,6 +222,32 @@ export class RepoService {
218
222
  await fs.writeFile(fullPath, existingContent, 'utf-8');
219
223
  this.context.logger.info(`Updated workflow sections in ${contextPath}`);
220
224
  }
225
+ /**
226
+ * Format the Repository Context section
227
+ * When agentic coding is enabled, includes a mandatory agent delegation section
228
+ */
229
+ formatRepoContextSection(enableAgenticCoding) {
230
+ let content = `## Repository Context
231
+
232
+ Before starting work, read \`.tiny-brain/analysis.json\` for the detected tech stack, test patterns, and documentation locations.
233
+
234
+ `;
235
+ if (enableAgenticCoding) {
236
+ content += `## Agent Delegation (MANDATORY)
237
+
238
+ IMPORTANT: You MUST delegate implementation work to specialized agents using the Task tool. Do NOT write production code or tests directly.
239
+
240
+ When the user asks you to implement a feature, fix a bug, or refactor code:
241
+ 1. Use the Task tool with \`subagent_type="tiny-brain:developer"\`
242
+ 2. Provide the full task context in the prompt
243
+ 3. Let the agent handle the TDD workflow (red/green/refactor)
244
+
245
+ Only skip delegation for trivial tasks (single-line fixes, config changes).
246
+
247
+ `;
248
+ }
249
+ return content;
250
+ }
221
251
  /**
222
252
  * Format the Commit Message Format section
223
253
  */
@@ -450,6 +480,10 @@ The \`.tiny-brain/\` directory stores operational tracking data separate from do
450
480
 
451
481
  \`\`\`
452
482
  .tiny-brain/
483
+ ├── analysis.json # Detected tech stack and repo analysis
484
+ ├── tech/ # Technology-specific context files
485
+ │ ├── config.json # Tech context mode configuration
486
+ │ └── {name}.md # One file per detected technology
453
487
  ├── progress/ # PRD progress tracking
454
488
  │ └── {prd-id}.json # One file per PRD
455
489
  └── fixes/ # Fix progress tracking
@@ -37,6 +37,10 @@ export declare class AsTool {
37
37
  * Check if agents are installed by checking the platform-specific agents directory
38
38
  */
39
39
  private static checkHasAgents;
40
+ /**
41
+ * Resolve feature flags from ConfigService
42
+ */
43
+ private static resolveFeatureFlags;
40
44
  /**
41
45
  * Build persona response data structure
42
46
  */
@@ -1 +1 @@
1
- {"version":3,"file":"as.tool.d.ts","sourceRoot":"","sources":["../../../src/tools/persona/as.tool.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACrF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAWrE,OAAO,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAqCrE;;;;;;;;;GASG;AACH,qBAAa,MAAM;IACjB,MAAM,CAAC,iBAAiB,IAAI,OAAO;IAqDnC;;OAEG;WACU,OAAO,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC;IA4BvF;;OAEG;mBACkB,wBAAwB;IAmE7C;;OAEG;mBACkB,mBAAmB;IA2ExC;;OAEG;mBACkB,oBAAoB;IAmGzC;;OAEG;mBACkB,eAAe;IAsBpC;;OAEG;mBACkB,cAAc;IAWnC;;OAEG;mBACkB,aAAa;IA6DlC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;IA8H7B;;OAEG;mBACkB,mBAAmB;mBAyDnB,qBAAqB;CAsC3C"}
1
+ {"version":3,"file":"as.tool.d.ts","sourceRoot":"","sources":["../../../src/tools/persona/as.tool.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACrF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAYrE,OAAO,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAyCrE;;;;;;;;;GASG;AACH,qBAAa,MAAM;IACjB,MAAM,CAAC,iBAAiB,IAAI,OAAO;IAqDnC;;OAEG;WACU,OAAO,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC;IA4BvF;;OAEG;mBACkB,wBAAwB;IAsE7C;;OAEG;mBACkB,mBAAmB;IA8ExC;;OAEG;mBACkB,oBAAoB;IAsGzC;;OAEG;mBACkB,eAAe;IAsBpC;;OAEG;mBACkB,cAAc;IAWnC;;OAEG;mBACkB,mBAAmB;IAexC;;OAEG;mBACkB,aAAa;IA6DlC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,cAAc;IA8H7B;;OAEG;mBACkB,mBAAmB;mBA4DnB,qBAAqB;CAsC3C"}
@@ -1,12 +1,9 @@
1
1
  import { z } from 'zod';
2
2
  import { createErrorResult } from '../index.js';
3
- import { createDefaultProfile, PlanningService, RulesService, LibraryClient, parsePersonaMarkdown } from '@magic-ingredients/tiny-brain-core';
3
+ import { createDefaultProfile, PlanningService, RulesService, LibraryClient, ConfigService, parsePersonaMarkdown } from '@magic-ingredients/tiny-brain-core';
4
4
  import { PersonaService } from '../../services/persona-service.js';
5
5
  import { RepoService } from '../../services/repo-service.js';
6
6
  import { AgentService } from '../../services/agent-service.js';
7
- import { existsSync } from 'fs';
8
- import { join } from 'path';
9
- import { getRepoRoot } from '../../utils/repo-utils.js';
10
7
  const AsArgsSchema = z.object({
11
8
  personaName: z.string().optional(),
12
9
  confirmCreate: z.boolean().optional(),
@@ -147,11 +144,14 @@ export class AsTool {
147
144
  const formatter = agentService.getDefaultFormatter();
148
145
  const contextFilePath = formatter.getRepoContextFilePath();
149
146
  const response = await AsTool.buildResponse(context, parsedPersona, contextFilePath);
150
- const hasAgents = await AsTool.checkHasAgents(context, contextFilePath);
147
+ const [hasAgents, featureFlags] = await Promise.all([
148
+ AsTool.checkHasAgents(context, contextFilePath),
149
+ AsTool.resolveFeatureFlags(context),
150
+ ]);
151
151
  return {
152
152
  content: [{
153
153
  type: 'text',
154
- text: AsTool.formatResponse(response, contextFilePath, hasAgents),
154
+ text: AsTool.formatResponse(response, contextFilePath, hasAgents, featureFlags, context.libraryAuth),
155
155
  }],
156
156
  isError: false,
157
157
  };
@@ -201,11 +201,14 @@ export class AsTool {
201
201
  // Activate persona and return response with updated context
202
202
  const updatedContext = await AsTool.activatePersona(personaName, profile, context);
203
203
  const response = await AsTool.buildResponse(updatedContext, parsedPersona, contextFilePath);
204
- const hasAgents = await AsTool.checkHasAgents(updatedContext, contextFilePath);
204
+ const [hasAgents, featureFlags] = await Promise.all([
205
+ AsTool.checkHasAgents(updatedContext, contextFilePath),
206
+ AsTool.resolveFeatureFlags(updatedContext),
207
+ ]);
205
208
  return {
206
209
  content: [{
207
210
  type: 'text',
208
- text: AsTool.formatResponse(response, contextFilePath, hasAgents),
211
+ text: AsTool.formatResponse(response, contextFilePath, hasAgents, featureFlags, updatedContext.libraryAuth),
209
212
  }],
210
213
  isError: false,
211
214
  };
@@ -280,11 +283,14 @@ export class AsTool {
280
283
  const contextFilePath = formatter?.getRepoContextFilePath();
281
284
  // Step 8: Build and return response
282
285
  const response = await AsTool.buildResponse(updatedContext, parsedPersona, contextFilePath);
283
- const hasAgents = await AsTool.checkHasAgents(updatedContext, contextFilePath);
286
+ const [hasAgents, featureFlags] = await Promise.all([
287
+ AsTool.checkHasAgents(updatedContext, contextFilePath),
288
+ AsTool.resolveFeatureFlags(updatedContext),
289
+ ]);
284
290
  return {
285
291
  content: [{
286
292
  type: 'text',
287
- text: AsTool.formatResponse(response, contextFilePath, hasAgents) + needsInitMessage,
293
+ text: AsTool.formatResponse(response, contextFilePath, hasAgents, featureFlags, updatedContext.libraryAuth) + needsInitMessage,
288
294
  }],
289
295
  isError: false,
290
296
  };
@@ -327,6 +333,24 @@ export class AsTool {
327
333
  return false;
328
334
  }
329
335
  }
336
+ /**
337
+ * Resolve feature flags from ConfigService
338
+ */
339
+ static async resolveFeatureFlags(context) {
340
+ try {
341
+ const configService = new ConfigService(context);
342
+ const [sddEnabled, tddEnabled, adrEnabled, agenticCodingEnabled] = await Promise.all([
343
+ configService.isSDDEnabled(),
344
+ configService.isTDDEnabled(),
345
+ configService.isADREnabled(),
346
+ configService.isAgenticCodingEnabled(),
347
+ ]);
348
+ return { sddEnabled, tddEnabled, adrEnabled, agenticCodingEnabled };
349
+ }
350
+ catch {
351
+ return { sddEnabled: false, tddEnabled: false, adrEnabled: false, agenticCodingEnabled: false };
352
+ }
353
+ }
330
354
  /**
331
355
  * Build persona response data structure
332
356
  */
@@ -385,7 +409,7 @@ export class AsTool {
385
409
  /**
386
410
  * Format the persona response as human-readable markdown with enforced workflow
387
411
  */
388
- static formatResponse(response, contextFilePath, hasAgents) {
412
+ static formatResponse(response, contextFilePath, hasAgents, featureFlags, libraryAuth) {
389
413
  let formatted = `# THIS IS YOUR CONTEXT FOR THIS SESSION - YOU MUST USE IT\n\n`;
390
414
  formatted += `IMPORTANT: USER RULES and USER DETAILS ALWAYS take precedence over SYSTEM RULES and SYSTEM DETAILS. When there are conflicts, follow the user's preferences.\n\n`;
391
415
  formatted += `## PERSONA: ${response.PERSONA.name}\n\n`;
@@ -461,33 +485,32 @@ export class AsTool {
461
485
  formatted += `**EXCEPTION**: Only skip agents if you can explicitly justify why none apply\n\n`;
462
486
  formatted += `**MANDATORY CONFIRMATION:** When ready to proceed, you MUST respond with exactly this format (copy it exactly, do not modify):\n\n`;
463
487
  formatted += `\`\`\`\n`;
488
+ formatted += ` 🧠 tiny brain\n\n`;
464
489
  formatted += ` ✅ Received context for persona\n`;
465
490
  formatted += ` ✅ Re-read ${contextFilePath}\n`;
466
- // Check for PRD and ADR directories and add checklist items (indented by 2 spaces)
467
- try {
468
- const repoRoot = getRepoRoot();
469
- const hasPRD = existsSync(join(repoRoot, 'docs', 'prd'));
470
- const hasADR = existsSync(join(repoRoot, 'docs', 'adr'));
471
- if (hasPRD) {
472
- formatted += ` ✅ Enabled Spec Driven Design workflow with Test Driven Development\n`;
473
- }
474
- if (hasADR) {
475
- formatted += ` ✅ Enabled Architecture Decision Records\n`;
476
- }
491
+ if (featureFlags?.sddEnabled) {
492
+ formatted += ` ✅ Enabled Spec Driven Design workflow with Test Driven Development\n`;
493
+ }
494
+ if (featureFlags?.adrEnabled) {
495
+ formatted += ` ✅ Enabled Architecture Decision Records\n`;
496
+ }
497
+ if (featureFlags?.agenticCodingEnabled) {
498
+ formatted += ` ✅ Enabled agentic coding\n`;
477
499
  }
478
- catch {
479
- // Not in a repo, skip PRD/ADR checklist items
500
+ if (libraryAuth?.token) {
501
+ formatted += ` 🔑 Connected to Tiny Brain Remote\n`;
502
+ }
503
+ if (libraryAuth?.hasLlmApiKey) {
504
+ formatted += ` 🔑 LLM API key configured\n`;
480
505
  }
481
506
  // Only show pre-flight and agent-first workflow if agents are installed
482
507
  if (hasAgents) {
483
508
  formatted += ` ✅ Completed pre-flight checklist\n`;
484
509
  formatted += ` ✅ Ready for agent-first workflow\n`;
485
510
  }
486
- formatted += ` \n\n`;
487
- formatted += ` I've switched to your **${response.PERSONA.name}** persona.\n`;
488
- formatted += ` \n`;
489
- formatted += ` 🧠 Dashboard available at: [http://localhost:8765](http://localhost:8765)\n`;
490
511
  formatted += `\`\`\`\n\n`;
512
+ formatted += `I've switched to your **${response.PERSONA.name}** persona.\n\n`;
513
+ formatted += `🧠 Dashboard available at: [http://localhost:8765](http://localhost:8765)\n`;
491
514
  }
492
515
  else {
493
516
  // Add persona switch confirmation message when no context file
@@ -524,11 +547,14 @@ export class AsTool {
524
547
  context.logger.info(`Renamed persona '${oldName}' to '${newName}'`);
525
548
  // Return response showing the renamed persona is now active with updated context
526
549
  const response = await AsTool.buildResponse(updatedContext, parsedPersona, contextFilePath);
527
- const hasAgents = await AsTool.checkHasAgents(updatedContext, contextFilePath);
550
+ const [hasAgents, featureFlags] = await Promise.all([
551
+ AsTool.checkHasAgents(updatedContext, contextFilePath),
552
+ AsTool.resolveFeatureFlags(updatedContext),
553
+ ]);
528
554
  return {
529
555
  content: [{
530
556
  type: 'text',
531
- text: AsTool.formatResponse(response, contextFilePath, hasAgents),
557
+ text: AsTool.formatResponse(response, contextFilePath, hasAgents, featureFlags, updatedContext.libraryAuth),
532
558
  }],
533
559
  isError: false,
534
560
  };
@@ -16,5 +16,8 @@ export declare class QualityTool {
16
16
  private static handleSave;
17
17
  private static handleHistory;
18
18
  private static handleDetails;
19
+ private static handleCompare;
20
+ private static handlePlan;
21
+ private static handlePlanDetails;
19
22
  }
20
23
  //# 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;AAGH,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;AAiCrE;;GAEG;AACH,qBAAa,WAAW;IACtB,MAAM,CAAC,iBAAiB,IAAI,OAAO;WA4EtB,OAAO,CAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,UAAU,CAAC;mBAgCD,UAAU;mBAqCV,aAAa;mBA2Bb,aAAa;CA0EnC"}
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"}
@@ -5,13 +5,15 @@
5
5
  * Analysis is performed by agents; this tool only handles persistence.
6
6
  */
7
7
  import { z } from 'zod';
8
+ import { promises as fs } from 'fs';
9
+ import path from 'path';
8
10
  import { createSuccessResult, createErrorResult } from '../index.js';
9
11
  import { QualityService, QualityIssueSchema, SaveQualityRunInputSchema, } from '@magic-ingredients/tiny-brain-core';
10
12
  /**
11
13
  * Zod schema for QualityTool arguments
12
14
  */
13
15
  const QualityArgsSchema = z.object({
14
- operation: z.enum(['save', 'history', 'details']),
16
+ operation: z.enum(['save', 'history', 'details', 'compare', 'plan', 'plan-details']),
15
17
  // For 'save' operation
16
18
  score: z.number().min(0).max(100).optional(),
17
19
  grade: z.enum(['A', 'B', 'C', 'D', 'F']).optional(),
@@ -28,6 +30,14 @@ const QualityArgsSchema = z.object({
28
30
  limit: z.number().min(1).optional(),
29
31
  // For 'details' operation
30
32
  runId: z.string().optional(),
33
+ // For 'compare' operation
34
+ baseRunId: z.string().optional(),
35
+ targetRunId: z.string().optional(),
36
+ // For 'plan' operation
37
+ sourceRunId: z.string().optional(),
38
+ targetGrade: z.string().optional(),
39
+ // For 'plan-details' operation
40
+ planId: z.string().optional(),
31
41
  });
32
42
  /**
33
43
  * MCP Tool for quality analysis persistence
@@ -44,18 +54,22 @@ Persists and retrieves quality analysis results. Analysis is performed by agents
44
54
  • save: Persist a quality run to docs/quality/runs/
45
55
  • history: List previous quality runs with summary data
46
56
  • details: Get full details for a specific run by ID
57
+ • compare: Compare two runs to see new, resolved, and persistent issues
58
+ • plan: Generate a Quality Improvement Plan from a saved run
59
+ • plan-details: Retrieve a saved Quality Improvement Plan by ID
47
60
 
48
61
  💡 WORKFLOW:
49
62
  1. Quality-coordinator agent performs analysis
50
63
  2. Agent calls quality save with score, grade, issues, recommendations
51
64
  3. Results stored as markdown in docs/quality/runs/YYYY-MM-DD-quality.md
52
- 4. Use history/details to retrieve past runs`,
65
+ 4. Use history/details to retrieve past runs
66
+ 5. Use plan to generate an improvement roadmap from a run`,
53
67
  inputSchema: {
54
68
  type: 'object',
55
69
  properties: {
56
70
  operation: {
57
71
  type: 'string',
58
- enum: ['save', 'history', 'details'],
72
+ enum: ['save', 'history', 'details', 'compare', 'plan', 'plan-details'],
59
73
  description: 'The quality operation to perform',
60
74
  },
61
75
  // For 'save' operation
@@ -80,6 +94,42 @@ Persists and retrieves quality analysis results. Analysis is performed by agents
80
94
  line: { type: 'number' },
81
95
  message: { type: 'string' },
82
96
  suggestion: { type: 'string' },
97
+ effort: {
98
+ type: 'string',
99
+ enum: ['trivial', 'small', 'medium', 'large', 'epic'],
100
+ description: 'Estimated effort to remediate',
101
+ },
102
+ impact: {
103
+ type: 'string',
104
+ enum: ['critical', 'high', 'medium', 'low'],
105
+ description: 'Impact level if this issue is not addressed',
106
+ },
107
+ theme: {
108
+ type: 'string',
109
+ description: 'Thematic grouping tag (e.g., "type-safety", "auth-hardening")',
110
+ },
111
+ evidence: {
112
+ type: 'string',
113
+ description: 'Code snippet or evidence showing the problem',
114
+ },
115
+ relatedIssues: {
116
+ type: 'array',
117
+ items: { type: 'number' },
118
+ description: 'Indices of related issues in the same run',
119
+ },
120
+ references: {
121
+ type: 'array',
122
+ items: { type: 'string' },
123
+ description: 'External references such as CWE or OWASP identifiers',
124
+ },
125
+ scoreImpact: {
126
+ type: 'number',
127
+ description: 'Points that would be gained if this issue were fixed',
128
+ },
129
+ effortHours: {
130
+ type: 'number',
131
+ description: 'Estimated hours to remediate',
132
+ },
83
133
  },
84
134
  required: ['category', 'severity', 'file', 'message'],
85
135
  },
@@ -103,6 +153,29 @@ Persists and retrieves quality analysis results. Analysis is performed by agents
103
153
  type: 'string',
104
154
  description: 'Run ID to get details for (required for details)',
105
155
  },
156
+ // For 'compare' operation
157
+ baseRunId: {
158
+ type: 'string',
159
+ description: 'Base run ID for comparison (required for compare)',
160
+ },
161
+ targetRunId: {
162
+ type: 'string',
163
+ description: 'Target run ID for comparison (required for compare)',
164
+ },
165
+ // For 'plan' operation
166
+ sourceRunId: {
167
+ type: 'string',
168
+ description: 'Source run ID to generate improvement plan from (required for plan)',
169
+ },
170
+ targetGrade: {
171
+ type: 'string',
172
+ description: 'Target grade to aim for in the improvement plan (optional for plan)',
173
+ },
174
+ // For 'plan-details' operation
175
+ planId: {
176
+ type: 'string',
177
+ description: 'Plan ID to retrieve details for (required for plan-details)',
178
+ },
106
179
  },
107
180
  required: ['operation'],
108
181
  },
@@ -123,6 +196,12 @@ Persists and retrieves quality analysis results. Analysis is performed by agents
123
196
  return await QualityTool.handleHistory(validatedArgs, service);
124
197
  case 'details':
125
198
  return await QualityTool.handleDetails(validatedArgs, service);
199
+ case 'compare':
200
+ return await QualityTool.handleCompare(validatedArgs, service);
201
+ case 'plan':
202
+ return await QualityTool.handlePlan(validatedArgs, service);
203
+ case 'plan-details':
204
+ return await QualityTool.handlePlanDetails(validatedArgs, context.repositoryRoot);
126
205
  default:
127
206
  return createErrorResult(`Unknown operation: ${validatedArgs.operation}`);
128
207
  }
@@ -243,4 +322,140 @@ Persists and retrieves quality analysis results. Analysis is performed by agents
243
322
  }
244
323
  return createSuccessResult(lines.join('\n'));
245
324
  }
325
+ static async handleCompare(args, service) {
326
+ if (!args.baseRunId) {
327
+ return createErrorResult('baseRunId is required for compare operation');
328
+ }
329
+ if (!args.targetRunId) {
330
+ return createErrorResult('targetRunId is required for compare operation');
331
+ }
332
+ const comparison = await service.compareRuns(args.baseRunId, args.targetRunId);
333
+ if (!comparison) {
334
+ return createErrorResult(`One or both runs not found: ${args.baseRunId}, ${args.targetRunId}`);
335
+ }
336
+ const deltaSign = comparison.scoreDelta >= 0 ? '+' : '';
337
+ const lines = [
338
+ `📊 Run Comparison: ${comparison.baseRunId} → ${comparison.targetRunId}`,
339
+ '',
340
+ '## Summary',
341
+ `**Score Delta:** ${deltaSign}${comparison.scoreDelta}`,
342
+ `**Grade Change:** ${comparison.gradeChange.from} → ${comparison.gradeChange.to}`,
343
+ `**New Issues:** ${comparison.newIssues.length}`,
344
+ `**Resolved Issues:** ${comparison.resolvedIssues.length}`,
345
+ `**Persistent Issues:** ${comparison.persistentIssues.length}`,
346
+ '',
347
+ ];
348
+ // Category changes
349
+ const categoryEntries = Object.entries(comparison.categoryChanges);
350
+ if (categoryEntries.length > 0) {
351
+ lines.push('## Category Changes');
352
+ lines.push('');
353
+ lines.push('| Category | Count Delta | Deduction Delta |');
354
+ lines.push('|----------|-------------|-----------------|');
355
+ for (const [category, changes] of categoryEntries) {
356
+ const countSign = changes.countDelta >= 0 ? '+' : '';
357
+ const deductionSign = changes.deductionDelta >= 0 ? '+' : '';
358
+ lines.push(`| ${category} | ${countSign}${changes.countDelta} | ${deductionSign}${changes.deductionDelta.toFixed(1)} |`);
359
+ }
360
+ lines.push('');
361
+ }
362
+ // New issues
363
+ if (comparison.newIssues.length > 0) {
364
+ lines.push('## New Issues');
365
+ for (const issue of comparison.newIssues) {
366
+ lines.push(`- **[${issue.severity}]** ${issue.message} (${issue.file})`);
367
+ }
368
+ lines.push('');
369
+ }
370
+ // Resolved issues
371
+ if (comparison.resolvedIssues.length > 0) {
372
+ lines.push('## Resolved Issues');
373
+ for (const issue of comparison.resolvedIssues) {
374
+ lines.push(`- **[${issue.severity}]** ${issue.message} (${issue.file})`);
375
+ }
376
+ lines.push('');
377
+ }
378
+ return createSuccessResult(lines.join('\n'));
379
+ }
380
+ static async handlePlan(args, service) {
381
+ if (!args.sourceRunId) {
382
+ return createErrorResult('sourceRunId is required for plan operation');
383
+ }
384
+ const run = await service.getRunDetails(args.sourceRunId);
385
+ if (!run) {
386
+ return createErrorResult(`Quality run not found: ${args.sourceRunId}`);
387
+ }
388
+ const plan = QualityService.generateImprovementPlan(run, args.targetGrade);
389
+ const result = await service.saveImprovementPlan(plan);
390
+ const totalInitiatives = plan.phases.reduce((sum, phase) => sum + phase.initiatives.length, 0);
391
+ const response = [
392
+ '✅ Quality Improvement Plan generated successfully!',
393
+ '',
394
+ `📋 Plan ID: ${result.planId}`,
395
+ `📁 File: ${result.filePath}`,
396
+ `📊 Current Score: ${plan.currentScore}/100 (Grade ${plan.currentGrade})`,
397
+ `🎯 Phases: ${plan.phases.length}`,
398
+ `📦 Initiatives: ${totalInitiatives}`,
399
+ `⏱️ Total Effort: ${plan.totalEffortHours} hours`,
400
+ '',
401
+ '## Executive Summary',
402
+ '',
403
+ plan.executiveSummary,
404
+ '',
405
+ 'Use "quality plan-details planId=<id>" to see full plan details.',
406
+ ].join('\n');
407
+ return createSuccessResult(response);
408
+ }
409
+ static async handlePlanDetails(args, repositoryRoot) {
410
+ if (!args.planId) {
411
+ return createErrorResult('planId is required for plan-details operation');
412
+ }
413
+ try {
414
+ const plansDir = path.join(repositoryRoot, 'docs', 'quality', 'plans');
415
+ const filePath = path.join(plansDir, `${args.planId}.md`);
416
+ const content = await fs.readFile(filePath, 'utf-8');
417
+ // Extract JSON from the Raw Data code block
418
+ const jsonMatch = content.match(/```json\n([\s\S]*?)\n```/);
419
+ if (!jsonMatch) {
420
+ return createErrorResult(`Could not parse plan data from: ${args.planId}`);
421
+ }
422
+ const planData = JSON.parse(jsonMatch[1]);
423
+ const lines = [
424
+ `📋 Quality Improvement Plan: ${planData.planId}`,
425
+ '',
426
+ '## Summary',
427
+ `**Date:** ${planData.date}`,
428
+ `**Source Run:** ${planData.sourceRunId}`,
429
+ `**Current Score:** ${planData.currentScore}/100 (Grade ${planData.currentGrade})`,
430
+ `**Total Effort:** ${planData.totalEffortHours} hours`,
431
+ '',
432
+ '## Executive Summary',
433
+ '',
434
+ planData.executiveSummary,
435
+ '',
436
+ ];
437
+ // Phase summaries
438
+ if (planData.phases && planData.phases.length > 0) {
439
+ lines.push('## Phases');
440
+ lines.push('');
441
+ for (const phase of planData.phases) {
442
+ lines.push(`### Phase ${phase.phase}: ${phase.name}`);
443
+ lines.push(`**Projected Score:** ${phase.projectedScore}/100 (Grade ${phase.projectedGrade})`);
444
+ lines.push(`**Effort:** ${phase.totalEffortHours} hours`);
445
+ lines.push(`**Initiatives:** ${phase.initiatives.length}`);
446
+ lines.push('');
447
+ }
448
+ }
449
+ // ROI
450
+ if (planData.roiAnalysis) {
451
+ lines.push('## ROI Analysis');
452
+ lines.push(`**Estimated Cost:** ${planData.roiAnalysis.currency} ${planData.roiAnalysis.estimatedCostToFix}`);
453
+ lines.push('');
454
+ }
455
+ return createSuccessResult(lines.join('\n'));
456
+ }
457
+ catch {
458
+ return createErrorResult(`Improvement plan not found: ${args.planId}`);
459
+ }
460
+ }
246
461
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magic-ingredients/tiny-brain-local",
3
- "version": "0.18.1",
3
+ "version": "0.19.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.18.1",
34
+ "@magic-ingredients/tiny-brain-core": "^0.19.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",