@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.
- package/dist/services/repo-service.d.ts +5 -0
- package/dist/services/repo-service.d.ts.map +1 -1
- package/dist/services/repo-service.js +36 -2
- package/dist/tools/persona/as.tool.d.ts +4 -0
- package/dist/tools/persona/as.tool.d.ts.map +1 -1
- package/dist/tools/persona/as.tool.js +56 -30
- package/dist/tools/quality/quality.tool.d.ts +3 -0
- package/dist/tools/quality/quality.tool.d.ts.map +1 -1
- package/dist/tools/quality/quality.tool.js +218 -3
- package/package.json +2 -2
|
@@ -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;
|
|
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
|
|
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;
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
-
|
|
479
|
-
|
|
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
|
|
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;
|
|
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.
|
|
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.
|
|
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",
|