@magic-ingredients/tiny-brain-local 0.16.0 → 0.17.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,161 +1,49 @@
1
1
  /**
2
2
  * Repository Context Service
3
3
  *
4
- * Manages the tiny-brain section of the context file (CLAUDE.md, etc.)
5
- * Responsible for formatting and storing repository analysis and agent usage.
4
+ * Service for repository context operations including:
5
+ * - Writing workflow sections to CLAUDE.md (commit format, TDD, progress tracking)
6
+ * - Tech context now managed by TechContextService (.tiny-brain/analysis.json, .tiny-brain/tech/*.md)
6
7
  */
7
8
  import * as fs from 'fs/promises';
8
9
  import { existsSync, readFileSync } from 'fs';
9
10
  import * as path from 'path';
10
11
  import { fileURLToPath } from 'url';
11
- import { detectTechStackChanges, ConfigService } from '@magic-ingredients/tiny-brain-core';
12
12
  import { getRepoRoot } from '../utils/repo-utils.js';
13
+ import { ConfigService } from '@magic-ingredients/tiny-brain-core';
13
14
  /**
14
- * Service for managing repository context in the context file
15
- * Handles the tiny-brain section with analysis and agent usage
15
+ * Service for repository context operations
16
+ * Tech context is now managed by TechContextService
17
+ * Workflow sections are written to CLAUDE.md based on config flags
16
18
  */
