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