@magic-ingredients/tiny-brain-local 0.18.0 → 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/analyse-service.d.ts.map +1 -1
- package/dist/services/analyse-service.js +1 -3
- 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 +77 -33
- package/dist/tools/config/config.tool.d.ts.map +1 -1
- package/dist/tools/config/config.tool.js +1 -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
- package/dist/services/tech-context-service.d.ts +0 -106
- package/dist/services/tech-context-service.d.ts.map +0 -1
- package/dist/services/tech-context-service.js +0 -365
|
@@ -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,
|
|
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"}
|
|
@@ -4,10 +4,8 @@
|
|
|
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 } from '@magic-ingredients/tiny-brain-core';
|
|
7
|
+
import { analyseRepository, formatTechStackChanges, detectTechStackChanges, ConfigService, RepoConfigService, LibraryClient, TechContextService, } from '@magic-ingredients/tiny-brain-core';
|
|
8
8
|
import { RepoService } from './repo-service.js';
|
|
9
|
-
import { LibraryClient } from '@magic-ingredients/tiny-brain-core';
|
|
10
|
-
import { TechContextService } from './tech-context-service.js';
|
|
11
9
|
import { fileURLToPath } from 'url';
|
|
12
10
|
import { dirname, join } from 'path';
|
|
13
11
|
import { existsSync } from 'fs';
|
|
@@ -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
|
*/
|
|
@@ -229,7 +259,7 @@ export class RepoService {
|
|
|
229
259
|
**BEFORE EVERY COMMIT**, check if you're working on tracked work:
|
|
230
260
|
|
|
231
261
|
1. **Active PRDs?** Check \`.tiny-brain/progress/\` for \`in_progress\` status
|
|
232
|
-
2. **Open Fixes?** Check \`.tiny-brain/fixes/progress.json\` for \`
|
|
262
|
+
2. **Open Fixes?** Check \`.tiny-brain/fixes/progress.json\` for \`documented\` or \`in_progress\` status
|
|
233
263
|
|
|
234
264
|
**If YES, you MUST include tracking headers or the commit will be REJECTED.**
|
|
235
265
|
|
|
@@ -265,42 +295,53 @@ Description of changes...
|
|
|
265
295
|
|
|
266
296
|
**WARNING:** The commit-msg hook will reject commits missing required headers.
|
|
267
297
|
|
|
268
|
-
###
|
|
269
|
-
|
|
270
|
-
**IMMEDIATELY after your commit is accepted**, you MUST update the source markdown file. This is NOT optional.
|
|
298
|
+
### Updating Markdown After Commits
|
|
271
299
|
|
|
272
300
|
**For PRD tasks:**
|
|
273
301
|
1. Open the feature file: \`docs/prd/{prd-id}/features/{feature-id}.md\`
|
|
274
|
-
2.
|
|
302
|
+
2. Update the task with status and commitSha:
|
|
303
|
+
\`\`\`markdown
|
|
304
|
+
### 1. Task description
|
|
305
|
+
status: completed
|
|
306
|
+
commitSha: abc1234
|
|
275
307
|
\`\`\`
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
4. If ALL tasks in the feature are complete, mark the feature complete in the PRD file
|
|
281
|
-
- The feature file's frontmatter contains \`prd: {prd-id}\` - use this to find \`docs/prd/{prd-id}/prd.md\`
|
|
308
|
+
3. Run: \`npx tiny-brain sync-file docs/prd/{prd-id}/features/{feature-id}.md\`
|
|
309
|
+
4. If ALL tasks in the feature are complete, update the PRD status
|
|
310
|
+
|
|
311
|
+
### Fix Status Workflow
|
|
282
312
|
|
|
283
|
-
|
|
313
|
+
Fix documents have three statuses: \`documented\` → \`in_progress\` → \`resolved\`
|
|
314
|
+
|
|
315
|
+
**When starting work on a fix:**
|
|
284
316
|
1. Open the fix file: \`.tiny-brain/fixes/{fix-id}.md\`
|
|
285
|
-
2.
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
3.
|
|
297
|
-
|
|
298
|
-
|
|
317
|
+
2. Update frontmatter: \`status: in_progress\`
|
|
318
|
+
3. Run: \`npx tiny-brain sync-file .tiny-brain/fixes/{fix-id}.md\`
|
|
319
|
+
|
|
320
|
+
**After each commit:**
|
|
321
|
+
1. Update the completed task(s) in the markdown:
|
|
322
|
+
\`\`\`markdown
|
|
323
|
+
### 1. Task description
|
|
324
|
+
status: completed
|
|
325
|
+
commitSha: abc1234
|
|
326
|
+
\`\`\`
|
|
327
|
+
2. If one commit addresses multiple tasks, use the same commitSha for all of them
|
|
328
|
+
3. If a task is no longer needed (work done elsewhere or obsolete), mark it superseded:
|
|
329
|
+
\`\`\`markdown
|
|
330
|
+
### 3. Obsolete task
|
|
331
|
+
status: superseded
|
|
332
|
+
commitSha: null
|
|
333
|
+
\`\`\`
|
|
334
|
+
4. Run: \`npx tiny-brain sync-file .tiny-brain/fixes/{fix-id}.md\`
|
|
335
|
+
|
|
336
|
+
**When all tasks are complete:**
|
|
337
|
+
1. **ONLY set \`status: resolved\`** when ALL tasks are accounted for:
|
|
338
|
+
- 100% of tasks must have either \`status: completed\` (with commitSha) or \`status: superseded\`
|
|
339
|
+
- Example: A fix with 5 tasks could be: 3 completed + 2 superseded = resolved
|
|
299
340
|
- A fix with incomplete tasks stays \`in_progress\`
|
|
300
|
-
|
|
341
|
+
2. Update YAML frontmatter:
|
|
301
342
|
- Set \`status: resolved\`
|
|
302
343
|
- Set \`resolved: YYYY-MM-DDTHH:mm:ss.sssZ\` (ISO timestamp)
|
|
303
|
-
- Add \`resolution\` object
|
|
344
|
+
- Add \`resolution\` object:
|
|
304
345
|
\`\`\`yaml
|
|
305
346
|
resolution:
|
|
306
347
|
rootCause: Brief description of what caused the issue
|
|
@@ -311,10 +352,9 @@ Description of changes...
|
|
|
311
352
|
- path/to/file1.ts
|
|
312
353
|
- path/to/file2.ts
|
|
313
354
|
\`\`\`
|
|
355
|
+
3. Run: \`npx tiny-brain sync-file .tiny-brain/fixes/{fix-id}.md\`
|
|
314
356
|
|
|
315
|
-
**The
|
|
316
|
-
|
|
317
|
-
**Failure to update markdown files is a workflow violation.**
|
|
357
|
+
**Note:** The markdown file is the source of truth. The \`sync-file\` command updates \`progress.json\` from the markdown.
|
|
318
358
|
|
|
319
359
|
`;
|
|
320
360
|
}
|
|
@@ -440,6 +480,10 @@ The \`.tiny-brain/\` directory stores operational tracking data separate from do
|
|
|
440
480
|
|
|
441
481
|
\`\`\`
|
|
442
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
|
|
443
487
|
├── progress/ # PRD progress tracking
|
|
444
488
|
│ └── {prd-id}.json # One file per PRD
|
|
445
489
|
└── fixes/ # Fix progress tracking
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.tool.d.ts","sourceRoot":"","sources":["../../../src/tools/config/config.tool.ts"],"names":[],"mappings":"AACA,OAAO,EAA0C,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"config.tool.d.ts","sourceRoot":"","sources":["../../../src/tools/config/config.tool.ts"],"names":[],"mappings":"AACA,OAAO,EAA0C,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAUrE,qBAAa,UAAU;IACrB,MAAM,CAAC,iBAAiB,IAAI,OAAO;WA4DtB,OAAO,CAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,UAAU,CAAC;mBA6BD,UAAU;mBAwCV,SAAS;mBAoCT,iBAAiB;mBAgEjB,SAAS;mBAuDT,WAAW;CAqBjC"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { createSuccessResult, createErrorResult } from '../index.js';
|
|
3
|
-
import { ConfigService } from '@magic-ingredients/tiny-brain-core';
|
|
4
|
-
import { TechContextService } from '../../services/tech-context-service.js';
|
|
3
|
+
import { ConfigService, TechContextService } from '@magic-ingredients/tiny-brain-core';
|
|
5
4
|
const ConfigArgsSchema = z.object({
|
|
6
5
|
operation: z.enum(['list', 'get', 'show-sources', 'set', 'reset']),
|
|
7
6
|
key: z.string().optional(),
|
|
@@ -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",
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tech Context Service
|
|
3
|
-
*
|
|
4
|
-
* Manages reading and writing of tech context files:
|
|
5
|
-
* - .tiny-brain/analysis.json - repo analysis data
|
|
6
|
-
* - .tiny-brain/tech/*.md - per-tech expertise files
|
|
7
|
-
* - .tiny-brain/tech/config.json - agent mode configuration
|
|
8
|
-
* - .claude/agents/tech-*.md - tech agent files (when enableAgentic=true)
|
|
9
|
-
*/
|
|
10
|
-
import type { RepoAnalysisFile, TechExpertiseFrontmatter, TechExpertiseFile, TechConfig } from '@magic-ingredients/tiny-brain-core';
|
|
11
|
-
/** Input analysis data from repository analysis */
|
|
12
|
-
export interface AnalysisInput {
|
|
13
|
-
languages: string[];
|
|
14
|
-
frameworks: string[];
|
|
15
|
-
testingTools: string[];
|
|
16
|
-
buildTools: string[];
|
|
17
|
-
hasTests: boolean;
|
|
18
|
-
testFileCount: number;
|
|
19
|
-
testPatterns: string[];
|
|
20
|
-
isPolyglot?: boolean;
|
|
21
|
-
primaryLanguage?: string;
|
|
22
|
-
documentationPattern?: 'single-readme' | 'folder-readmes' | 'docs-folder' | 'mixed';
|
|
23
|
-
documentationLocations?: string[];
|
|
24
|
-
}
|
|
25
|
-
export declare class TechContextService {
|
|
26
|
-
private readonly tinyBrainDir;
|
|
27
|
-
private readonly techDir;
|
|
28
|
-
private readonly agentsDir;
|
|
29
|
-
constructor(repoPath: string);
|
|
30
|
-
/** Get the .tiny-brain directory path */
|
|
31
|
-
getTinyBrainDir(): string;
|
|
32
|
-
/** Get the .tiny-brain/tech directory path */
|
|
33
|
-
getTechDir(): string;
|
|
34
|
-
/** Get the .claude/agents directory path */
|
|
35
|
-
getAgentsDir(): string;
|
|
36
|
-
/** Ensure required directories exist */
|
|
37
|
-
ensureDirectories(): Promise<void>;
|
|
38
|
-
/**
|
|
39
|
-
* Write analysis data to .tiny-brain/analysis.json
|
|
40
|
-
*/
|
|
41
|
-
writeAnalysis(analysis: AnalysisInput): Promise<void>;
|
|
42
|
-
/**
|
|
43
|
-
* Write a tech expertise file with YAML frontmatter
|
|
44
|
-
*/
|
|
45
|
-
writeTechFile(name: string, frontmatter: TechExpertiseFrontmatter, content: string): Promise<void>;
|
|
46
|
-
/**
|
|
47
|
-
* Write raw markdown content to a tech file
|
|
48
|
-
* Used when receiving complete markdown from TBR API
|
|
49
|
-
*/
|
|
50
|
-
writeTechFileRaw(name: string, content: string): Promise<void>;
|
|
51
|
-
/**
|
|
52
|
-
* Read analysis data from .tiny-brain/analysis.json
|
|
53
|
-
*/
|
|
54
|
-
readAnalysis(): Promise<RepoAnalysisFile | null>;
|
|
55
|
-
/**
|
|
56
|
-
* Read all tech expertise files from .tiny-brain/tech/
|
|
57
|
-
*/
|
|
58
|
-
readTechFiles(): Promise<TechExpertiseFile[]>;
|
|
59
|
-
/**
|
|
60
|
-
* Get tech files that match a given file path based on filePatterns
|
|
61
|
-
*/
|
|
62
|
-
getTechForFile(filePath: string): Promise<TechExpertiseFile[]>;
|
|
63
|
-
/**
|
|
64
|
-
* Check if the tech stack has changed compared to previous analysis
|
|
65
|
-
*/
|
|
66
|
-
hasStackChanged(analysis: AnalysisInput): Promise<boolean>;
|
|
67
|
-
/**
|
|
68
|
-
* Write config to .tiny-brain/tech/config.json
|
|
69
|
-
*/
|
|
70
|
-
writeConfig(config: Omit<TechConfig, 'lastSynced'>): Promise<void>;
|
|
71
|
-
/**
|
|
72
|
-
* Read config from .tiny-brain/tech/config.json
|
|
73
|
-
*/
|
|
74
|
-
readConfig(): Promise<TechConfig>;
|
|
75
|
-
/**
|
|
76
|
-
* Install tech agents to .claude/agents/
|
|
77
|
-
* Converts tech context files into proper Claude Code sub-agents with:
|
|
78
|
-
* - name: tech-{name} (matches subagent_type="tech-react")
|
|
79
|
-
* - description: for Claude Code auto-delegation
|
|
80
|
-
* - Sub-agent invocation context
|
|
81
|
-
*/
|
|
82
|
-
installTechAgents(): Promise<void>;
|
|
83
|
-
/**
|
|
84
|
-
* Remove only tech-*.md files from .claude/agents/
|
|
85
|
-
*/
|
|
86
|
-
removeTechAgents(): Promise<void>;
|
|
87
|
-
/**
|
|
88
|
-
* Sync agents based on enableAgentic preference
|
|
89
|
-
*/
|
|
90
|
-
syncAgents(enableAgentic: boolean): Promise<void>;
|
|
91
|
-
/**
|
|
92
|
-
* Get versions of all local tech context files
|
|
93
|
-
* @returns Map of tech name → version
|
|
94
|
-
*/
|
|
95
|
-
getLocalTechVersions(): Promise<Map<string, string>>;
|
|
96
|
-
/**
|
|
97
|
-
* Compare versions to determine if TBS version is newer
|
|
98
|
-
* @returns true if tbsVersion is newer than localVersion
|
|
99
|
-
*/
|
|
100
|
-
shouldUpdateTech(localVersion: string, tbsVersion: string): boolean;
|
|
101
|
-
/**
|
|
102
|
-
* Parse YAML frontmatter from a markdown file
|
|
103
|
-
*/
|
|
104
|
-
private parseFrontmatter;
|
|
105
|
-
}
|
|
106
|
-
//# sourceMappingURL=tech-context-service.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tech-context-service.d.ts","sourceRoot":"","sources":["../../src/services/tech-context-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EACV,gBAAgB,EAGhB,wBAAwB,EACxB,iBAAiB,EACjB,UAAU,EACX,MAAM,oCAAoC,CAAC;AAE5C,mDAAmD;AACnD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,eAAe,GAAG,gBAAgB,GAAG,aAAa,GAAG,OAAO,CAAC;IACpF,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC;CACnC;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,QAAQ,EAAE,MAAM;IAM5B,yCAAyC;IACzC,eAAe,IAAI,MAAM;IAIzB,8CAA8C;IAC9C,UAAU,IAAI,MAAM;IAIpB,4CAA4C;IAC5C,YAAY,IAAI,MAAM;IAItB,wCAAwC;IAClC,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAKxC;;OAEG;IACG,aAAa,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAoC3D;;OAEG;IACG,aAAa,CACjB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,wBAAwB,EACrC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC;IAmBhB;;;OAGG;IACG,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMpE;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAWtD;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IA0BnD;;OAEG;IACG,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAwBpE;;OAEG;IACG,eAAe,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IA4BhE;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAYxE;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC;IAWvC;;;;;;OAMG;IACG,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAkCxC;;OAEG;IACG,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAcvC;;OAEG;IACG,UAAU,CAAC,aAAa,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvD;;;OAGG;IACG,oBAAoB,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAW1D;;;OAGG;IACH,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO;IAsBnE;;OAEG;IACH,OAAO,CAAC,gBAAgB;CA+CzB"}
|
|
@@ -1,365 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tech Context Service
|
|
3
|
-
*
|
|
4
|
-
* Manages reading and writing of tech context files:
|
|
5
|
-
* - .tiny-brain/analysis.json - repo analysis data
|
|
6
|
-
* - .tiny-brain/tech/*.md - per-tech expertise files
|
|
7
|
-
* - .tiny-brain/tech/config.json - agent mode configuration
|
|
8
|
-
* - .claude/agents/tech-*.md - tech agent files (when enableAgentic=true)
|
|
9
|
-
*/
|
|
10
|
-
import { promises as fs } from 'fs';
|
|
11
|
-
import path from 'path';
|
|
12
|
-
import crypto from 'crypto';
|
|
13
|
-
import minimatch from 'minimatch';
|
|
14
|
-
export class TechContextService {
|
|
15
|
-
tinyBrainDir;
|
|
16
|
-
techDir;
|
|
17
|
-
agentsDir;
|
|
18
|
-
constructor(repoPath) {
|
|
19
|
-
this.tinyBrainDir = path.join(repoPath, '.tiny-brain');
|
|
20
|
-
this.techDir = path.join(this.tinyBrainDir, 'tech');
|
|
21
|
-
this.agentsDir = path.join(repoPath, '.claude', 'agents');
|
|
22
|
-
}
|
|
23
|
-
/** Get the .tiny-brain directory path */
|
|
24
|
-
getTinyBrainDir() {
|
|
25
|
-
return this.tinyBrainDir;
|
|
26
|
-
}
|
|
27
|
-
/** Get the .tiny-brain/tech directory path */
|
|
28
|
-
getTechDir() {
|
|
29
|
-
return this.techDir;
|
|
30
|
-
}
|
|
31
|
-
/** Get the .claude/agents directory path */
|
|
32
|
-
getAgentsDir() {
|
|
33
|
-
return this.agentsDir;
|
|
34
|
-
}
|
|
35
|
-
/** Ensure required directories exist */
|
|
36
|
-
async ensureDirectories() {
|
|
37
|
-
await fs.mkdir(this.tinyBrainDir, { recursive: true });
|
|
38
|
-
await fs.mkdir(this.techDir, { recursive: true });
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Write analysis data to .tiny-brain/analysis.json
|
|
42
|
-
*/
|
|
43
|
-
async writeAnalysis(analysis) {
|
|
44
|
-
await this.ensureDirectories();
|
|
45
|
-
const stack = {
|
|
46
|
-
languages: analysis.languages,
|
|
47
|
-
frameworks: analysis.frameworks,
|
|
48
|
-
testing: analysis.testingTools,
|
|
49
|
-
build: analysis.buildTools,
|
|
50
|
-
};
|
|
51
|
-
const analysisData = {
|
|
52
|
-
hasTests: analysis.hasTests,
|
|
53
|
-
testFileCount: analysis.testFileCount,
|
|
54
|
-
testPatterns: analysis.testPatterns,
|
|
55
|
-
isPolyglot: analysis.isPolyglot ?? false,
|
|
56
|
-
primaryLanguage: analysis.primaryLanguage ?? analysis.languages[0] ?? 'unknown',
|
|
57
|
-
documentationPattern: analysis.documentationPattern,
|
|
58
|
-
documentationLocations: analysis.documentationLocations,
|
|
59
|
-
};
|
|
60
|
-
// Generate hash from stable analysis input
|
|
61
|
-
const hashInput = JSON.stringify({ stack, analysis: analysisData });
|
|
62
|
-
const analysisHash = crypto.createHash('sha256').update(hashInput).digest('hex');
|
|
63
|
-
const file = {
|
|
64
|
-
version: '1.0',
|
|
65
|
-
detectedAt: new Date().toISOString(),
|
|
66
|
-
analysisHash,
|
|
67
|
-
stack,
|
|
68
|
-
analysis: analysisData,
|
|
69
|
-
};
|
|
70
|
-
const filePath = path.join(this.tinyBrainDir, 'analysis.json');
|
|
71
|
-
await fs.writeFile(filePath, JSON.stringify(file, null, 2), 'utf-8');
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Write a tech expertise file with YAML frontmatter
|
|
75
|
-
*/
|
|
76
|
-
async writeTechFile(name, frontmatter, content) {
|
|
77
|
-
await this.ensureDirectories();
|
|
78
|
-
const yamlFrontmatter = [
|
|
79
|
-
'---',
|
|
80
|
-
`name: ${frontmatter.name}`,
|
|
81
|
-
`version: ${frontmatter.version}`,
|
|
82
|
-
`domain: ${frontmatter.domain}`,
|
|
83
|
-
`filePatterns:`,
|
|
84
|
-
...frontmatter.filePatterns.map(p => ` - "${p}"`),
|
|
85
|
-
frontmatter.description ? `description: "${frontmatter.description}"` : null,
|
|
86
|
-
'---',
|
|
87
|
-
].filter(Boolean).join('\n');
|
|
88
|
-
const fileContent = `${yamlFrontmatter}\n\n${content}`;
|
|
89
|
-
const filePath = path.join(this.techDir, `${name}.md`);
|
|
90
|
-
await fs.writeFile(filePath, fileContent, 'utf-8');
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Write raw markdown content to a tech file
|
|
94
|
-
* Used when receiving complete markdown from TBR API
|
|
95
|
-
*/
|
|
96
|
-
async writeTechFileRaw(name, content) {
|
|
97
|
-
await this.ensureDirectories();
|
|
98
|
-
const filePath = path.join(this.techDir, `${name}.md`);
|
|
99
|
-
await fs.writeFile(filePath, content, 'utf-8');
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Read analysis data from .tiny-brain/analysis.json
|
|
103
|
-
*/
|
|
104
|
-
async readAnalysis() {
|
|
105
|
-
const filePath = path.join(this.tinyBrainDir, 'analysis.json');
|
|
106
|
-
try {
|
|
107
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
108
|
-
return JSON.parse(content);
|
|
109
|
-
}
|
|
110
|
-
catch {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Read all tech expertise files from .tiny-brain/tech/
|
|
116
|
-
*/
|
|
117
|
-
async readTechFiles() {
|
|
118
|
-
try {
|
|
119
|
-
const files = await fs.readdir(this.techDir);
|
|
120
|
-
const techFiles = [];
|
|
121
|
-
for (const file of files) {
|
|
122
|
-
if (!file.endsWith('.md'))
|
|
123
|
-
continue;
|
|
124
|
-
const filePath = path.join(this.techDir, file);
|
|
125
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
126
|
-
const parsed = this.parseFrontmatter(content);
|
|
127
|
-
if (parsed) {
|
|
128
|
-
techFiles.push({
|
|
129
|
-
...parsed,
|
|
130
|
-
filePath,
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return techFiles;
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
return [];
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Get tech files that match a given file path based on filePatterns
|
|
142
|
-
*/
|
|
143
|
-
async getTechForFile(filePath) {
|
|
144
|
-
const techFiles = await this.readTechFiles();
|
|
145
|
-
const matches = [];
|
|
146
|
-
const basename = path.basename(filePath);
|
|
147
|
-
for (const techFile of techFiles) {
|
|
148
|
-
for (const pattern of techFile.frontmatter.filePatterns) {
|
|
149
|
-
// Check multiple matching strategies:
|
|
150
|
-
// 1. Full path glob match
|
|
151
|
-
// 2. Basename match (for patterns like *.tsx)
|
|
152
|
-
// 3. Directory prefix match (for patterns like components/)
|
|
153
|
-
if (minimatch(filePath, pattern) ||
|
|
154
|
-
minimatch(basename, pattern) ||
|
|
155
|
-
minimatch(filePath, `**/${pattern}`) ||
|
|
156
|
-
filePath.includes(pattern.replace(/\/$/, ''))) {
|
|
157
|
-
matches.push(techFile);
|
|
158
|
-
break; // Don't add same file multiple times
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return matches;
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Check if the tech stack has changed compared to previous analysis
|
|
166
|
-
*/
|
|
167
|
-
async hasStackChanged(analysis) {
|
|
168
|
-
const existing = await this.readAnalysis();
|
|
169
|
-
if (!existing)
|
|
170
|
-
return true;
|
|
171
|
-
// Generate hash for new analysis using same logic as writeAnalysis
|
|
172
|
-
const stack = {
|
|
173
|
-
languages: analysis.languages,
|
|
174
|
-
frameworks: analysis.frameworks,
|
|
175
|
-
testing: analysis.testingTools,
|
|
176
|
-
build: analysis.buildTools,
|
|
177
|
-
};
|
|
178
|
-
const analysisData = {
|
|
179
|
-
hasTests: analysis.hasTests,
|
|
180
|
-
testFileCount: analysis.testFileCount,
|
|
181
|
-
testPatterns: analysis.testPatterns,
|
|
182
|
-
isPolyglot: analysis.isPolyglot ?? false,
|
|
183
|
-
primaryLanguage: analysis.primaryLanguage ?? analysis.languages[0] ?? 'unknown',
|
|
184
|
-
documentationPattern: analysis.documentationPattern,
|
|
185
|
-
documentationLocations: analysis.documentationLocations,
|
|
186
|
-
};
|
|
187
|
-
const hashInput = JSON.stringify({ stack, analysis: analysisData });
|
|
188
|
-
const newHash = crypto.createHash('sha256').update(hashInput).digest('hex');
|
|
189
|
-
return newHash !== existing.analysisHash;
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Write config to .tiny-brain/tech/config.json
|
|
193
|
-
*/
|
|
194
|
-
async writeConfig(config) {
|
|
195
|
-
await this.ensureDirectories();
|
|
196
|
-
const fullConfig = {
|
|
197
|
-
...config,
|
|
198
|
-
lastSynced: new Date().toISOString(),
|
|
199
|
-
};
|
|
200
|
-
const filePath = path.join(this.techDir, 'config.json');
|
|
201
|
-
await fs.writeFile(filePath, JSON.stringify(fullConfig, null, 2), 'utf-8');
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Read config from .tiny-brain/tech/config.json
|
|
205
|
-
*/
|
|
206
|
-
async readConfig() {
|
|
207
|
-
const filePath = path.join(this.techDir, 'config.json');
|
|
208
|
-
try {
|
|
209
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
210
|
-
return JSON.parse(content);
|
|
211
|
-
}
|
|
212
|
-
catch {
|
|
213
|
-
return { useAgents: false };
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Install tech agents to .claude/agents/
|
|
218
|
-
* Converts tech context files into proper Claude Code sub-agents with:
|
|
219
|
-
* - name: tech-{name} (matches subagent_type="tech-react")
|
|
220
|
-
* - description: for Claude Code auto-delegation
|
|
221
|
-
* - Sub-agent invocation context
|
|
222
|
-
*/
|
|
223
|
-
async installTechAgents() {
|
|
224
|
-
await fs.mkdir(this.agentsDir, { recursive: true });
|
|
225
|
-
const techFiles = await this.readTechFiles();
|
|
226
|
-
for (const techFile of techFiles) {
|
|
227
|
-
const name = techFile.frontmatter.name;
|
|
228
|
-
const agentFileName = `tech-${name}.md`;
|
|
229
|
-
const agentPath = path.join(this.agentsDir, agentFileName);
|
|
230
|
-
// Build description from frontmatter or generate default
|
|
231
|
-
const description = techFile.frontmatter.description
|
|
232
|
-
|| `${name} development specialist. Use for ${techFile.frontmatter.domain} tasks involving ${name}.`;
|
|
233
|
-
// Create agent file with proper Claude Code sub-agent format
|
|
234
|
-
const agentContent = `---
|
|
235
|
-
name: tech-${name}
|
|
236
|
-
description: ${description}
|
|
237
|
-
version: ${techFile.frontmatter.version}
|
|
238
|
-
domain: ${techFile.frontmatter.domain}
|
|
239
|
-
---
|
|
240
|
-
|
|
241
|
-
# ${name} Sub-Agent
|
|
242
|
-
|
|
243
|
-
You are a specialized ${name} development agent invoked by the developer agent. Apply the expertise and patterns below to the task you've been given.
|
|
244
|
-
|
|
245
|
-
## Tech Expertise
|
|
246
|
-
|
|
247
|
-
${techFile.content}`;
|
|
248
|
-
await fs.writeFile(agentPath, agentContent, 'utf-8');
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
/**
|
|
252
|
-
* Remove only tech-*.md files from .claude/agents/
|
|
253
|
-
*/
|
|
254
|
-
async removeTechAgents() {
|
|
255
|
-
try {
|
|
256
|
-
const files = await fs.readdir(this.agentsDir);
|
|
257
|
-
for (const file of files) {
|
|
258
|
-
if (file.startsWith('tech-') && file.endsWith('.md')) {
|
|
259
|
-
await fs.unlink(path.join(this.agentsDir, file));
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
catch {
|
|
264
|
-
// Directory may not exist, ignore
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
/**
|
|
268
|
-
* Sync agents based on enableAgentic preference
|
|
269
|
-
*/
|
|
270
|
-
async syncAgents(enableAgentic) {
|
|
271
|
-
await this.writeConfig({ useAgents: enableAgentic });
|
|
272
|
-
if (enableAgentic) {
|
|
273
|
-
await this.installTechAgents();
|
|
274
|
-
}
|
|
275
|
-
else {
|
|
276
|
-
await this.removeTechAgents();
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Get versions of all local tech context files
|
|
281
|
-
* @returns Map of tech name → version
|
|
282
|
-
*/
|
|
283
|
-
async getLocalTechVersions() {
|
|
284
|
-
const techFiles = await this.readTechFiles();
|
|
285
|
-
const versions = new Map();
|
|
286
|
-
for (const file of techFiles) {
|
|
287
|
-
versions.set(file.frontmatter.name, file.frontmatter.version);
|
|
288
|
-
}
|
|
289
|
-
return versions;
|
|
290
|
-
}
|
|
291
|
-
/**
|
|
292
|
-
* Compare versions to determine if TBS version is newer
|
|
293
|
-
* @returns true if tbsVersion is newer than localVersion
|
|
294
|
-
*/
|
|
295
|
-
shouldUpdateTech(localVersion, tbsVersion) {
|
|
296
|
-
const parseVersion = (v) => {
|
|
297
|
-
return v.split('.').map(s => parseInt(s, 10) || 0);
|
|
298
|
-
};
|
|
299
|
-
const local = parseVersion(localVersion);
|
|
300
|
-
const tbs = parseVersion(tbsVersion);
|
|
301
|
-
// Pad arrays to same length
|
|
302
|
-
const maxLen = Math.max(local.length, tbs.length);
|
|
303
|
-
while (local.length < maxLen)
|
|
304
|
-
local.push(0);
|
|
305
|
-
while (tbs.length < maxLen)
|
|
306
|
-
tbs.push(0);
|
|
307
|
-
// Compare each segment
|
|
308
|
-
for (let i = 0; i < maxLen; i++) {
|
|
309
|
-
if (tbs[i] > local[i])
|
|
310
|
-
return true;
|
|
311
|
-
if (tbs[i] < local[i])
|
|
312
|
-
return false;
|
|
313
|
-
}
|
|
314
|
-
return false; // Versions are equal
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Parse YAML frontmatter from a markdown file
|
|
318
|
-
*/
|
|
319
|
-
parseFrontmatter(content) {
|
|
320
|
-
const match = content.match(/^---\n([\s\S]*?)\n---\n\n?([\s\S]*)$/);
|
|
321
|
-
if (!match)
|
|
322
|
-
return null;
|
|
323
|
-
const yamlContent = match[1];
|
|
324
|
-
const markdownContent = match[2];
|
|
325
|
-
// Simple YAML parsing for our known structure
|
|
326
|
-
const frontmatter = {};
|
|
327
|
-
const lines = yamlContent.split('\n');
|
|
328
|
-
let currentKey = '';
|
|
329
|
-
let filePatterns = [];
|
|
330
|
-
for (const line of lines) {
|
|
331
|
-
const keyMatch = line.match(/^(\w+):\s*(.*)$/);
|
|
332
|
-
if (keyMatch) {
|
|
333
|
-
currentKey = keyMatch[1];
|
|
334
|
-
const value = keyMatch[2].replace(/^["']|["']$/g, '');
|
|
335
|
-
if (currentKey === 'filePatterns') {
|
|
336
|
-
filePatterns = [];
|
|
337
|
-
}
|
|
338
|
-
else if (currentKey === 'name') {
|
|
339
|
-
frontmatter.name = value;
|
|
340
|
-
}
|
|
341
|
-
else if (currentKey === 'version') {
|
|
342
|
-
frontmatter.version = value;
|
|
343
|
-
}
|
|
344
|
-
else if (currentKey === 'domain') {
|
|
345
|
-
frontmatter.domain = value;
|
|
346
|
-
}
|
|
347
|
-
else if (currentKey === 'description') {
|
|
348
|
-
frontmatter.description = value;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
else if (currentKey === 'filePatterns' && line.trim().startsWith('-')) {
|
|
352
|
-
const pattern = line.trim().replace(/^-\s*/, '').replace(/^["']|["']$/g, '');
|
|
353
|
-
filePatterns.push(pattern);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
frontmatter.filePatterns = filePatterns;
|
|
357
|
-
if (!frontmatter.name || !frontmatter.version || !frontmatter.domain) {
|
|
358
|
-
return null;
|
|
359
|
-
}
|
|
360
|
-
return {
|
|
361
|
-
frontmatter: frontmatter,
|
|
362
|
-
content: markdownContent,
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
}
|