17
19
  export class RepoService {
18
20
  context;
19
21
  static REPO_BLOCK_START = '## tiny-brain - start';
20
22
  static REPO_BLOCK_END = '## tiny-brain - end';
21
- static ANALYSIS_MARKER_PREFIX = '<!-- TB_ANALYSIS:';
22
- static ANALYSIS_MARKER_SUFFIX = ' -->';
23
- // New constants for subagent context protocol
24
- static SUBAGENT_PROTOCOL_HEADER = '## Subagent Context Protocol';
25
- static AGENT_USAGE_HEADER = '### When to Use Agents';
26
- static REPO_CONTEXT_HEADER = '### Repository Context';
27
- static USAGE_TEMPLATE_HEADER = '### Usage Template';
28
- static EXAMPLE_HEADER = '### Example';
29
- static ANALYSIS_IGNORE_PREFIX = '<!-- TB_ANALYSIS-IGNORE:';
30
- static ANALYSIS_IGNORE_SUFFIX = ' -->';
31
- static STATUS_IGNORE_PREFIX = '<!-- TB-STATUS-IGNORE:';
32
- static STATUS_IGNORE_SUFFIX = ' -->';
33
23
  configService;
34
- constructor(context, configService) {
24
+ constructor(context) {
35
25
  this.context = context;
36
- this.configService = configService || new ConfigService(context);
37
- }
38
- /**
39
- * Write the repository block to context file with phase-based agent structure
40
- */
41
- async writeRepoBlockWithPhases(analysis, agentResponse, contextFilePath = 'CLAUDE.md', version) {
42
- const repoRoot = getRepoRoot();
43
- const fullPath = path.join(repoRoot, contextFilePath);
44
- this.context.logger.debug('[RepoService] Writing repo block with phases to context file:', {
45
- repoRoot,
46
- contextFilePath,
47
- fullPath,
48
- agentCount: agentResponse?.agents?.length || 0
49
- });
50
- // Build the complete repo block with phase-based formatting
51
- const repoBlock = await this.formatRepoBlockWithPhases(analysis, agentResponse, version);
52
- // Read existing content
53
- let existingContent = '';
54
- try {
55
- existingContent = await fs.readFile(fullPath, 'utf-8');
56
- }
57
- catch {
58
- // File doesn't exist, that's ok
59
- }
60
- // Update or append tiny-brain section
61
- const startMarker = RepoService.REPO_BLOCK_START;
62
- const endMarker = RepoService.REPO_BLOCK_END;
63
- if (existingContent.includes(startMarker)) {
64
- // Replace existing tiny-brain section
65
- const startIndex = existingContent.indexOf(startMarker);
66
- const endIndex = existingContent.indexOf(endMarker);
67
- if (endIndex > -1) {
68
- // Replace from start to end marker (inclusive)
69
- const afterEnd = existingContent.substring(endIndex + endMarker.length);
70
- existingContent = existingContent.substring(0, startIndex) +
71
- repoBlock +
72
- afterEnd;
73
- }
74
- else {
75
- // No end marker found, replace to next section or end of file
76
- const nextSectionIndex = existingContent.indexOf('\n## ', startIndex + 1);
77
- if (nextSectionIndex > -1) {
78
- existingContent = existingContent.substring(0, startIndex) +
79
- repoBlock +
80
- existingContent.substring(nextSectionIndex);
81
- }
82
- else {
83
- existingContent = existingContent.substring(0, startIndex) + repoBlock;
84
- }
85
- }
86
- }
87
- else {
88
- // Append tiny-brain section
89
- existingContent = existingContent.trimEnd() + '\n\n' + repoBlock;
90
- }
91
- // Ensure directory exists and write file
92
- await fs.mkdir(path.dirname(fullPath), { recursive: true });
93
- await fs.writeFile(fullPath, existingContent, 'utf-8');
94
- this.context.logger.info(`Updated repo block with ${agentResponse?.agents?.length || 0} agents in phase structure`);
95
- // Return the formatted block (without the extra newline at the end)
96
- return repoBlock.trimEnd();
26
+ // Ensure repositoryRoot is set for ConfigService to load repo config
27
+ const contextWithRepo = {
28
+ ...context,
29
+ repositoryRoot: context.repositoryRoot || this.getRepositoryRoot(),
30
+ };
31
+ this.configService = new ConfigService(contextWithRepo);
97
32
  }
98
33
  /**
99
- * Write the repository block to context file and return the formatted block (legacy compact format)
34
+ * Get repository root using the repo-utils helper
35
+ * This is used as fallback when context.repositoryRoot is not set
100
36
  */
101
- async writeRepoBlockToContextFile(analysis, agents, contextFilePath = 'CLAUDE.md', version) {
102
- const repoRoot = getRepoRoot();
103
- const fullPath = path.join(repoRoot, contextFilePath);
104
- this.context.logger.debug('[RepoService] Writing repo block to context file:', {
105
- repoRoot,
106
- contextFilePath,
107
- fullPath
108
- });
109
- // Build the complete repo block
110
- const repoBlock = await this.formatRepoBlock(analysis, agents, version);
111
- // Read existing content
112
- let existingContent = '';
37
+ getRepositoryRoot() {
113
38
  try {
114
- existingContent = await fs.readFile(fullPath, 'utf-8');
39
+ return getRepoRoot();
115
40
  }
116
41
  catch {
117
- // File doesn't exist, that's ok
118
- }
119
- // Update or append tiny-brain section
120
- const startMarker = RepoService.REPO_BLOCK_START;
121
- const endMarker = RepoService.REPO_BLOCK_END;
122
- if (existingContent.includes(startMarker)) {
123
- // Replace existing tiny-brain section
124
- const startIndex = existingContent.indexOf(startMarker);
125
- const endIndex = existingContent.indexOf(endMarker);
126
- if (endIndex > -1) {
127
- // Replace from start to end marker (inclusive)
128
- const afterEnd = existingContent.substring(endIndex + endMarker.length);
129
- existingContent = existingContent.substring(0, startIndex) +
130
- repoBlock +
131
- afterEnd;
132
- }
133
- else {
134
- // No end marker found, replace to next section or end of file
135
- const nextSectionIndex = existingContent.indexOf('\n## ', startIndex + 1);
136
- if (nextSectionIndex > -1) {
137
- existingContent = existingContent.substring(0, startIndex) +
138
- repoBlock +
139
- existingContent.substring(nextSectionIndex);
140
- }
141
- else {
142
- existingContent = existingContent.substring(0, startIndex) + repoBlock;
143
- }
144
- }
42
+ return undefined;
145
43
  }
146
- else {
147
- // Append tiny-brain section
148
- existingContent = existingContent.trimEnd() + '\n\n' + repoBlock;
149
- }
150
- // Ensure directory exists and write file
151
- await fs.mkdir(path.dirname(fullPath), { recursive: true });
152
- await fs.writeFile(fullPath, existingContent, 'utf-8');
153
- this.context.logger.info(`Updated repo block with ${agents.length} agents`);
154
- // Return the formatted block (without the extra newline at the end)
155
- return repoBlock.trimEnd();
156
44
  }
157
45
  /**
158
- * Read the repository block from context file
46
+ * Read the repository block from context file (for legacy migration)
159
47
  */
160
48
  async readRepoBlockFromContextFile(contextFilePath = 'CLAUDE.md') {
161
49
  try {
@@ -188,342 +76,6 @@ export class RepoService {
188
76
  throw error;
189
77
  }
190
78
  }
191
- /**
192
- * Format the repository block with phase-based agent structure
193
- */
194
- async formatRepoBlockWithPhases(analysis, agentResponse, version) {
195
- let block = `${RepoService.REPO_BLOCK_START}\n`;
196
- // Add YAML frontmatter with version if provided
197
- if (version) {
198
- block += '---\n';
199
- block += `version: ${version}\n`;
200
- block += `updated: ${new Date().toISOString()}\n`;
201
- block += '---\n\n';
202
- }
203
- // PRD Workflow section (appears FIRST if PRD exists)
204
- const prdWorkflow = await this.formatPRDWorkflowSection();
205
- if (prdWorkflow) {
206
- block += prdWorkflow;
207
- }
208
- // ADR section (if exists)
209
- const adrSection = this.formatADRSection();
210
- if (adrSection) {
211
- block += adrSection;
212
- }
213
- block += `${RepoService.SUBAGENT_PROTOCOL_HEADER}\n\n`;
214
- // When to use agents - phase-based format
215
- block += `${RepoService.AGENT_USAGE_HEADER}\n\n`;
216
- block += this.formatAgentsWithPhases(agentResponse);
217
- // Repository context - YAML format
218
- block += `${RepoService.REPO_CONTEXT_HEADER}\n`;
219
- block += this.formatRepositoryContextYaml(analysis);
220
- // Documentation templates section (conditional)
221
- const docTemplatesSection = this.formatDocumentationTemplatesSection();
222
- if (docTemplatesSection) {
223
- block += docTemplatesSection;
224
- }
225
- // Usage template
226
- block += `\n${RepoService.USAGE_TEMPLATE_HEADER}\n\n`;
227
- block += '```\n';
228
- block += 'Repository Context: [paste the yaml above]\n\n';
229
- block += 'AGENT: [choose from list above]\n';
230
- block += 'TASK: [specific task description]\n';
231
- block += 'CONSTRAINTS: [requirements/limitations]\n';
232
- block += 'EXPECTED OUTPUT: [what you need back]\n';
233
- block += '```\n';
234
- // Example
235
- block += `\n${RepoService.EXAMPLE_HEADER}\n`;
236
- block += '```\n';
237
- block += 'Repository Context: [repo context yaml]\n\n';
238
- block += 'AGENT: component-developer\n';
239
- block += 'TASK: Create a UserProfile component with avatar, name, and email\n';
240
- block += 'CONSTRAINTS: Must be accessible, use TypeScript, follow TDD\n';
241
- block += 'EXPECTED OUTPUT: Component file with tests and usage example\n';
242
- block += '```\n';
243
- // TB_ANALYSIS at the end with IGNORE suffix for clarity
244
- block += `\n${RepoService.ANALYSIS_IGNORE_PREFIX}${JSON.stringify(analysis)}${RepoService.ANALYSIS_IGNORE_SUFFIX}\n`;
245
- block += `${RepoService.REPO_BLOCK_END}\n`;
246
- return block;
247
- }
248
- /**
249
- * Format the complete repository block with simplified structure
250
- */
251
- async formatRepoBlock(analysis, agents, version) {
252
- let block = `${RepoService.REPO_BLOCK_START}\n`;
253
- // Add YAML frontmatter with version if provided
254
- if (version) {
255
- block += '---\n';
256
- block += `version: ${version}\n`;
257
- block += `updated: ${new Date().toISOString()}\n`;
258
- block += '---\n\n';
259
- }
260
- // PRD Workflow section (includes commit format, TDD workflow if enabled)
261
- const prdWorkflow = await this.formatPRDWorkflowSection();
262
- if (prdWorkflow) {
263
- block += prdWorkflow;
264
- }
265
- // Tech Stack & Agents section (simplified table format)
266
- block += '## Tech Stack & Agents\n\n';
267
- block += this.formatAgentTable(agents, analysis);
268
- // Repository context - YAML format
269
- block += '\n## Repository Context\n';
270
- block += this.formatRepositoryContextYaml(analysis);
271
- // Documentation templates section (conditional)
272
- const docTemplatesSection = this.formatDocumentationTemplatesSection();
273
- if (docTemplatesSection) {
274
- block += docTemplatesSection;
275
- }
276
- // TB_ANALYSIS at the end with IGNORE suffix for clarity
277
- block += `\n${RepoService.ANALYSIS_IGNORE_PREFIX}${JSON.stringify(analysis)}${RepoService.ANALYSIS_IGNORE_SUFFIX}\n`;
278
- block += `${RepoService.REPO_BLOCK_END}\n`;
279
- return block;
280
- }
281
- /**
282
- * Parse the repository block from context file into structured data
283
- */
284
- async parseRepoBlock(contextFilePath = 'CLAUDE.md') {
285
- const repoBlockMarkdown = await this.readRepoBlockFromContextFile(contextFilePath);
286
- if (!repoBlockMarkdown)
287
- return null;
288
- // Extract JSON from comment
289
- const analysisRegex = new RegExp(`${RepoService.ANALYSIS_MARKER_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(.*?)${RepoService.ANALYSIS_MARKER_SUFFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 's');
290
- const jsonMatch = repoBlockMarkdown.match(analysisRegex);
291
- const rawAnalysis = jsonMatch ? JSON.parse(jsonMatch[1]) : null;
292
- // Extract analysis section
293
- const analysisMatch = repoBlockMarkdown.match(/### repo analysis\n([\s\S]*?)(?=\n###|\n##|$)/);
294
- const analysis = analysisMatch ? analysisMatch[1].trim() : '';
295
- // Extract documentation section (optional)
296
- const docMatch = repoBlockMarkdown.match(/#### documentation\n([\s\S]*?)(?=\n###|\n##|$)/);
297
- const documentation = docMatch ? docMatch[1].trim() : null;
298
- // Extract agent usage section
299
- const agentMatch = repoBlockMarkdown.match(/### agent usage\n([\s\S]*?)(?=\n##|$)/);
300
- const agentUsage = agentMatch ? agentMatch[1].trim() : 'No agents available for this repository.';
301
- return { rawAnalysis, analysis, documentation, agentUsage };
302
- }
303
- /**
304
- * Format agents with phase-based structure
305
- */
306
- formatAgentsWithPhases(agentResponse) {
307
- // Handle empty response
308
- if (!agentResponse?.agentUsage?.phases || Object.keys(agentResponse.agentUsage.phases).length === 0) {
309
- return 'No agents available for this repository.\n';
310
- }
311
- let output = '';
312
- const phases = agentResponse.agentUsage.phases;
313
- const phaseOrder = ['ideation', 'development', 'testing', 'operations'];
314
- const phaseNames = {
315
- ideation: 'Phase 1: Ideation',
316
- development: 'Phase 2: Development',
317
- testing: 'Phase 3: Testing & Quality',
318
- operations: 'Phase 4: Operations & Analysis'
319
- };
320
- // Format each phase
321
- phaseOrder.forEach((phaseKey) => {
322
- const phase = phases[phaseKey];
323
- if (!phase)
324
- return;
325
- output += `#### ${phaseNames[phaseKey]}\n`;
326
- // List agents
327
- if (phase.agents && phase.agents.length > 0) {
328
- phase.agents.forEach((agentName) => {
329
- const agent = agentResponse.agents?.find((a) => a.name === agentName);
330
- if (agent) {
331
- output += `- \`${agent.name}\` - ${agent.details.description}\n`;
332
- }
333
- else {
334
- output += `- \`${agentName}\`\n`;
335
- }
336
- });
337
- output += '\n';
338
- }
339
- // Phase handoffs
340
- if (phase.handoffs && phase.handoffs.length > 0) {
341
- const phaseName = phaseNames[phaseKey] || phaseKey;
342
- const phaseTitle = phaseName.includes(':') ? phaseName.split(':')[0] : phaseName;
343
- output += `**${phaseTitle} Handoffs:**\n`;
344
- phase.handoffs.forEach((handoff) => {
345
- if (typeof handoff === 'string') {
346
- // Handle string format: "from → to: description"
347
- output += `- \`${handoff.replace(' → ', '` → `').replace(': ', '`: ')}\n`;
348
- }
349
- else {
350
- // Handle object format
351
- output += `- \`${handoff.from}\` → \`${handoff.to}\``;
352
- if (handoff.description) {
353
- output += `: ${handoff.description}`;
354
- }
355
- output += '\n';
356
- }
357
- });
358
- output += '\n';
359
- }
360
- });
361
- // Cross-phase handoffs
362
- if (agentResponse.agentUsage.crossPhaseHandoffs && agentResponse.agentUsage.crossPhaseHandoffs.length > 0) {
363
- output += '### Cross-Phase Handoffs\n';
364
- agentResponse.agentUsage.crossPhaseHandoffs.forEach((handoff) => {
365
- const fromPhaseName = phaseNames[handoff.from] || handoff.from;
366
- const toPhaseName = phaseNames[handoff.to] || handoff.to;
367
- const fromName = fromPhaseName.includes(':') ? fromPhaseName.split(':')[1]?.trim() : fromPhaseName;
368
- const toName = toPhaseName.includes(':') ? toPhaseName.split(':')[1]?.trim() : toPhaseName;
369
- output += `**${fromName.charAt(0).toUpperCase() + fromName.slice(1)} → ${toName.charAt(0).toUpperCase() + toName.slice(1)}:**\n`;
370
- output += `- ${handoff.description}\n\n`;
371
- });
372
- }
373
- // Prerequisites
374
- if (agentResponse.agentUsage.prerequisites && Object.keys(agentResponse.agentUsage.prerequisites).length > 0) {
375
- output += '### Agent Prerequisites\n';
376
- Object.entries(agentResponse.agentUsage.prerequisites).forEach(([agent, prerequisites]) => {
377
- // Prerequisites can be either a string or array of strings
378
- if (Array.isArray(prerequisites)) {
379
- if (prerequisites.length > 0) {
380
- const formattedPrereqs = prerequisites.map(prereq => `\`${prereq}\``).join(', ');
381
- output += `- \`${agent}\`: ${formattedPrereqs}\n`;
382
- }
383
- }
384
- else if (typeof prerequisites === 'string' && prerequisites.trim()) {
385
- // Prerequisites is a string description - wrap agent names in backticks
386
- const formattedPrerequisites = prerequisites.replace(/([a-z-]+(?:-[a-z]+)*)/g, (match) => {
387
- // Only wrap strings that look like agent names (hyphenated words)
388
- if (match.includes('-') || ['system', 'feature', 'component', 'functional', 'tdd', 'refactoring', 'metrics', 'root'].some(word => match.includes(word))) {
389
- return `\`${match}\``;
390
- }
391
- return match;
392
- });
393
- output += `- \`${agent}\`: ${formattedPrerequisites}\n`;
394
- }
395
- });
396
- output += '\n';
397
- }
398
- // Invocation template
399
- if (agentResponse.agentUsage.invocationTemplate?.structure) {
400
- output += '### Agent Invocation Template\n';
401
- output += '```\n';
402
- output += agentResponse.agentUsage.invocationTemplate.structure.replace(/\\n/g, '\n');
403
- output += '\n```\n';
404
- }
405
- return output;
406
- }
407
- /**
408
- * Format agents as a simple table with tech-stack specific "Use for" descriptions
409
- */
410
- formatAgentTable(agents, analysis) {
411
- if (agents.length === 0) {
412
- return 'No agents installed for this repository.\n';
413
- }
414
- // Generate tech stack summary
415
- const techParts = [];
416
- if (analysis.languages.length > 0) {
417
- techParts.push(analysis.languages.join('/'));
418
- }
419
- if (analysis.frameworks.length > 0) {
420
- techParts.push(analysis.frameworks.join(', '));
421
- }
422
- const testingTool = analysis.testingTools[0];
423
- if (testingTool) {
424
- techParts.push(`${testingTool} testing`);
425
- }
426
- let output = '';
427
- if (techParts.length > 0) {
428
- output += `${techParts.join(' repository with ')}.\n\n`;
429
- }
430
- // Build agent table
431
- output += '| Agent | Use for |\n';
432
- output += '|-------|--------|\n';
433
- agents.forEach(agent => {
434
- // Use TBR-provided usage if available, otherwise generic fallback
435
- const useCase = agent.usage || this.generateAgentUseCase(agent);
436
- output += `| \`${agent.name}\` | ${useCase} |\n`;
437
- });
438
- output += '\nSkills automatically select appropriate agents. Use Task tool for direct invocation.\n';
439
- return output;
440
- }
441
- /**
442
- * Generate generic "Use for" description based on agent name
443
- * This is a fallback when TBR doesn't provide contextual usage
444
- */
445
- generateAgentUseCase(agent) {
446
- const name = agent.name.toLowerCase();
447
- // Generic fallback based on agent name
448
- if (name.includes('typescript'))
449
- return 'TypeScript files';
450
- if (name.includes('react'))
451
- return 'React components';
452
- if (name.includes('component'))
453
- return 'UI components';
454
- if (name.includes('tdd') || name.includes('test'))
455
- return 'Test files';
456
- if (name.includes('backend'))
457
- return 'Backend code';
458
- if (name.includes('frontend'))
459
- return 'Frontend code';
460
- if (name.includes('architect'))
461
- return 'Architecture decisions';
462
- if (name.includes('refactor'))
463
- return 'Refactoring';
464
- if (name.includes('security'))
465
- return 'Security';
466
- if (name.includes('performance'))
467
- return 'Performance';
468
- if (name.includes('quality'))
469
- return 'Code review';
470
- if (name.includes('validator'))
471
- return 'Validation';
472
- // Fallback to cleaned-up agent name
473
- return agent.name.replace(/-/g, ' ');
474
- }
475
- /**
476
- * Format repository context as clean YAML
477
- */
478
- formatRepositoryContextYaml(analysis) {
479
- const repoRoot = getRepoRoot();
480
- let yaml = '```yaml\n';
481
- // Analysis metadata
482
- yaml += `Analysis Date: ${new Date().toISOString()}\n`;
483
- yaml += `Analysis Version: ${this.getAnalysisVersion()}\n`;
484
- yaml += `Languages: ${analysis.languages.join(', ')}\n`;
485
- yaml += `Frameworks: ${analysis.frameworks.join(', ')}\n`;
486
- yaml += `Build Tools: ${analysis.buildTools.join(', ')}\n`;
487
- yaml += `Testing Tools: ${analysis.testingTools.join(', ')}\n`;
488
- if (analysis.hasTests) {
489
- yaml += `Tests: ${analysis.testFileCount} files (patterns: ${analysis.testPatterns.join(', ')})\n`;
490
- }
491
- if (analysis.isPolyglot) {
492
- yaml += `Polyglot: Yes (primary: ${analysis.primaryLanguage})\n`;
493
- }
494
- yaml += `Documentation Pattern: ${analysis.documentationPattern}\n`;
495
- // Build documentation locations including PRD and ADR paths if they exist
496
- const docLocations = analysis.documentationLocations?.map(loc => path.isAbsolute(loc) ? loc : path.join(repoRoot, loc)) || [];
497
- // Add PRD path if it exists
498
- const prdPath = path.join(repoRoot, 'docs', 'prd');
499
- if (existsSync(prdPath) && !docLocations.includes(prdPath)) {
500
- docLocations.push(prdPath);
501
- }
502
- // Add ADR path if it exists
503
- const adrPath = path.join(repoRoot, 'docs', 'adr');
504
- if (existsSync(adrPath) && !docLocations.includes(adrPath)) {
505
- docLocations.push(adrPath);
506
- }
507
- yaml += `Documentation Locations: ${docLocations.join(', ')}\n`;
508
- yaml += '```\n';
509
- return yaml;
510
- }
511
- /**
512
- * Get the analysis version from package.json
513
- */
514
- getAnalysisVersion() {
515
- try {
516
- // Read package.json from the tiny-brain-local package
517
- const currentFilePath = fileURLToPath(import.meta.url);
518
- const packageJsonPath = path.join(path.dirname(currentFilePath), '../../package.json');
519
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
520
- return packageJson.version || '0.0.0';
521
- }
522
- catch (error) {
523
- this.context.logger.warn('Failed to read package version:', error);
524
- return '0.0.0';
525
- }
526
- }
527
79
  /**
528
80
  * Extract version from tiny-brain block frontmatter
529
81
  * @param content - The content containing the tiny-brain block
@@ -535,106 +87,6 @@ export class RepoService {
535
87
  const match = content.match(versionRegex);
536
88
  return match ? match[1] : null;
537
89
  }
538
- /**
539
- * Format documentation templates section for PRDs and ADRs
540
- * Only includes sections for directories that exist
541
- */
542
- formatDocumentationTemplatesSection() {
543
- const repoRoot = getRepoRoot();
544
- const prdPath = path.join(repoRoot, 'docs', 'prd');
545
- const adrPath = path.join(repoRoot, 'docs', 'adr');
546
- const hasPRD = existsSync(prdPath);
547
- const hasADR = existsSync(adrPath);
548
- // If neither exists, return empty string
549
- if (!hasPRD && !hasADR) {
550
- return '';
551
- }
552
- let section = '\n### Documentation Templates\n\n';
553
- const skillsPath = path.join(repoRoot, '.claude', 'skills');
554
- // Add PRD instructions if directory exists
555
- if (hasPRD) {
556
- section += '**PRD & Feature Creation (Model-Invoked Skills):**\n';
557
- section += `- Skills are automatically triggered based on your intent - describe what you want to do\n`;
558
- section += `- "I want to plan a new feature" → triggers plan skill\n`;
559
- section += `- "I want to add a feature to the PRD" → triggers feature skill\n`;
560
- section += `- "I want to track a bug fix" → triggers fix skill\n`;
561
- section += `- Skills: \`.claude/skills/plan/\`, \`.claude/skills/feature/\`, \`.claude/skills/fix/\`\n`;
562
- section += `- Check available skills: ask "What Skills are available?"\n\n`;
563
- }
564
- // Add ADR instructions if directory exists
565
- if (hasADR) {
566
- const adrSkillPath = path.join(skillsPath, 'adr');
567
- section += '**ADR Creation:**\n';
568
- section += `- Use \`/adr\` skill to create Architecture Decision Records\n`;
569
- section += `- Skill located at \`${adrSkillPath}/\`\n`;
570
- section += `- Template at \`${adrSkillPath}/templates/adr-template.md\`\n`;
571
- }
572
- return section;
573
- }
574
- /**
575
- * Extract TB_ANALYSIS from content with new IGNORE format
576
- */
577
- extractTBAnalysis(content) {
578
- const regex = new RegExp(`${RepoService.ANALYSIS_IGNORE_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(.*?)${RepoService.ANALYSIS_IGNORE_SUFFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 's');
579
- const match = content.match(regex);
580
- if (match && match[1]) {
581
- try {
582
- return JSON.parse(match[1]);
583
- }
584
- catch {
585
- return null;
586
- }
587
- }
588
- return null;
589
- }
590
- /**
591
- * Extract TB-STATUS from content with backwards compatibility for TB_ANALYSIS-IGNORE
592
- */
593
- extractTBStatus(content) {
594
- // First try TB-STATUS-IGNORE (new format)
595
- const statusRegex = new RegExp(`${RepoService.STATUS_IGNORE_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(.*?)${RepoService.STATUS_IGNORE_SUFFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 's');
596
- const statusMatch = content.match(statusRegex);
597
- if (statusMatch && statusMatch[1]) {
598
- try {
599
- return JSON.parse(statusMatch[1]);
600
- }
601
- catch {
602
- return null;
603
- }
604
- }
605
- // Fall back to TB_ANALYSIS-IGNORE (legacy format)
606
- const analysisRegex = new RegExp(`${RepoService.ANALYSIS_IGNORE_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(.*?)${RepoService.ANALYSIS_IGNORE_SUFFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 's');
607
- const analysisMatch = content.match(analysisRegex);
608
- if (analysisMatch && analysisMatch[1]) {
609
- try {
610
- const analysis = JSON.parse(analysisMatch[1]);
611
- // Convert RepoAnalysis to TBStatus format (no additional fields for legacy)
612
- return analysis;
613
- }
614
- catch {
615
- return null;
616
- }
617
- }
618
- return null;
619
- }
620
- /**
621
- * Generate TB-STATUS-IGNORE format string with current timestamp
622
- */
623
- updateTBStatus(analysis, installedAgents) {
624
- const tbStatus = {
625
- ...analysis,
626
- lastUpdated: new Date().toISOString(),
627
- ...(installedAgents && { installedAgents })
628
- };
629
- return `${RepoService.STATUS_IGNORE_PREFIX}${JSON.stringify(tbStatus)}${RepoService.STATUS_IGNORE_SUFFIX}`;
630
- }
631
- /**
632
- * Check if repository is initialized (has TB-STATUS-IGNORE or TB_ANALYSIS-IGNORE)
633
- */
634
- isRepoInitialized(content) {
635
- const tbStatus = this.extractTBStatus(content);
636
- return tbStatus !== null;
637
- }
638
90
  /**
639
91
  * Check if we're in a repository context
640
92
  */
@@ -649,562 +101,373 @@ export class RepoService {
649
101
  }
650
102
  }
651
103
  /**
652
- * Check if repository is initialized (has TB block in context file)
104
+ * Check if repository is initialized (has .tiny-brain/analysis.json)
105
+ * This is the new initialization check - no longer checks CLAUDE.md
653
106
  */
654
107
  async isRepositoryInitialized() {
655
108
  if (!this.isInRepository()) {
656
- return false; // Not in repo, so not initialized
109
+ return false;
657
110
  }
658
111
  try {
659
- const content = await this.readRepoBlockFromContextFile();
660
- return content ? this.isRepoInitialized(content) : false;
112
+ const repoRoot = getRepoRoot();
113
+ const analysisPath = path.join(repoRoot, '.tiny-brain', 'analysis.json');
114
+ return existsSync(analysisPath);
661
115
  }
662
116
  catch {
663
117
  return false;
664
118
  }
665
119
  }
666
120
  /**
667
- * Compare two tech stacks and return differences
668
- */
669
- compareTechStack(previousAnalysis, currentAnalysis) {
670
- return detectTechStackChanges({
671
- languages: previousAnalysis.languages || [],
672
- frameworks: previousAnalysis.frameworks || [],
673
- buildTools: previousAnalysis.buildTools || [],
674
- testingTools: previousAnalysis.testingTools || []
675
- }, {
676
- languages: currentAnalysis.languages || [],
677
- frameworks: currentAnalysis.frameworks || [],
678
- buildTools: currentAnalysis.buildTools || [],
679
- testingTools: currentAnalysis.testingTools || []
680
- });
681
- }
682
- /**
683
- * Migrate legacy TB_ANALYSIS-IGNORE format to new TB-STATUS-IGNORE format
684
- */
685
- migrateToNewFormat(existingContent, newAnalysis) {
686
- // Generate new TB-STATUS-IGNORE format
687
- const newStatusIgnore = this.updateTBStatus(newAnalysis);
688
- // Replace TB_ANALYSIS-IGNORE with TB-STATUS-IGNORE
689
- if (existingContent.includes(RepoService.ANALYSIS_IGNORE_PREFIX)) {
690
- const analysisRegex = new RegExp(`${RepoService.ANALYSIS_IGNORE_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*?${RepoService.ANALYSIS_IGNORE_SUFFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 's');
691
- return existingContent.replace(analysisRegex, newStatusIgnore);
692
- }
693
- // Replace existing TB-STATUS-IGNORE with updated version
694
- if (existingContent.includes(RepoService.STATUS_IGNORE_PREFIX)) {
695
- const statusRegex = new RegExp(`${RepoService.STATUS_IGNORE_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*?${RepoService.STATUS_IGNORE_SUFFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 's');
696
- return existingContent.replace(statusRegex, newStatusIgnore);
697
- }
698
- // No existing format found, return original content
699
- return existingContent;
700
- }
701
- /**
702
- * Update agent usage section in CLAUDE.md with phase-based structure
121
+ * Get the analysis version from package.json
703
122
  */
704
- async updateAgentUsageSection(installedAgents, phaseStructure, contextFilePath = 'CLAUDE.md') {
123
+ getAnalysisVersion() {
705
124
  try {
706
- const repoRoot = getRepoRoot();
707
- const fullPath = path.join(repoRoot, contextFilePath);
708
- // Read existing content or create new
709
- let existingContent = '';
710
- try {
711
- existingContent = await fs.readFile(fullPath, 'utf-8');
712
- }
713
- catch {
714
- // File doesn't exist, create with just the tiny-brain section
715
- existingContent = '';
716
- }
717
- // Generate the agent usage content
718
- const agentUsageContent = this.formatPhaseBasedAgentUsage(installedAgents, phaseStructure);
719
- // Update or create the tiny-brain section
720
- const updatedContent = this.updateTinyBrainSection(existingContent, agentUsageContent);
721
- // Ensure directory exists and write file
722
- await fs.mkdir(path.dirname(fullPath), { recursive: true });
723
- await fs.writeFile(fullPath, updatedContent, 'utf-8');
724
- this.context.logger.info(`Updated ${contextFilePath} with ${installedAgents.length} agents in phase structure`);
125
+ // Read package.json from the tiny-brain-local package
126
+ const currentFilePath = fileURLToPath(import.meta.url);
127
+ const packageJsonPath = path.join(path.dirname(currentFilePath), '../../package.json');
128
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
129
+ return packageJson.version || '0.0.0';
725
130
  }
726
131
  catch (error) {
727
- this.context.logger.error(`Failed to update agent usage section in ${contextFilePath}:`, error);
728
- throw error;
729
- }
730
- }
731
- /**
732
- * Generate phase structure from installed agents
733
- */
734
- generatePhaseStructure(installedAgents) {
735
- const structure = {
736
- phases: {},
737
- crossPhaseHandoffs: [],
738
- prerequisites: {}
739
- };
740
- // Agent categorization
741
- const ideationAgents = [];
742
- const developmentAgents = [];
743
- const testingAgents = [];
744
- const operationsAgents = [];
745
- // Categorize agents
746
- installedAgents.forEach(agent => {
747
- const name = agent.name.toLowerCase();
748
- if (name.includes('prd') || name.includes('researcher') ||
749
- name.includes('architect') || name.includes('planner')) {
750
- ideationAgents.push(agent.name);
751
- }
752
- else if (name.includes('test') || name.includes('validator') ||
753
- name.includes('tdd') || name.includes('refactor')) {
754
- testingAgents.push(agent.name);
755
- }
756
- else if (name.includes('metrics') || name.includes('analyst') ||
757
- name.includes('collector') || name.includes('monitor')) {
758
- operationsAgents.push(agent.name);
759
- }
760
- else {
761
- // Default to development
762
- developmentAgents.push(agent.name);
763
- }
764
- });
765
- // Build phase structure
766
- if (ideationAgents.length > 0) {
767
- structure.phases.ideation = {
768
- agents: ideationAgents,
769
- handoffs: this.generatePhaseHandoffs(ideationAgents)
770
- };
771
- }
772
- if (developmentAgents.length > 0) {
773
- structure.phases.development = {
774
- agents: developmentAgents,
775
- handoffs: this.generatePhaseHandoffs(developmentAgents)
776
- };
777
- }
778
- if (testingAgents.length > 0) {
779
- structure.phases.testing = {
780
- agents: testingAgents,
781
- handoffs: this.generatePhaseHandoffs(testingAgents)
782
- };
783
- }
784
- if (operationsAgents.length > 0) {
785
- structure.phases.operations = {
786
- agents: operationsAgents,
787
- handoffs: this.generatePhaseHandoffs(operationsAgents)
788
- };
132
+ this.context.logger.warn('Failed to read package version:', error);
133
+ return '0.0.0';
789
134
  }
790
- // Generate cross-phase handoffs
791
- structure.crossPhaseHandoffs = this.generateCrossPhaseHandoffs(structure.phases);
792
- // Generate prerequisites
793
- structure.prerequisites = this.generatePrerequisites(installedAgents);
794
- return structure;
795
135
  }
796
136
  /**
797
- * Create or update TB-STATUS-IGNORE with analysis and installed agents
137
+ * Write workflow sections to CLAUDE.md based on config flags
138
+ *
139
+ * Writes the following sections when their respective flags are enabled:
140
+ * - Commit Message Format (when SDD enabled)
141
+ * - TDD Workflow (when TDD enabled)
142
+ * - Progress Tracking Auto-Commit Configuration (when SDD enabled)
143
+ * - Operational Tracking Directory (when SDD enabled)
798
144
  */
799
- async createOrUpdateTBStatus(analysis, installedAgents, contextFilePath = 'CLAUDE.md') {
800
- try {
801
- const repoRoot = getRepoRoot();
802
- const fullPath = path.join(repoRoot, contextFilePath);
803
- // Read existing content
804
- let existingContent = '';
805
- try {
806
- existingContent = await fs.readFile(fullPath, 'utf-8');
807
- }
808
- catch {
809
- // File doesn't exist
810
- }
811
- // Create TB status data
812
- const tbStatus = {
813
- ...analysis,
814
- lastUpdated: new Date().toISOString(),
815
- installedAgents
816
- };
817
- // Generate TB-STATUS-IGNORE format
818
- const statusIgnore = `${RepoService.STATUS_IGNORE_PREFIX}${JSON.stringify(tbStatus)}${RepoService.STATUS_IGNORE_SUFFIX}`;
819
- // Update the status in the content
820
- let updatedContent = existingContent;
821
- // Replace existing TB-STATUS-IGNORE or TB_ANALYSIS-IGNORE
822
- if (existingContent.includes(RepoService.STATUS_IGNORE_PREFIX)) {
823
- const statusRegex = new RegExp(`${RepoService.STATUS_IGNORE_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*?${RepoService.STATUS_IGNORE_SUFFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 's');
824
- updatedContent = existingContent.replace(statusRegex, statusIgnore);
825
- }
826
- else if (existingContent.includes(RepoService.ANALYSIS_IGNORE_PREFIX)) {
827
- const analysisRegex = new RegExp(`${RepoService.ANALYSIS_IGNORE_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*?${RepoService.ANALYSIS_IGNORE_SUFFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 's');
828
- updatedContent = existingContent.replace(analysisRegex, statusIgnore);
829
- }
830
- else {
831
- // Add to tiny-brain section or create it
832
- updatedContent = this.addStatusToContent(existingContent, statusIgnore);
833
- }
834
- // Write updated content
835
- await fs.mkdir(path.dirname(fullPath), { recursive: true });
836
- await fs.writeFile(fullPath, updatedContent, 'utf-8');
837
- this.context.logger.info(`Updated TB-STATUS-IGNORE with ${installedAgents.length} agents`);
838
- }
839
- catch (error) {
840
- this.context.logger.error('Failed to create or update TB-STATUS-IGNORE:', error);
841
- throw error;
145
+ async writeWorkflowSections(options = {}) {
146
+ const { contextPath = 'CLAUDE.md' } = options;
147
+ // Check config flags
148
+ const enableSDD = await this.configService.isSDDEnabled();
149
+ const enableTDD = await this.configService.isTDDEnabled();
150
+ // If nothing is enabled, don't write anything
151
+ if (!enableSDD && !enableTDD) {
152
+ return;
842
153
  }
843
- }
844
- /**
845
- * Migrate legacy TB_ANALYSIS-IGNORE to TB-STATUS-IGNORE format
846
- */
847
- async migrateTBAnalysisToTBStatus(existingContent, installedAgents, contextFilePath = 'CLAUDE.md') {
848
- try {
849
- // Extract existing analysis data
850
- const existingAnalysis = this.extractTBAnalysis(existingContent);
851
- if (!existingAnalysis) {
852
- this.context.logger.warn('No TB_ANALYSIS-IGNORE found to migrate');
853
- return;
854
- }
855
- // Create TB status with preserved analysis data and new agents
856
- const tbStatus = {
857
- ...existingAnalysis,
858
- lastUpdated: new Date().toISOString(),
859
- installedAgents
860
- };
861
- // Generate new TB-STATUS-IGNORE
862
- const statusIgnore = `${RepoService.STATUS_IGNORE_PREFIX}${JSON.stringify(tbStatus)}${RepoService.STATUS_IGNORE_SUFFIX}`;
863
- // Replace TB_ANALYSIS-IGNORE with TB-STATUS-IGNORE
864
- const analysisRegex = new RegExp(`${RepoService.ANALYSIS_IGNORE_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*?${RepoService.ANALYSIS_IGNORE_SUFFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 's');
865
- const updatedContent = existingContent.replace(analysisRegex, statusIgnore);
866
- // Write migrated content
867
- const repoRoot = getRepoRoot();
868
- const fullPath = path.join(repoRoot, contextFilePath);
869
- await fs.writeFile(fullPath, updatedContent, 'utf-8');
870
- this.context.logger.info('Successfully migrated TB_ANALYSIS-IGNORE to TB-STATUS-IGNORE format');
154
+ // Build the workflow sections content
155
+ let workflowContent = '';
156
+ // Commit Message Format (when SDD enabled)
157
+ if (enableSDD) {
158
+ workflowContent += this.formatCommitMessageSection();
871
159
  }
872
- catch (error) {
873
- this.context.logger.error('Failed to migrate TB_ANALYSIS-IGNORE:', error);
874
- throw error;
160
+ // TDD Workflow (when TDD enabled)
161
+ if (enableTDD) {
162
+ workflowContent += this.formatTDDWorkflowSection();
875
163
  }
876
- }
877
- /**
878
- * Get TB status from CLAUDE.md with fallback to legacy format
879
- */
880
- async getTBStatus(contextFilePath = 'CLAUDE.md') {
881
- try {
882
- const repoRoot = getRepoRoot();
883
- const fullPath = path.join(repoRoot, contextFilePath);
884
- const content = await fs.readFile(fullPath, 'utf-8');
885
- // Try to extract TB status using existing method
886
- const tbStatus = this.extractTBStatus(content);
887
- if (tbStatus && !tbStatus.installedAgents) {
888
- // Legacy format detected, recommend migration
889
- this.context.logger.info('Legacy TB_ANALYSIS-IGNORE format detected. Consider migrating to TB-STATUS-IGNORE.');
890
- }
891
- return tbStatus;
164
+ // Progress Tracking Auto-Commit Configuration (when SDD enabled)
165
+ if (enableSDD) {
166
+ workflowContent += this.formatProgressTrackingSection();
892
167
  }
893
- catch (error) {
894
- if (error.code === 'ENOENT') {
895
- // File doesn't exist
896
- return null;
897
- }
898
- this.context.logger.warn('Failed to parse TB status:', error);
899
- return null;
168
+ // Operational Tracking Directory (when SDD enabled)
169
+ if (enableSDD) {
170
+ workflowContent += this.formatOperationalTrackingSection();
900
171
  }
901
- }
902
- /**
903
- * Update repository context YAML section with current analysis
904
- */
905
- async updateRepositoryContext(analysis, contextFilePath = 'CLAUDE.md') {
172
+ // Build the complete tiny-brain block
173
+ const repoBlock = `${RepoService.REPO_BLOCK_START}\n\n${workflowContent}${RepoService.REPO_BLOCK_END}\n`;
174
+ // Get full path
175
+ const repoRoot = getRepoRoot();
176
+ const fullPath = path.join(repoRoot, contextPath);
177
+ // Read existing content
178
+ let existingContent = '';
906
179
  try {
907
- const repoRoot = getRepoRoot();
908
- const fullPath = path.join(repoRoot, contextFilePath);
909
- // Read existing content
910
- let existingContent = '';
911
- try {
912
- existingContent = await fs.readFile(fullPath, 'utf-8');
913
- }
914
- catch {
915
- // File doesn't exist
916
- return;
917
- }
918
- // Generate new YAML content
919
- const newYamlContent = this.formatRepositoryContextYaml(analysis);
920
- // Find and replace the Repository Context section
921
- const contextHeaderRegex = /### Repository Context\n```yaml\n.*?\n```/s;
922
- const updatedContent = existingContent.replace(contextHeaderRegex, `### Repository Context\n${newYamlContent}`);
923
- // Write updated content
924
- await fs.writeFile(fullPath, updatedContent, 'utf-8');
925
- this.context.logger.info('Updated repository context YAML section');
926
- }
927
- catch (error) {
928
- this.context.logger.error('Failed to update repository context:', error);
929
- throw error;
930
- }
931
- }
932
- /**
933
- * Format phase-based agent usage content
934
- */
935
- formatPhaseBasedAgentUsage(installedAgents, phaseStructure) {
936
- // Suppress unused parameter warning - installedAgents used implicitly through phaseStructure
937
- void installedAgents;
938
- let content = '### When to Use Agents\n\n';
939
- const phaseNames = {
940
- ideation: 'Phase 1: Ideation',
941
- development: 'Phase 2: Development',
942
- testing: 'Phase 3: Testing & Quality',
943
- operations: 'Phase 4: Operations & Analysis'
944
- };
945
- // Generate phase sections
946
- Object.entries(phaseStructure.phases).forEach(([phaseKey, phase]) => {
947
- const phaseName = phaseNames[phaseKey] || phaseKey;
948
- content += `#### ${phaseName}\n`;
949
- // List agents
950
- phase.agents.forEach(agentName => {
951
- const description = this.getAgentDescription(agentName);
952
- content += `- \`${agentName}\` - ${description}\n`;
953
- });
954
- // Add handoffs if any
955
- if (phase.handoffs.length > 0) {
956
- content += `\n**${phaseName.split(':')[0]} Handoffs:**\n`;
957
- phase.handoffs.forEach(handoff => {
958
- content += `- ${handoff.replace(/(\w+)/g, '`$1`')}\n`;
959
- });
960
- }
961
- content += '\n';
962
- });
963
- // Add cross-phase handoffs
964
- if (phaseStructure.crossPhaseHandoffs.length > 0) {
965
- content += '### Cross-Phase Handoffs\n';
966
- phaseStructure.crossPhaseHandoffs.forEach(handoff => {
967
- const fromPhase = phaseNames[handoff.from] || handoff.from;
968
- const toPhase = phaseNames[handoff.to] || handoff.to;
969
- content += `**${fromPhase.split(':')[1]?.trim()} → ${toPhase.split(':')[1]?.trim()}:**\n`;
970
- content += `- ${handoff.description}\n\n`;
971
- });
180
+ existingContent = await fs.readFile(fullPath, 'utf-8');
972
181
  }
973
- // Add prerequisites
974
- if (Object.keys(phaseStructure.prerequisites).length > 0) {
975
- content += '### Agent Prerequisites\n';
976
- Object.entries(phaseStructure.prerequisites).forEach(([agent, prereqs]) => {
977
- if (Array.isArray(prereqs)) {
978
- const formattedPrereqs = prereqs.map(p => `\`${p}\``).join(', ');
979
- content += `- \`${agent}\`: ${formattedPrereqs}\n`;
980
- }
981
- else if (typeof prereqs === 'string') {
982
- content += `- \`${agent}\`: ${prereqs}\n`;
983
- }
984
- });
182
+ catch {
183
+ // File doesn't exist, that's ok
985
184
  }
986
- return content;
987
- }
988
- /**
989
- * Update tiny-brain section in content with new agent usage
990
- */
991
- updateTinyBrainSection(existingContent, agentUsageContent) {
185
+ // Update or append tiny-brain section
992
186
  const startMarker = RepoService.REPO_BLOCK_START;
993
187
  const endMarker = RepoService.REPO_BLOCK_END;
994
188
  if (existingContent.includes(startMarker)) {
995
189
  // Replace existing tiny-brain section
996
190
  const startIndex = existingContent.indexOf(startMarker);
997
- const endIndex = existingContent.indexOf(endMarker);
191
+ const endIndex = existingContent.indexOf(endMarker, startIndex);
998
192
  if (endIndex > -1) {
999
- // Replace content between markers
1000
- const beforeSection = existingContent.substring(0, startIndex);
1001
- const afterSection = existingContent.substring(endIndex + endMarker.length);
1002
- return beforeSection + this.buildTinyBrainSection(agentUsageContent) + afterSection;
1003
- }
1004
- }
1005
- // Add new tiny-brain section
1006
- return existingContent.trimEnd() + '\n\n' + this.buildTinyBrainSection(agentUsageContent);
1007
- }
1008
- /**
1009
- * Build complete tiny-brain section
1010
- */
1011
- buildTinyBrainSection(agentUsageContent) {
1012
- return `${RepoService.REPO_BLOCK_START}\n` +
1013
- `${RepoService.SUBAGENT_PROTOCOL_HEADER}\n\n` +
1014
- agentUsageContent + '\n' +
1015
- `${RepoService.REPO_BLOCK_END}`;
1016
- }
1017
- /**
1018
- * Add TB status to content (create tiny-brain section if needed)
1019
- */
1020
- addStatusToContent(existingContent, statusIgnore) {
1021
- const startMarker = RepoService.REPO_BLOCK_START;
1022
- const endMarker = RepoService.REPO_BLOCK_END;
1023
- if (existingContent.includes(startMarker) && existingContent.includes(endMarker)) {
1024
- // Add before end marker
1025
- const endIndex = existingContent.indexOf(endMarker);
1026
- return existingContent.substring(0, endIndex) +
1027
- statusIgnore + '\n' +
1028
- existingContent.substring(endIndex);
1029
- }
1030
- else {
1031
- // Create minimal tiny-brain section
1032
- return existingContent.trimEnd() + '\n\n' +
1033
- `${startMarker}\n${statusIgnore}\n${endMarker}\n`;
1034
- }
1035
- }
1036
- /**
1037
- * Generate handoffs for agents within a phase
1038
- */
1039
- generatePhaseHandoffs(agents) {
1040
- const handoffs = [];
1041
- // Generate common handoff patterns
1042
- if (agents.includes('prd-researcher') && agents.includes('system-architect')) {
1043
- handoffs.push('prd-researcher → system-architect: Requirements analysis to system design');
1044
- }
1045
- if (agents.includes('system-architect') && agents.includes('backend-architect')) {
1046
- handoffs.push('system-architect → backend-architect: System design to backend architecture');
1047
- }
1048
- if (agents.includes('feature-developer') && agents.includes('component-developer')) {
1049
- handoffs.push('feature-developer → component-developer: Feature specs to UI implementation');
1050
- }
1051
- if (agents.includes('feature-developer') && agents.includes('typescript-developer')) {
1052
- handoffs.push('feature-developer → typescript-developer: Feature specs to type-safe implementation');
1053
- }
1054
- return handoffs;
1055
- }
1056
- /**
1057
- * Generate cross-phase handoffs based on available phases
1058
- */
1059
- generateCrossPhaseHandoffs(phases) {
1060
- const handoffs = [];
1061
- if (phases.ideation && phases.development) {
1062
- handoffs.push({
1063
- from: 'ideation',
1064
- to: 'development',
1065
- description: 'Architecture outputs → Development agents'
1066
- });
1067
- }
1068
- if (phases.development && phases.testing) {
1069
- handoffs.push({
1070
- from: 'development',
1071
- to: 'testing',
1072
- description: 'Implementation outputs → Testing agents'
1073
- });
1074
- }
1075
- if (phases.testing && phases.operations) {
1076
- handoffs.push({
1077
- from: 'testing',
1078
- to: 'operations',
1079
- description: 'Quality metrics → Operational monitoring'
1080
- });
1081
- }
1082
- return handoffs;
1083
- }
1084
- /**
1085
- * Generate prerequisites for agents
1086
- */
1087
- generatePrerequisites(installedAgents) {
1088
- const prerequisites = {};
1089
- const agentNames = installedAgents.map(a => a.name);
1090
- agentNames.forEach(agent => {
1091
- if (agent === 'feature-developer' && agentNames.includes('system-architect')) {
1092
- prerequisites[agent] = ['system-architect'];
1093
- }
1094
- if (agent === 'component-developer' && agentNames.includes('system-architect')) {
1095
- prerequisites[agent] = ['system-architect'];
1096
- }
1097
- if (agent === 'typescript-developer' &&
1098
- (agentNames.includes('backend-architect') || agentNames.includes('system-architect'))) {
1099
- prerequisites[agent] = agentNames.includes('backend-architect') ?
1100
- ['backend-architect', 'system-architect'] : ['system-architect'];
1101
- }
1102
- if (agent === 'tdd-validator' && agentNames.some(a => a.includes('developer'))) {
1103
- prerequisites[agent] = agentNames.filter(a => a.includes('developer'));
193
+ // Replace from start to end marker (inclusive)
194
+ const afterEnd = existingContent.substring(endIndex + endMarker.length);
195
+ existingContent = existingContent.substring(0, startIndex) +
196
+ repoBlock +
197
+ afterEnd;
1104
198
  }
1105
- });
1106
- return prerequisites;
1107
- }
1108
- /**
1109
- * Get description for an agent (placeholder - could be enhanced)
1110
- */
1111
- getAgentDescription(agentName) {
1112
- const descriptions = {
1113
- 'prd-researcher': 'Research and analyze Product Requirements Documents to generate comprehensive technical tasks and implementation strategies',
1114
- 'system-architect': 'Design comprehensive system architectures that integrate frontend, backend, and infrastructure components',
1115
- 'backend-architect': 'Design and architect reliable backend systems with focus on data integrity, security, and fault tolerance',
1116
- 'feature-developer': 'Feature implementation specialist who transforms requirements into working functionality',
1117
- 'component-developer': 'UI component development specialist focused on building reusable, accessible, and well-tested components',
1118
- 'typescript-developer': 'TypeScript development specialist focused on type-safe, maintainable code following modern JavaScript/TypeScript best practices',
1119
- 'tdd-validator': 'Test-Driven Development methodology validator ensuring rigorous TDD compliance and quality gates',
1120
- 'functional-tester': 'Functional testing specialist who validates software functionality against requirements',
1121
- 'refactoring-expert': 'Code refactoring specialist focused on improving code quality, maintainability, and performance without changing functionality',
1122
- 'metrics-collector': 'Metrics and observability specialist who gathers, analyzes, and reports on system and business metrics',
1123
- 'root-cause-analyst': 'Incident investigation specialist who diagnoses system issues and identifies root causes'
1124
- };
1125
- return descriptions[agentName] || `${agentName.replace(/-/g, ' ')} specialist`;
1126
- }
1127
- /**
1128
- * Format workflow sections for CLAUDE.md
1129
- * Each section is independent - enabled features appear regardless of other settings
1130
- */
1131
- async formatPRDWorkflowSection() {
1132
- let section = '';
1133
- const repoRoot = getRepoRoot();
1134
- const prdPath = path.join(repoRoot, 'docs', 'prd');
1135
- const adrPath = path.join(repoRoot, 'docs', 'adr');
1136
- const hasPRD = existsSync(prdPath);
1137
- const hasADR = existsSync(adrPath);
1138
- // Check all feature flags
1139
- const enableSDD = await this.configService.isSDDEnabled();
1140
- const enableADR = await this.configService.isADREnabled();
1141
- const enableQuality = await this.configService.isQualityEnabled();
1142
- const enableTDD = await this.configService.isTDDEnabled();
1143
- // PRD Workflow section (requires SDD enabled AND docs/prd exists)
1144
- if (enableSDD && hasPRD) {
1145
- section += '## PRD Workflow\n\n';
1146
- section += 'IMPORTANT: Skills at `.claude/skills/` are **model-invoked** - describe your intent and Claude will use them automatically.\n';
1147
- section += 'Examples: "I want to plan a new feature", "track this bug fix", "add a feature to the PRD"\n\n';
1148
- section += '**Product Requirements Documents (PRDs):**\n';
1149
- section += `- Location: \`docs/prd/{prd-id}/prd.md\`\n`;
1150
- section += `- Features: \`docs/prd/{prd-id}/features/{feature-id}.md\`\n`;
1151
- section += `- Skills: plan (create PRDs), feature (add features), fix (track fixes)\n\n`;
1152
- // Commit Message Format (part of SDD)
1153
- section += '## Commit Message Format\n\n';
1154
- section += 'This project uses **Conventional Commits** for PRD-tracked tasks.\n\n';
1155
- section += '**Format:**\n';
1156
- section += '```\n';
1157
- section += '{type}({scope}): commit title\n\n';
1158
- section += 'PRD: {prd-id}\n';
1159
- section += 'Feature: {feature-id}\n';
1160
- section += 'Task: {exact-task-description}\n\n';
1161
- section += 'Description of changes...\n\n';
1162
- section += '🤖 Generated with [Claude Code](https://claude.com/claude-code)\n';
1163
- section += '```\n\n';
1164
- section += '**Quick Reference:**\n';
1165
- section += '- PRD ID = directory name in `docs/prd/`\n';
1166
- section += '- Feature ID = feature\'s `id` field\n';
1167
- section += '- Task = EXACT description from progress.json\n';
1168
- section += '- Type = `test`, `feat`, or `refactor` (for TDD tracking)\n\n';
1169
- }
1170
- // ADR section (independent - requires ADR enabled)
1171
- if (enableADR && hasADR) {
1172
- section += '## Architecture Decision Records\n\n';
1173
- section += `- Location: \`docs/adr/NNNN-decision-title.md\`\n`;
1174
- section += `- Use \`/adr\` skill to create ADRs\n\n`;
1175
- if (enableSDD && hasPRD) {
1176
- section += '**ADR Suggestions:** After PRD commits, check for ADR suggestions with `plan operation=check-adrs`\n\n';
199
+ else {
200
+ // No end marker found, replace to next section or end of file
201
+ const nextSectionIndex = existingContent.indexOf('\n## ', startIndex + 1);
202
+ if (nextSectionIndex > -1) {
203
+ existingContent = existingContent.substring(0, startIndex) +
204
+ repoBlock +
205
+ existingContent.substring(nextSectionIndex);
206
+ }
207
+ else {
208
+ existingContent = existingContent.substring(0, startIndex) + repoBlock;
209
+ }
1177
210
  }
1178
211
  }
1179
- // Quality section (independent - requires Quality enabled)
1180
- if (enableQuality) {
1181
- section += '## Code Quality Analysis\n\n';
1182
- section += `- Use \`/quality\` skill to run comprehensive quality analysis\n`;
1183
- section += `- Categories: Security, Reliability, Performance, Maintainability, Testing, Architecture, Documentation, Operations\n`;
1184
- section += `- Grades: A (90-100), B (80-89), C (70-79), D (60-69), F (<60)\n\n`;
1185
- }
1186
- // TDD Workflow section (independent - requires TDD enabled)
1187
- if (enableTDD) {
1188
- section += '## TDD Workflow\n\n';
1189
- section += '**MANDATORY:** This repository follows strict Test-Driven Development (TDD).\n\n';
1190
- section += '**Red → Green → Refactor Cycle:**\n\n';
1191
- section += '| Phase | Commit Type | What to Commit | Tests Must |\n';
1192
- section += '|-------|-------------|----------------|------------|\n';
1193
- section += '| RED | `test:` | Test files only | FAIL |\n';
1194
- section += '| GREEN | `feat:` or `fix:` | Implementation only | PASS |\n';
1195
- section += '| REFACTOR | `refactor:` | Any (optional) | PASS |\n\n';
1196
- section += '**Critical Rules:**\n';
1197
- section += '- NEVER commit test + implementation files together\n';
1198
- section += '- One task = one complete TDD cycle\n';
1199
- section += '- Git hooks enforce: `test:` skips test run, `feat:`/`fix:` runs full checks\n\n';
212
+ else {
213
+ // Append tiny-brain section
214
+ existingContent = existingContent.trimEnd() + '\n\n' + repoBlock;
1200
215
  }
1201
- return section;
1202
- }
1203
- /**
1204
- * Format ADR section (currently empty as ADR info is part of PRD workflow)
1205
- */
1206
- formatADRSection() {
1207
- // ADR information is now included in PRD workflow section
1208
- return '';
216
+ // Ensure directory exists and write file
217
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
218
+ await fs.writeFile(fullPath, existingContent, 'utf-8');
219
+ this.context.logger.info(`Updated workflow sections in ${contextPath}`);
220
+ }
221
+ /**
222
+ * Format the Commit Message Format section
223
+ */
224
+ formatCommitMessageSection() {
225
+ return `## Commit Message Format
226
+
227
+ ### CRITICAL: Commit Header Requirements
228
+
229
+ **BEFORE EVERY COMMIT**, check if you're working on tracked work:
230
+
231
+ 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
233
+
234
+ **If YES, you MUST include tracking headers or the commit will be REJECTED.**
235
+
236
+ ### For PRD-tracked work:
237
+ \`\`\`
238
+ feat(scope): description
239
+
240
+ PRD: {prd-id}
241
+ Feature: {feature-id}
242
+ Task: {exact task description from progress.json}
243
+
244
+ Description of changes...
245
+ \`\`\`
246
+
247
+ ### For Fix-tracked work:
248
+ \`\`\`
249
+ feat(scope): description
250
+
251
+ Fix: {fix-id}
252
+ Task: {exact task description from fix document}
253
+
254
+ Description of changes...
255
+ \`\`\`
256
+
257
+ ### Commit Types
258
+ | Type | When | Headers Required? |
259
+ |------|------|-------------------|
260
+ | \`test:\` | Writing failing tests (TDD RED) | Yes |
261
+ | \`feat:\` | Implementation (TDD GREEN) | Yes |
262
+ | \`fix:\` | Bug fixes | Yes |
263
+ | \`refactor:\` | Code improvement | Yes |
264
+ | \`chore:\` | Maintenance (untracked) | No |
265
+
266
+ **WARNING:** The commit-msg hook will reject commits missing required headers.
267
+
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.
271
+
272
+ **For PRD tasks:**
273
+ 1. Open the feature file: \`docs/prd/{prd-id}/features/{feature-id}.md\`
274
+ 2. Find the task checkbox and update it:
275
+ \`\`\`
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\`
282
+
283
+ **For Fix tasks:**
284
+ 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
299
+ - A fix with incomplete tasks stays \`in_progress\`
300
+ 4. When fully resolved, update YAML frontmatter:
301
+ - Set \`status: resolved\`
302
+ - Set \`resolved: YYYY-MM-DDTHH:mm:ss.sssZ\` (ISO timestamp)
303
+ - Add \`resolution\` object with \`rootCause\`, \`fix\` (array), and \`filesModified\` (array):
304
+ \`\`\`yaml
305
+ resolution:
306
+ rootCause: Brief description of what caused the issue
307
+ fix:
308
+ - First fix action taken
309
+ - Second fix action taken
310
+ filesModified:
311
+ - path/to/file1.ts
312
+ - path/to/file2.ts
313
+ \`\`\`
314
+
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.**
318
+
319
+ `;
320
+ }
321
+ /**
322
+ * Format the TDD Workflow section
323
+ */
324
+ formatTDDWorkflowSection() {
325
+ return `## TDD Workflow
326
+
327
+ IMPORTANT: This repository follows strict Test-Driven Development (TDD) with a 3-phase commit workflow.
328
+
329
+ **Red → Green → Refactor Cycle:**
330
+
331
+ 1. **Red Phase** (\`test:\` or \`test(scope):\` commits):
332
+ - Write failing tests first
333
+ - Tests SHOULD fail (that's the point!)
334
+ - Use: \`git commit -m "test: ..."\` or \`git commit -m "test(api): ..."\`
335
+ - Git hook automatically runs typecheck + lint but SKIPS tests
336
+ - Tracked in: \`testCommitSha\` field
337
+
338
+ 2. **Green Phase** (\`feat:\` or \`feat(scope):\` commits):
339
+ - Implement minimum code to make tests pass
340
+ - Use: \`git commit -m "feat: ..."\` or \`git commit -m "feat(auth): ..."\`
341
+ - Git hook automatically runs full checks (typecheck + lint + test)
342
+ - Tracked in: \`commitSha\` field
343
+ - **Marks task as COMPLETED**
344
+
345
+ 3. **Refactor Phase** (\`refactor:\` or \`refactor(scope):\` commits - optional):
346
+ - Improve code quality without changing behavior
347
+ - Use: \`git commit -m "refactor: ..."\` or \`git commit -m "refactor(plan): ..."\`
348
+ - Git hook automatically runs full checks (typecheck + lint + test)
349
+ - Tracked in: \`refactorCommitSha\` field
350
+
351
+ **Why This Matters:**
352
+ - Git hooks automatically detect commit type and run appropriate checks
353
+ - Separate commit tracking enables Feature 9 (TDD phase tracking in dashboard)
354
+ - Provides audit trail of development process
355
+ - Only \`feat:\` or \`feat(scope):\` commits mark tasks as completed
356
+
357
+ `;
358
+ }
359
+ /**
360
+ * Format the Progress Tracking Auto-Commit Configuration section
361
+ */
362
+ formatProgressTrackingSection() {
363
+ return `## Progress Tracking Auto-Commit Configuration
364
+
365
+ By default, tiny-brain uses **manual mode** for progress.json commits. This means after tracking a task commit, you must manually stage and commit the progress.json updates. This keeps your working tree clean and gives you full control over what gets committed.
366
+
367
+ ### Default Behavior (Manual Mode)
368
+
369
+ When you complete a task with a \`feat:\` or \`test:\` commit:
370
+ 1. The post-commit hook updates progress.json automatically
371
+ 2. You'll see a friendly message with instructions:
372
+ \`\`\`
373
+ 📝 Progress tracking updated!
374
+
375
+ To commit progress.json changes, run:
376
+ git add .tiny-brain/progress/*.json
377
+ git commit -m "chore: update progress tracking"
378
+
379
+ 💡 Tip: Enable auto-commit to skip this step:
380
+ npx tiny-brain config preferences set autoCommitProgress true
381
+ \`\`\`
382
+ 3. Commit the progress.json changes when you're ready
383
+
384
+ ### Enabling Auto-Commit
385
+
386
+ Auto-commit automatically creates a second commit containing progress.json updates after each tracked task commit.
387
+
388
+ **Enable globally (all repos):**
389
+ \`\`\`bash
390
+ npx tiny-brain config preferences set autoCommitProgress true
391
+ \`\`\`
392
+
393
+ **Enable for current repo only:**
394
+ \`\`\`bash
395
+ npx tiny-brain config preferences set autoCommitProgress true --repo
396
+ \`\`\`
397
+
398
+ **Disable auto-commit:**
399
+ \`\`\`bash
400
+ npx tiny-brain config preferences set autoCommitProgress false
401
+ \`\`\`
402
+
403
+ **Check current setting:**
404
+ \`\`\`bash
405
+ npx tiny-brain config preferences get autoCommitProgress
406
+ \`\`\`
407
+
408
+ ### When to Use Each Mode
409
+
410
+ **Use Manual Mode (default) when:**
411
+ - Working in a team - keeps progress updates separate from feature commits
412
+ - You want full control over what gets committed
413
+ - You prefer to batch progress updates
414
+ - You're concerned about commit history noise
415
+
416
+ **Use Auto-Commit Mode when:**
417
+ - Working solo or in small teams where commit noise is acceptable
418
+ - You want progress always synced with remote
419
+ - You frequently switch machines and need up-to-date progress
420
+ - You forget to commit progress.json regularly
421
+
422
+ ### Configuration Hierarchy
423
+
424
+ Settings follow this precedence (highest to lowest):
425
+ 1. Per-repository config (\`.git/tiny-brain/preferences.json\`)
426
+ 2. Global config (\`~/.tiny-brain/config/preferences.json\`)
427
+ 3. Default values (manual mode)
428
+
429
+ This allows you to set a global preference but override it per repository as needed.
430
+
431
+ `;
432
+ }
433
+ /**
434
+ * Format the Operational Tracking Directory section
435
+ */
436
+ formatOperationalTrackingSection() {
437
+ return `## Operational Tracking Directory (.tiny-brain/)
438
+
439
+ The \`.tiny-brain/\` directory stores operational tracking data separate from documentation:
440
+
441
+ \`\`\`
442
+ .tiny-brain/
443
+ ├── progress/ # PRD progress tracking
444
+ │ └── {prd-id}.json # One file per PRD
445
+ └── fixes/ # Fix progress tracking
446
+ └── progress.json # Aggregated fix tracking
447
+ \`\`\`
448
+
449
+ **Key distinction:**
450
+ - **Documentation** (in \`docs/\`) - PRD markdown, feature specs, fix analysis - permanent, reviewed
451
+ - **Operational tracking** (in \`.tiny-brain/\`) - progress.json files - transient, auto-generated
452
+
453
+ ### Gitignore Options
454
+
455
+ Teams can choose whether to track progress files in git:
456
+
457
+ **Option 1: Track progress (default)** - Keep \`.tiny-brain/\` in git for visibility across team members.
458
+
459
+ **Option 2: Ignore progress** - Add to \`.gitignore\` to reduce PR noise:
460
+ \`\`\`gitignore
461
+ # Ignore operational tracking (optional)
462
+ .tiny-brain/progress/
463
+ .tiny-brain/fixes/progress.json
464
+ \`\`\`
465
+
466
+ **Option 3: Ignore all** - Ignore entire directory:
467
+ \`\`\`gitignore
468
+ .tiny-brain/
469
+ \`\`\`
470
+
471
+ `;
1209
472
  }
1210
473
  }