@magic-ingredients/tiny-brain-local 0.8.0 → 0.10.1

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.
Files changed (147) hide show
  1. package/dist/agents/formatters/claude-code-formatter.d.ts +37 -0
  2. package/dist/agents/formatters/claude-code-formatter.d.ts.map +1 -0
  3. package/dist/agents/formatters/claude-code-formatter.js +217 -0
  4. package/dist/agents/formatters/claude-code-formatter.js.map +1 -0
  5. package/dist/agents/formatters/formatter-factory.d.ts +25 -0
  6. package/dist/agents/formatters/formatter-factory.d.ts.map +1 -0
  7. package/dist/agents/formatters/formatter-factory.js +61 -0
  8. package/dist/agents/formatters/formatter-factory.js.map +1 -0
  9. package/dist/agents/types.d.ts +68 -0
  10. package/dist/agents/types.d.ts.map +1 -0
  11. package/dist/agents/types.js +12 -0
  12. package/dist/agents/types.js.map +1 -0
  13. package/dist/analyser/analyzers/script-analyzer.d.ts +10 -0
  14. package/dist/analyser/analyzers/script-analyzer.d.ts.map +1 -0
  15. package/dist/analyser/analyzers/script-analyzer.js +205 -0
  16. package/dist/analyser/analyzers/script-analyzer.js.map +1 -0
  17. package/dist/analyser/detectors/base-detector.d.ts +12 -0
  18. package/dist/analyser/detectors/base-detector.d.ts.map +1 -0
  19. package/dist/analyser/detectors/base-detector.js +50 -0
  20. package/dist/analyser/detectors/base-detector.js.map +1 -0
  21. package/dist/analyser/detectors/javascript-detector.d.ts +19 -0
  22. package/dist/analyser/detectors/javascript-detector.d.ts.map +1 -0
  23. package/dist/analyser/detectors/javascript-detector.js +347 -0
  24. package/dist/analyser/detectors/javascript-detector.js.map +1 -0
  25. package/dist/analyser/index.d.ts +5 -0
  26. package/dist/analyser/index.d.ts.map +1 -0
  27. package/dist/analyser/index.js +315 -0
  28. package/dist/analyser/index.js.map +1 -0
  29. package/dist/analyser/types.d.ts +2 -0
  30. package/dist/analyser/types.d.ts.map +1 -0
  31. package/dist/analyser/types.js +2 -0
  32. package/dist/analyser/types.js.map +1 -0
  33. package/dist/analyser/utils.d.ts +5 -0
  34. package/dist/analyser/utils.d.ts.map +1 -0
  35. package/dist/analyser/utils.js +24 -0
  36. package/dist/analyser/utils.js.map +1 -0
  37. package/dist/cli/cli-factory.d.ts.map +1 -1
  38. package/dist/cli/cli-factory.js +17 -0
  39. package/dist/cli/cli-factory.js.map +1 -1
  40. package/dist/cli/commands/analyse.command.d.ts +7 -0
  41. package/dist/cli/commands/analyse.command.d.ts.map +1 -0
  42. package/dist/cli/commands/analyse.command.js +130 -0
  43. package/dist/cli/commands/analyse.command.js.map +1 -0
  44. package/dist/cli/commands/status.command.d.ts.map +1 -1
  45. package/dist/cli/commands/status.command.js +3 -1
  46. package/dist/cli/commands/status.command.js.map +1 -1
  47. package/dist/core/mcp-server.d.ts +10 -8
  48. package/dist/core/mcp-server.d.ts.map +1 -1
  49. package/dist/core/mcp-server.js +93 -85
  50. package/dist/core/mcp-server.js.map +1 -1
  51. package/dist/index.d.ts +3 -0
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +33 -8
  54. package/dist/index.js.map +1 -1
  55. package/dist/prompts/persona/persona.prompt.js +8 -8
  56. package/dist/prompts/persona/persona.prompt.js.map +1 -1
  57. package/dist/prompts/planning/planning.prompt.d.ts +0 -8
  58. package/dist/prompts/planning/planning.prompt.d.ts.map +1 -1
  59. package/dist/prompts/planning/planning.prompt.js +0 -175
  60. package/dist/prompts/planning/planning.prompt.js.map +1 -1
  61. package/dist/services/agent-installation-service.d.ts +101 -0
  62. package/dist/services/agent-installation-service.d.ts.map +1 -0
  63. package/dist/services/agent-installation-service.js +328 -0
  64. package/dist/services/agent-installation-service.js.map +1 -0
  65. package/dist/services/agent-manager.d.ts +45 -0
  66. package/dist/services/agent-manager.d.ts.map +1 -0
  67. package/dist/services/agent-manager.js +154 -0
  68. package/dist/services/agent-manager.js.map +1 -0
  69. package/dist/services/agent-service.d.ts +70 -0
  70. package/dist/services/agent-service.d.ts.map +1 -0
  71. package/dist/services/agent-service.js +273 -0
  72. package/dist/services/agent-service.js.map +1 -0
  73. package/dist/services/analyse-service.d.ts +97 -0
  74. package/dist/services/analyse-service.d.ts.map +1 -0
  75. package/dist/services/analyse-service.js +370 -0
  76. package/dist/services/analyse-service.js.map +1 -0
  77. package/dist/services/dashboard-launcher.service.d.ts +20 -0
  78. package/dist/services/dashboard-launcher.service.d.ts.map +1 -0
  79. package/dist/services/dashboard-launcher.service.js +30 -0
  80. package/dist/services/dashboard-launcher.service.js.map +1 -0
  81. package/dist/services/persona-enhancer.d.ts +52 -0
  82. package/dist/services/persona-enhancer.d.ts.map +1 -0
  83. package/dist/services/persona-enhancer.js +252 -0
  84. package/dist/services/persona-enhancer.js.map +1 -0
  85. package/dist/services/persona-grouper.d.ts +29 -0
  86. package/dist/services/persona-grouper.d.ts.map +1 -0
  87. package/dist/services/persona-grouper.js +111 -0
  88. package/dist/services/persona-grouper.js.map +1 -0
  89. package/dist/services/persona-service.d.ts +52 -0
  90. package/dist/services/persona-service.d.ts.map +1 -0
  91. package/dist/services/{enhanced-persona-service.js → persona-service.js} +125 -7
  92. package/dist/services/persona-service.js.map +1 -0
  93. package/dist/services/remote/auth-token-service.d.ts.map +1 -1
  94. package/dist/services/remote/auth-token-service.js +10 -3
  95. package/dist/services/remote/auth-token-service.js.map +1 -1
  96. package/dist/services/remote/system-persona-service.d.ts.map +1 -1
  97. package/dist/services/remote/system-persona-service.js +41 -10
  98. package/dist/services/remote/system-persona-service.js.map +1 -1
  99. package/dist/services/repo-service.d.ts +195 -0
  100. package/dist/services/repo-service.d.ts.map +1 -0
  101. package/dist/services/repo-service.js +1023 -0
  102. package/dist/services/repo-service.js.map +1 -0
  103. package/dist/services/types/persona-types.d.ts +84 -0
  104. package/dist/services/types/persona-types.d.ts.map +1 -0
  105. package/dist/services/types/persona-types.js +5 -0
  106. package/dist/services/types/persona-types.js.map +1 -0
  107. package/dist/services/versioning-service.d.ts +79 -0
  108. package/dist/services/versioning-service.d.ts.map +1 -0
  109. package/dist/services/versioning-service.js +191 -0
  110. package/dist/services/versioning-service.js.map +1 -0
  111. package/dist/storage/local-filesystem-adapter.d.ts +1 -0
  112. package/dist/storage/local-filesystem-adapter.d.ts.map +1 -1
  113. package/dist/storage/local-filesystem-adapter.js +47 -3
  114. package/dist/storage/local-filesystem-adapter.js.map +1 -1
  115. package/dist/storage/platform-config-adapter.d.ts +9 -0
  116. package/dist/storage/platform-config-adapter.d.ts.map +1 -1
  117. package/dist/storage/platform-config-adapter.js +55 -1
  118. package/dist/storage/platform-config-adapter.js.map +1 -1
  119. package/dist/tools/analyse.tool.d.ts +17 -0
  120. package/dist/tools/analyse.tool.d.ts.map +1 -0
  121. package/dist/tools/analyse.tool.js +124 -0
  122. package/dist/tools/analyse.tool.js.map +1 -0
  123. package/dist/tools/persona/as.tool.d.ts +32 -11
  124. package/dist/tools/persona/as.tool.d.ts.map +1 -1
  125. package/dist/tools/persona/as.tool.js +452 -317
  126. package/dist/tools/persona/as.tool.js.map +1 -1
  127. package/dist/tools/persona/persona.tool.js +2 -2
  128. package/dist/tools/persona/persona.tool.js.map +1 -1
  129. package/dist/tools/plan/plan.tool.d.ts +3 -3
  130. package/dist/tools/plan/plan.tool.d.ts.map +1 -1
  131. package/dist/tools/plan/plan.tool.js +78 -55
  132. package/dist/tools/plan/plan.tool.js.map +1 -1
  133. package/dist/tools/tool-registry.d.ts.map +1 -1
  134. package/dist/tools/tool-registry.js +4 -0
  135. package/dist/tools/tool-registry.js.map +1 -1
  136. package/dist/utils/repo-utils.d.ts +10 -0
  137. package/dist/utils/repo-utils.d.ts.map +1 -0
  138. package/dist/utils/repo-utils.js +55 -0
  139. package/dist/utils/repo-utils.js.map +1 -0
  140. package/package.json +6 -2
  141. package/dist/services/enhanced-persona-service.d.ts +0 -22
  142. package/dist/services/enhanced-persona-service.d.ts.map +0 -1
  143. package/dist/services/enhanced-persona-service.js.map +0 -1
  144. package/dist/services/plan-watcher.service.d.ts +0 -141
  145. package/dist/services/plan-watcher.service.d.ts.map +0 -1
  146. package/dist/services/plan-watcher.service.js +0 -1010
  147. package/dist/services/plan-watcher.service.js.map +0 -1
@@ -0,0 +1,1023 @@
1
+ /**
2
+ * Repository Context Service
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.
6
+ */
7
+ import * as fs from 'fs/promises';
8
+ import * as path from 'path';
9
+ import { detectTechStackChanges } from '@magic-ingredients/tiny-brain-core';
10
+ import { getRepoRoot } from '../utils/repo-utils.js';
11
+ /**
12
+ * Service for managing repository context in the context file
13
+ * Handles the tiny-brain section with analysis and agent usage
14
+ */
15
+ export class RepoService {
16
+ context;
17
+ static REPO_BLOCK_START = '## tiny-brain - start';
18
+ static REPO_BLOCK_END = '## tiny-brain - end';
19
+ static ANALYSIS_MARKER_PREFIX = '<!-- TB_ANALYSIS:';
20
+ static ANALYSIS_MARKER_SUFFIX = ' -->';
21
+ // New constants for subagent context protocol
22
+ static SUBAGENT_PROTOCOL_HEADER = '## Subagent Context Protocol';
23
+ static AGENT_USAGE_HEADER = '### When to Use Agents';
24
+ static REPO_CONTEXT_HEADER = '### Repository Context';
25
+ static USAGE_TEMPLATE_HEADER = '### Usage Template';
26
+ static EXAMPLE_HEADER = '### Example';
27
+ static ANALYSIS_IGNORE_PREFIX = '<!-- TB_ANALYSIS-IGNORE:';
28
+ static ANALYSIS_IGNORE_SUFFIX = ' -->';
29
+ static STATUS_IGNORE_PREFIX = '<!-- TB-STATUS-IGNORE:';
30
+ static STATUS_IGNORE_SUFFIX = ' -->';
31
+ constructor(context) {
32
+ this.context = context;
33
+ }
34
+ /**
35
+ * Write the repository block to context file with phase-based agent structure
36
+ */
37
+ async writeRepoBlockWithPhases(analysis, agentResponse, contextFilePath = 'CLAUDE.md') {
38
+ const repoRoot = getRepoRoot();
39
+ const fullPath = path.join(repoRoot, contextFilePath);
40
+ this.context.logger.debug('[RepoService] Writing repo block with phases to context file:', {
41
+ repoRoot,
42
+ contextFilePath,
43
+ fullPath,
44
+ agentCount: agentResponse?.agents?.length || 0
45
+ });
46
+ // Build the complete repo block with phase-based formatting
47
+ const repoBlock = this.formatRepoBlockWithPhases(analysis, agentResponse);
48
+ // Read existing content
49
+ let existingContent = '';
50
+ try {
51
+ existingContent = await fs.readFile(fullPath, 'utf-8');
52
+ }
53
+ catch {
54
+ // File doesn't exist, that's ok
55
+ }
56
+ // Update or append tiny-brain section
57
+ const startMarker = RepoService.REPO_BLOCK_START;
58
+ const endMarker = RepoService.REPO_BLOCK_END;
59
+ if (existingContent.includes(startMarker)) {
60
+ // Replace existing tiny-brain section
61
+ const startIndex = existingContent.indexOf(startMarker);
62
+ const endIndex = existingContent.indexOf(endMarker);
63
+ if (endIndex > -1) {
64
+ // Replace from start to end marker (inclusive)
65
+ const afterEnd = existingContent.substring(endIndex + endMarker.length);
66
+ existingContent = existingContent.substring(0, startIndex) +
67
+ repoBlock +
68
+ afterEnd;
69
+ }
70
+ else {
71
+ // No end marker found, replace to next section or end of file
72
+ const nextSectionIndex = existingContent.indexOf('\n## ', startIndex + 1);
73
+ if (nextSectionIndex > -1) {
74
+ existingContent = existingContent.substring(0, startIndex) +
75
+ repoBlock +
76
+ existingContent.substring(nextSectionIndex);
77
+ }
78
+ else {
79
+ existingContent = existingContent.substring(0, startIndex) + repoBlock;
80
+ }
81
+ }
82
+ }
83
+ else {
84
+ // Append tiny-brain section
85
+ existingContent = existingContent.trimEnd() + '\n\n' + repoBlock;
86
+ }
87
+ // Ensure directory exists and write file
88
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
89
+ await fs.writeFile(fullPath, existingContent, 'utf-8');
90
+ this.context.logger.info(`Updated repo block with ${agentResponse?.agents?.length || 0} agents in phase structure`);
91
+ // Return the formatted block (without the extra newline at the end)
92
+ return repoBlock.trimEnd();
93
+ }
94
+ /**
95
+ * Write the repository block to context file and return the formatted block (legacy compact format)
96
+ */
97
+ async writeRepoBlockToContextFile(analysis, agents, contextFilePath = 'CLAUDE.md') {
98
+ const repoRoot = getRepoRoot();
99
+ const fullPath = path.join(repoRoot, contextFilePath);
100
+ this.context.logger.debug('[RepoService] Writing repo block to context file:', {
101
+ repoRoot,
102
+ contextFilePath,
103
+ fullPath
104
+ });
105
+ // Build the complete repo block
106
+ const repoBlock = this.formatRepoBlock(analysis, agents);
107
+ // Read existing content
108
+ let existingContent = '';
109
+ try {
110
+ existingContent = await fs.readFile(fullPath, 'utf-8');
111
+ }
112
+ catch {
113
+ // File doesn't exist, that's ok
114
+ }
115
+ // Update or append tiny-brain section
116
+ const startMarker = RepoService.REPO_BLOCK_START;
117
+ const endMarker = RepoService.REPO_BLOCK_END;
118
+ if (existingContent.includes(startMarker)) {
119
+ // Replace existing tiny-brain section
120
+ const startIndex = existingContent.indexOf(startMarker);
121
+ const endIndex = existingContent.indexOf(endMarker);
122
+ if (endIndex > -1) {
123
+ // Replace from start to end marker (inclusive)
124
+ const afterEnd = existingContent.substring(endIndex + endMarker.length);
125
+ existingContent = existingContent.substring(0, startIndex) +
126
+ repoBlock +
127
+ afterEnd;
128
+ }
129
+ else {
130
+ // No end marker found, replace to next section or end of file
131
+ const nextSectionIndex = existingContent.indexOf('\n## ', startIndex + 1);
132
+ if (nextSectionIndex > -1) {
133
+ existingContent = existingContent.substring(0, startIndex) +
134
+ repoBlock +
135
+ existingContent.substring(nextSectionIndex);
136
+ }
137
+ else {
138
+ existingContent = existingContent.substring(0, startIndex) + repoBlock;
139
+ }
140
+ }
141
+ }
142
+ else {
143
+ // Append tiny-brain section
144
+ existingContent = existingContent.trimEnd() + '\n\n' + repoBlock;
145
+ }
146
+ // Ensure directory exists and write file
147
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
148
+ await fs.writeFile(fullPath, existingContent, 'utf-8');
149
+ this.context.logger.info(`Updated repo block with ${agents.length} agents`);
150
+ // Return the formatted block (without the extra newline at the end)
151
+ return repoBlock.trimEnd();
152
+ }
153
+ /**
154
+ * Read the repository block from context file
155
+ */
156
+ async readRepoBlockFromContextFile(contextFilePath = 'CLAUDE.md') {
157
+ try {
158
+ const repoRoot = getRepoRoot();
159
+ const fullPath = path.join(repoRoot, contextFilePath);
160
+ const content = await fs.readFile(fullPath, 'utf-8');
161
+ // Find the tiny-brain section
162
+ const startMarker = RepoService.REPO_BLOCK_START;
163
+ const endMarker = RepoService.REPO_BLOCK_END;
164
+ const startIndex = content.indexOf(startMarker);
165
+ if (startIndex === -1) {
166
+ return null;
167
+ }
168
+ const endIndex = content.indexOf(endMarker, startIndex);
169
+ if (endIndex === -1) {
170
+ // No end marker, read to next section or end
171
+ const nextSectionIndex = content.indexOf('\n## ', startIndex + 1);
172
+ if (nextSectionIndex > -1) {
173
+ return content.substring(startIndex, nextSectionIndex).trimEnd();
174
+ }
175
+ return content.substring(startIndex).trimEnd();
176
+ }
177
+ // Include the end marker
178
+ return content.substring(startIndex, endIndex + endMarker.length).trimEnd();
179
+ }
180
+ catch (error) {
181
+ if (error.code === 'ENOENT') {
182
+ return null;
183
+ }
184
+ throw error;
185
+ }
186
+ }
187
+ /**
188
+ * Format the repository block with phase-based agent structure
189
+ */
190
+ formatRepoBlockWithPhases(analysis, agentResponse) {
191
+ let block = `${RepoService.REPO_BLOCK_START}\n`;
192
+ block += `${RepoService.SUBAGENT_PROTOCOL_HEADER}\n\n`;
193
+ // When to use agents - phase-based format
194
+ block += `${RepoService.AGENT_USAGE_HEADER}\n\n`;
195
+ block += this.formatAgentsWithPhases(agentResponse);
196
+ // Repository context - YAML format
197
+ block += `${RepoService.REPO_CONTEXT_HEADER}\n`;
198
+ block += this.formatRepositoryContextYaml(analysis);
199
+ // Usage template
200
+ block += `\n${RepoService.USAGE_TEMPLATE_HEADER}\n\n`;
201
+ block += '```\n';
202
+ block += 'Repository Context: [paste the yaml above]\n\n';
203
+ block += 'AGENT: [choose from list above]\n';
204
+ block += 'TASK: [specific task description]\n';
205
+ block += 'CONSTRAINTS: [requirements/limitations]\n';
206
+ block += 'EXPECTED OUTPUT: [what you need back]\n';
207
+ block += '```\n';
208
+ // Example
209
+ block += `\n${RepoService.EXAMPLE_HEADER}\n`;
210
+ block += '```\n';
211
+ block += 'Repository Context: [repo context yaml]\n\n';
212
+ block += 'AGENT: component-developer\n';
213
+ block += 'TASK: Create a UserProfile component with avatar, name, and email\n';
214
+ block += 'CONSTRAINTS: Must be accessible, use TypeScript, follow TDD\n';
215
+ block += 'EXPECTED OUTPUT: Component file with tests and usage example\n';
216
+ block += '```\n';
217
+ // TB_ANALYSIS at the end with IGNORE suffix for clarity
218
+ block += `\n${RepoService.ANALYSIS_IGNORE_PREFIX}${JSON.stringify(analysis)}${RepoService.ANALYSIS_IGNORE_SUFFIX}\n`;
219
+ block += `${RepoService.REPO_BLOCK_END}\n`;
220
+ return block;
221
+ }
222
+ /**
223
+ * Format the complete repository block with subagent context protocol (legacy compact format)
224
+ */
225
+ formatRepoBlock(analysis, agents) {
226
+ let block = `${RepoService.REPO_BLOCK_START}\n`;
227
+ block += `${RepoService.SUBAGENT_PROTOCOL_HEADER}\n\n`;
228
+ // When to use agents - compact format
229
+ block += `${RepoService.AGENT_USAGE_HEADER}\n\n`;
230
+ block += this.formatAgentsCompact(agents);
231
+ // Repository context - YAML format
232
+ block += `${RepoService.REPO_CONTEXT_HEADER}\n`;
233
+ block += this.formatRepositoryContextYaml(analysis);
234
+ // Usage template
235
+ block += `\n${RepoService.USAGE_TEMPLATE_HEADER}\n\n`;
236
+ block += '```\n';
237
+ block += 'Repository Context: [paste the yaml above]\n\n';
238
+ block += 'AGENT: [choose from list above]\n';
239
+ block += 'TASK: [specific task description]\n';
240
+ block += 'CONSTRAINTS: [requirements/limitations]\n';
241
+ block += 'EXPECTED OUTPUT: [what you need back]\n';
242
+ block += '```\n';
243
+ // Example
244
+ block += `\n${RepoService.EXAMPLE_HEADER}\n`;
245
+ block += '```\n';
246
+ block += 'Repository Context: [repo context yaml]\n\n';
247
+ block += 'AGENT: component-developer\n';
248
+ block += 'TASK: Create a UserProfile component with avatar, name, and email\n';
249
+ block += 'CONSTRAINTS: Must be accessible, use TypeScript, follow TDD\n';
250
+ block += 'EXPECTED OUTPUT: Component file with tests and usage example\n';
251
+ block += '```\n';
252
+ // TB_ANALYSIS at the end with IGNORE suffix for clarity
253
+ block += `\n${RepoService.ANALYSIS_IGNORE_PREFIX}${JSON.stringify(analysis)}${RepoService.ANALYSIS_IGNORE_SUFFIX}\n`;
254
+ block += `${RepoService.REPO_BLOCK_END}\n`;
255
+ return block;
256
+ }
257
+ /**
258
+ * Parse the repository block from context file into structured data
259
+ */
260
+ async parseRepoBlock(contextFilePath = 'CLAUDE.md') {
261
+ const repoBlockMarkdown = await this.readRepoBlockFromContextFile(contextFilePath);
262
+ if (!repoBlockMarkdown)
263
+ return null;
264
+ // Extract JSON from comment
265
+ const analysisRegex = new RegExp(`${RepoService.ANALYSIS_MARKER_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(.*?)${RepoService.ANALYSIS_MARKER_SUFFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 's');
266
+ const jsonMatch = repoBlockMarkdown.match(analysisRegex);
267
+ const rawAnalysis = jsonMatch ? JSON.parse(jsonMatch[1]) : null;
268
+ // Extract analysis section
269
+ const analysisMatch = repoBlockMarkdown.match(/### repo analysis\n([\s\S]*?)(?=\n###|\n##|$)/);
270
+ const analysis = analysisMatch ? analysisMatch[1].trim() : '';
271
+ // Extract documentation section (optional)
272
+ const docMatch = repoBlockMarkdown.match(/#### documentation\n([\s\S]*?)(?=\n###|\n##|$)/);
273
+ const documentation = docMatch ? docMatch[1].trim() : null;
274
+ // Extract agent usage section
275
+ const agentMatch = repoBlockMarkdown.match(/### agent usage\n([\s\S]*?)(?=\n##|$)/);
276
+ const agentUsage = agentMatch ? agentMatch[1].trim() : 'No agents available for this repository.';
277
+ return { rawAnalysis, analysis, documentation, agentUsage };
278
+ }
279
+ /**
280
+ * Format agents with phase-based structure
281
+ */
282
+ formatAgentsWithPhases(agentResponse) {
283
+ // Handle empty response
284
+ if (!agentResponse?.agentUsage?.phases || Object.keys(agentResponse.agentUsage.phases).length === 0) {
285
+ return 'No agents available for this repository.\n';
286
+ }
287
+ let output = '';
288
+ const phases = agentResponse.agentUsage.phases;
289
+ const phaseOrder = ['ideation', 'development', 'testing', 'operations'];
290
+ const phaseNames = {
291
+ ideation: 'Phase 1: Ideation',
292
+ development: 'Phase 2: Development',
293
+ testing: 'Phase 3: Testing & Quality',
294
+ operations: 'Phase 4: Operations & Analysis'
295
+ };
296
+ // Format each phase
297
+ phaseOrder.forEach((phaseKey) => {
298
+ const phase = phases[phaseKey];
299
+ if (!phase)
300
+ return;
301
+ output += `#### ${phaseNames[phaseKey]}\n`;
302
+ // List agents
303
+ if (phase.agents && phase.agents.length > 0) {
304
+ phase.agents.forEach((agentName) => {
305
+ const agent = agentResponse.agents?.find((a) => a.name === agentName);
306
+ if (agent) {
307
+ output += `- \`${agent.name}\` - ${agent.details.description}\n`;
308
+ }
309
+ else {
310
+ output += `- \`${agentName}\`\n`;
311
+ }
312
+ });
313
+ output += '\n';
314
+ }
315
+ // Phase handoffs
316
+ if (phase.handoffs && phase.handoffs.length > 0) {
317
+ const phaseName = phaseNames[phaseKey] || phaseKey;
318
+ const phaseTitle = phaseName.includes(':') ? phaseName.split(':')[0] : phaseName;
319
+ output += `**${phaseTitle} Handoffs:**\n`;
320
+ phase.handoffs.forEach((handoff) => {
321
+ if (typeof handoff === 'string') {
322
+ // Handle string format: "from → to: description"
323
+ output += `- \`${handoff.replace(' → ', '` → `').replace(': ', '`: ')}\n`;
324
+ }
325
+ else {
326
+ // Handle object format
327
+ output += `- \`${handoff.from}\` → \`${handoff.to}\``;
328
+ if (handoff.description) {
329
+ output += `: ${handoff.description}`;
330
+ }
331
+ output += '\n';
332
+ }
333
+ });
334
+ output += '\n';
335
+ }
336
+ });
337
+ // Cross-phase handoffs
338
+ if (agentResponse.agentUsage.crossPhaseHandoffs && agentResponse.agentUsage.crossPhaseHandoffs.length > 0) {
339
+ output += '### Cross-Phase Handoffs\n';
340
+ agentResponse.agentUsage.crossPhaseHandoffs.forEach((handoff) => {
341
+ const fromPhaseName = phaseNames[handoff.from] || handoff.from;
342
+ const toPhaseName = phaseNames[handoff.to] || handoff.to;
343
+ const fromName = fromPhaseName.includes(':') ? fromPhaseName.split(':')[1]?.trim() : fromPhaseName;
344
+ const toName = toPhaseName.includes(':') ? toPhaseName.split(':')[1]?.trim() : toPhaseName;
345
+ output += `**${fromName.charAt(0).toUpperCase() + fromName.slice(1)} → ${toName.charAt(0).toUpperCase() + toName.slice(1)}:**\n`;
346
+ output += `- ${handoff.description}\n\n`;
347
+ });
348
+ }
349
+ // Prerequisites
350
+ if (agentResponse.agentUsage.prerequisites && Object.keys(agentResponse.agentUsage.prerequisites).length > 0) {
351
+ output += '### Agent Prerequisites\n';
352
+ Object.entries(agentResponse.agentUsage.prerequisites).forEach(([agent, prerequisites]) => {
353
+ // Prerequisites can be either a string or array of strings
354
+ if (Array.isArray(prerequisites)) {
355
+ if (prerequisites.length > 0) {
356
+ const formattedPrereqs = prerequisites.map(prereq => `\`${prereq}\``).join(', ');
357
+ output += `- \`${agent}\`: ${formattedPrereqs}\n`;
358
+ }
359
+ }
360
+ else if (typeof prerequisites === 'string' && prerequisites.trim()) {
361
+ // Prerequisites is a string description - wrap agent names in backticks
362
+ const formattedPrerequisites = prerequisites.replace(/([a-z-]+(?:-[a-z]+)*)/g, (match) => {
363
+ // Only wrap strings that look like agent names (hyphenated words)
364
+ if (match.includes('-') || ['system', 'feature', 'component', 'functional', 'tdd', 'refactoring', 'metrics', 'root'].some(word => match.includes(word))) {
365
+ return `\`${match}\``;
366
+ }
367
+ return match;
368
+ });
369
+ output += `- \`${agent}\`: ${formattedPrerequisites}\n`;
370
+ }
371
+ });
372
+ output += '\n';
373
+ }
374
+ // Invocation template
375
+ if (agentResponse.agentUsage.invocationTemplate?.structure) {
376
+ output += '### Agent Invocation Template\n';
377
+ output += '```\n';
378
+ output += agentResponse.agentUsage.invocationTemplate.structure.replace(/\\n/g, '\n');
379
+ output += '\n```\n';
380
+ }
381
+ return output;
382
+ }
383
+ /**
384
+ * Format agents in compact style with categories
385
+ */
386
+ formatAgentsCompact(agents) {
387
+ const categories = {
388
+ 'Planning & Architecture': [],
389
+ 'Development': [],
390
+ 'Testing & Quality': [],
391
+ 'Operations & Analysis': []
392
+ };
393
+ agents.forEach(agent => {
394
+ const compactDesc = this.getCompactDescription(agent.description);
395
+ const line = `\`${agent.name}\` - ${compactDesc}`;
396
+ // Categorize based on keywords in name and description
397
+ const searchText = `${agent.name} ${agent.description}`.toLowerCase();
398
+ if (searchText.includes('architect') || searchText.includes('researcher') ||
399
+ searchText.includes('planner') || searchText.includes('design') ||
400
+ searchText.includes('prd') || searchText.includes('requirement')) {
401
+ categories['Planning & Architecture'].push(line);
402
+ }
403
+ else if (searchText.includes('test') || searchText.includes('validator') ||
404
+ searchText.includes('quality') || searchText.includes('refactor') ||
405
+ searchText.includes('tdd')) {
406
+ categories['Testing & Quality'].push(line);
407
+ }
408
+ else if (searchText.includes('metrics') || searchText.includes('analyst') ||
409
+ searchText.includes('monitor') || searchText.includes('observ') ||
410
+ searchText.includes('collector') || searchText.includes('root-cause')) {
411
+ categories['Operations & Analysis'].push(line);
412
+ }
413
+ else {
414
+ // Default to Development
415
+ categories['Development'].push(line);
416
+ }
417
+ });
418
+ // Build formatted output
419
+ let output = '';
420
+ for (const [category, agentList] of Object.entries(categories)) {
421
+ if (agentList.length > 0) {
422
+ output += `**${category}**\n`;
423
+ agentList.forEach(line => output += `- ${line}\n`);
424
+ output += '\n';
425
+ }
426
+ }
427
+ return output;
428
+ }
429
+ /**
430
+ * Get compact description by removing common phrases and truncating if needed
431
+ */
432
+ getCompactDescription(description) {
433
+ // Remove common prefixes and make concise
434
+ let compact = description
435
+ .replace(/^(A |An |The )/i, '')
436
+ .replace(/specialist (who|that) /gi, '')
437
+ .replace(/focused on /gi, '')
438
+ .replace(/development specialist /gi, '');
439
+ // Capitalize first letter
440
+ compact = compact.charAt(0).toUpperCase() + compact.slice(1);
441
+ // Truncate if too long (80 chars)
442
+ if (compact.length > 80) {
443
+ compact = compact.substring(0, 77) + '...';
444
+ }
445
+ return compact;
446
+ }
447
+ /**
448
+ * Format repository context as clean YAML
449
+ */
450
+ formatRepositoryContextYaml(analysis) {
451
+ let yaml = '```yaml\n';
452
+ yaml += `Languages: ${analysis.languages.join(', ')}\n`;
453
+ yaml += `Frameworks: ${analysis.frameworks.join(', ')}\n`;
454
+ yaml += `Build Tools: ${analysis.buildTools.join(', ')}\n`;
455
+ yaml += `Testing Tools: ${analysis.testingTools.join(', ')}\n`;
456
+ if (analysis.hasTests) {
457
+ yaml += `Tests: ${analysis.testFileCount} files (patterns: ${analysis.testPatterns.join(', ')})\n`;
458
+ }
459
+ if (analysis.isPolyglot) {
460
+ yaml += `Polyglot: Yes (primary: ${analysis.primaryLanguage})\n`;
461
+ }
462
+ yaml += `Documentation Pattern: ${analysis.documentationPattern}\n`;
463
+ // Always use absolute paths for documentation
464
+ const docLocations = analysis.documentationLocations?.map(loc => path.isAbsolute(loc) ? loc : path.join(process.cwd(), loc)) || [];
465
+ yaml += `Documentation Locations: ${docLocations.join(', ')}\n`;
466
+ yaml += '```\n';
467
+ return yaml;
468
+ }
469
+ /**
470
+ * Extract TB_ANALYSIS from content with new IGNORE format
471
+ */
472
+ extractTBAnalysis(content) {
473
+ const regex = new RegExp(`${RepoService.ANALYSIS_IGNORE_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(.*?)${RepoService.ANALYSIS_IGNORE_SUFFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 's');
474
+ const match = content.match(regex);
475
+ if (match && match[1]) {
476
+ try {
477
+ return JSON.parse(match[1]);
478
+ }
479
+ catch {
480
+ return null;
481
+ }
482
+ }
483
+ return null;
484
+ }
485
+ /**
486
+ * Extract TB-STATUS from content with backwards compatibility for TB_ANALYSIS-IGNORE
487
+ */
488
+ extractTBStatus(content) {
489
+ // First try TB-STATUS-IGNORE (new format)
490
+ const statusRegex = new RegExp(`${RepoService.STATUS_IGNORE_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(.*?)${RepoService.STATUS_IGNORE_SUFFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 's');
491
+ const statusMatch = content.match(statusRegex);
492
+ if (statusMatch && statusMatch[1]) {
493
+ try {
494
+ return JSON.parse(statusMatch[1]);
495
+ }
496
+ catch {
497
+ return null;
498
+ }
499
+ }
500
+ // Fall back to TB_ANALYSIS-IGNORE (legacy format)
501
+ const analysisRegex = new RegExp(`${RepoService.ANALYSIS_IGNORE_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(.*?)${RepoService.ANALYSIS_IGNORE_SUFFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 's');
502
+ const analysisMatch = content.match(analysisRegex);
503
+ if (analysisMatch && analysisMatch[1]) {
504
+ try {
505
+ const analysis = JSON.parse(analysisMatch[1]);
506
+ // Convert RepoAnalysis to TBStatus format (no additional fields for legacy)
507
+ return analysis;
508
+ }
509
+ catch {
510
+ return null;
511
+ }
512
+ }
513
+ return null;
514
+ }
515
+ /**
516
+ * Generate TB-STATUS-IGNORE format string with current timestamp
517
+ */
518
+ updateTBStatus(analysis, installedAgents) {
519
+ const tbStatus = {
520
+ ...analysis,
521
+ lastUpdated: new Date().toISOString(),
522
+ ...(installedAgents && { installedAgents })
523
+ };
524
+ return `${RepoService.STATUS_IGNORE_PREFIX}${JSON.stringify(tbStatus)}${RepoService.STATUS_IGNORE_SUFFIX}`;
525
+ }
526
+ /**
527
+ * Check if repository is initialized (has TB-STATUS-IGNORE or TB_ANALYSIS-IGNORE)
528
+ */
529
+ isRepoInitialized(content) {
530
+ const tbStatus = this.extractTBStatus(content);
531
+ return tbStatus !== null;
532
+ }
533
+ /**
534
+ * Check if we're in a repository context
535
+ */
536
+ isInRepository() {
537
+ try {
538
+ // This will throw if not in a repo
539
+ getRepoRoot();
540
+ return true;
541
+ }
542
+ catch {
543
+ return false;
544
+ }
545
+ }
546
+ /**
547
+ * Check if repository is initialized (has TB block in context file)
548
+ */
549
+ async isRepositoryInitialized() {
550
+ if (!this.isInRepository()) {
551
+ return false; // Not in repo, so not initialized
552
+ }
553
+ try {
554
+ const content = await this.readRepoBlockFromContextFile();
555
+ return content ? this.isRepoInitialized(content) : false;
556
+ }
557
+ catch {
558
+ return false;
559
+ }
560
+ }
561
+ /**
562
+ * Compare two tech stacks and return differences
563
+ */
564
+ compareTechStack(previousAnalysis, currentAnalysis) {
565
+ return detectTechStackChanges({
566
+ languages: previousAnalysis.languages || [],
567
+ frameworks: previousAnalysis.frameworks || [],
568
+ buildTools: previousAnalysis.buildTools || [],
569
+ testingTools: previousAnalysis.testingTools || []
570
+ }, {
571
+ languages: currentAnalysis.languages || [],
572
+ frameworks: currentAnalysis.frameworks || [],
573
+ buildTools: currentAnalysis.buildTools || [],
574
+ testingTools: currentAnalysis.testingTools || []
575
+ });
576
+ }
577
+ /**
578
+ * Migrate legacy TB_ANALYSIS-IGNORE format to new TB-STATUS-IGNORE format
579
+ */
580
+ migrateToNewFormat(existingContent, newAnalysis) {
581
+ // Generate new TB-STATUS-IGNORE format
582
+ const newStatusIgnore = this.updateTBStatus(newAnalysis);
583
+ // Replace TB_ANALYSIS-IGNORE with TB-STATUS-IGNORE
584
+ if (existingContent.includes(RepoService.ANALYSIS_IGNORE_PREFIX)) {
585
+ const analysisRegex = new RegExp(`${RepoService.ANALYSIS_IGNORE_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*?${RepoService.ANALYSIS_IGNORE_SUFFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 's');
586
+ return existingContent.replace(analysisRegex, newStatusIgnore);
587
+ }
588
+ // Replace existing TB-STATUS-IGNORE with updated version
589
+ if (existingContent.includes(RepoService.STATUS_IGNORE_PREFIX)) {
590
+ const statusRegex = new RegExp(`${RepoService.STATUS_IGNORE_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*?${RepoService.STATUS_IGNORE_SUFFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 's');
591
+ return existingContent.replace(statusRegex, newStatusIgnore);
592
+ }
593
+ // No existing format found, return original content
594
+ return existingContent;
595
+ }
596
+ /**
597
+ * Update agent usage section in CLAUDE.md with phase-based structure
598
+ */
599
+ async updateAgentUsageSection(installedAgents, phaseStructure, contextFilePath = 'CLAUDE.md') {
600
+ try {
601
+ const repoRoot = getRepoRoot();
602
+ const fullPath = path.join(repoRoot, contextFilePath);
603
+ // Read existing content or create new
604
+ let existingContent = '';
605
+ try {
606
+ existingContent = await fs.readFile(fullPath, 'utf-8');
607
+ }
608
+ catch {
609
+ // File doesn't exist, create with just the tiny-brain section
610
+ existingContent = '';
611
+ }
612
+ // Generate the agent usage content
613
+ const agentUsageContent = this.formatPhaseBasedAgentUsage(installedAgents, phaseStructure);
614
+ // Update or create the tiny-brain section
615
+ const updatedContent = this.updateTinyBrainSection(existingContent, agentUsageContent);
616
+ // Ensure directory exists and write file
617
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
618
+ await fs.writeFile(fullPath, updatedContent, 'utf-8');
619
+ this.context.logger.info(`Updated ${contextFilePath} with ${installedAgents.length} agents in phase structure`);
620
+ }
621
+ catch (error) {
622
+ this.context.logger.error(`Failed to update agent usage section in ${contextFilePath}:`, error);
623
+ throw error;
624
+ }
625
+ }
626
+ /**
627
+ * Generate phase structure from installed agents
628
+ */
629
+ generatePhaseStructure(installedAgents) {
630
+ const structure = {
631
+ phases: {},
632
+ crossPhaseHandoffs: [],
633
+ prerequisites: {}
634
+ };
635
+ // Agent categorization
636
+ const ideationAgents = [];
637
+ const developmentAgents = [];
638
+ const testingAgents = [];
639
+ const operationsAgents = [];
640
+ // Categorize agents
641
+ installedAgents.forEach(agent => {
642
+ const name = agent.name.toLowerCase();
643
+ if (name.includes('prd') || name.includes('researcher') ||
644
+ name.includes('architect') || name.includes('planner')) {
645
+ ideationAgents.push(agent.name);
646
+ }
647
+ else if (name.includes('test') || name.includes('validator') ||
648
+ name.includes('tdd') || name.includes('refactor')) {
649
+ testingAgents.push(agent.name);
650
+ }
651
+ else if (name.includes('metrics') || name.includes('analyst') ||
652
+ name.includes('collector') || name.includes('monitor')) {
653
+ operationsAgents.push(agent.name);
654
+ }
655
+ else {
656
+ // Default to development
657
+ developmentAgents.push(agent.name);
658
+ }
659
+ });
660
+ // Build phase structure
661
+ if (ideationAgents.length > 0) {
662
+ structure.phases.ideation = {
663
+ agents: ideationAgents,
664
+ handoffs: this.generatePhaseHandoffs(ideationAgents)
665
+ };
666
+ }
667
+ if (developmentAgents.length > 0) {
668
+ structure.phases.development = {
669
+ agents: developmentAgents,
670
+ handoffs: this.generatePhaseHandoffs(developmentAgents)
671
+ };
672
+ }
673
+ if (testingAgents.length > 0) {
674
+ structure.phases.testing = {
675
+ agents: testingAgents,
676
+ handoffs: this.generatePhaseHandoffs(testingAgents)
677
+ };
678
+ }
679
+ if (operationsAgents.length > 0) {
680
+ structure.phases.operations = {
681
+ agents: operationsAgents,
682
+ handoffs: this.generatePhaseHandoffs(operationsAgents)
683
+ };
684
+ }
685
+ // Generate cross-phase handoffs
686
+ structure.crossPhaseHandoffs = this.generateCrossPhaseHandoffs(structure.phases);
687
+ // Generate prerequisites
688
+ structure.prerequisites = this.generatePrerequisites(installedAgents);
689
+ return structure;
690
+ }
691
+ /**
692
+ * Create or update TB-STATUS-IGNORE with analysis and installed agents
693
+ */
694
+ async createOrUpdateTBStatus(analysis, installedAgents, contextFilePath = 'CLAUDE.md') {
695
+ try {
696
+ const repoRoot = getRepoRoot();
697
+ const fullPath = path.join(repoRoot, contextFilePath);
698
+ // Read existing content
699
+ let existingContent = '';
700
+ try {
701
+ existingContent = await fs.readFile(fullPath, 'utf-8');
702
+ }
703
+ catch {
704
+ // File doesn't exist
705
+ }
706
+ // Create TB status data
707
+ const tbStatus = {
708
+ ...analysis,
709
+ lastUpdated: new Date().toISOString(),
710
+ installedAgents
711
+ };
712
+ // Generate TB-STATUS-IGNORE format
713
+ const statusIgnore = `${RepoService.STATUS_IGNORE_PREFIX}${JSON.stringify(tbStatus)}${RepoService.STATUS_IGNORE_SUFFIX}`;
714
+ // Update the status in the content
715
+ let updatedContent = existingContent;
716
+ // Replace existing TB-STATUS-IGNORE or TB_ANALYSIS-IGNORE
717
+ if (existingContent.includes(RepoService.STATUS_IGNORE_PREFIX)) {
718
+ const statusRegex = new RegExp(`${RepoService.STATUS_IGNORE_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*?${RepoService.STATUS_IGNORE_SUFFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 's');
719
+ updatedContent = existingContent.replace(statusRegex, statusIgnore);
720
+ }
721
+ else if (existingContent.includes(RepoService.ANALYSIS_IGNORE_PREFIX)) {
722
+ const analysisRegex = new RegExp(`${RepoService.ANALYSIS_IGNORE_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*?${RepoService.ANALYSIS_IGNORE_SUFFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 's');
723
+ updatedContent = existingContent.replace(analysisRegex, statusIgnore);
724
+ }
725
+ else {
726
+ // Add to tiny-brain section or create it
727
+ updatedContent = this.addStatusToContent(existingContent, statusIgnore);
728
+ }
729
+ // Write updated content
730
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
731
+ await fs.writeFile(fullPath, updatedContent, 'utf-8');
732
+ this.context.logger.info(`Updated TB-STATUS-IGNORE with ${installedAgents.length} agents`);
733
+ }
734
+ catch (error) {
735
+ this.context.logger.error('Failed to create or update TB-STATUS-IGNORE:', error);
736
+ throw error;
737
+ }
738
+ }
739
+ /**
740
+ * Migrate legacy TB_ANALYSIS-IGNORE to TB-STATUS-IGNORE format
741
+ */
742
+ async migrateTBAnalysisToTBStatus(existingContent, installedAgents, contextFilePath = 'CLAUDE.md') {
743
+ try {
744
+ // Extract existing analysis data
745
+ const existingAnalysis = this.extractTBAnalysis(existingContent);
746
+ if (!existingAnalysis) {
747
+ this.context.logger.warn('No TB_ANALYSIS-IGNORE found to migrate');
748
+ return;
749
+ }
750
+ // Create TB status with preserved analysis data and new agents
751
+ const tbStatus = {
752
+ ...existingAnalysis,
753
+ lastUpdated: new Date().toISOString(),
754
+ installedAgents
755
+ };
756
+ // Generate new TB-STATUS-IGNORE
757
+ const statusIgnore = `${RepoService.STATUS_IGNORE_PREFIX}${JSON.stringify(tbStatus)}${RepoService.STATUS_IGNORE_SUFFIX}`;
758
+ // Replace TB_ANALYSIS-IGNORE with TB-STATUS-IGNORE
759
+ const analysisRegex = new RegExp(`${RepoService.ANALYSIS_IGNORE_PREFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}.*?${RepoService.ANALYSIS_IGNORE_SUFFIX.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 's');
760
+ const updatedContent = existingContent.replace(analysisRegex, statusIgnore);
761
+ // Write migrated content
762
+ const repoRoot = getRepoRoot();
763
+ const fullPath = path.join(repoRoot, contextFilePath);
764
+ await fs.writeFile(fullPath, updatedContent, 'utf-8');
765
+ this.context.logger.info('Successfully migrated TB_ANALYSIS-IGNORE to TB-STATUS-IGNORE format');
766
+ }
767
+ catch (error) {
768
+ this.context.logger.error('Failed to migrate TB_ANALYSIS-IGNORE:', error);
769
+ throw error;
770
+ }
771
+ }
772
+ /**
773
+ * Get TB status from CLAUDE.md with fallback to legacy format
774
+ */
775
+ async getTBStatus(contextFilePath = 'CLAUDE.md') {
776
+ try {
777
+ const repoRoot = getRepoRoot();
778
+ const fullPath = path.join(repoRoot, contextFilePath);
779
+ const content = await fs.readFile(fullPath, 'utf-8');
780
+ // Try to extract TB status using existing method
781
+ const tbStatus = this.extractTBStatus(content);
782
+ if (tbStatus && !tbStatus.installedAgents) {
783
+ // Legacy format detected, recommend migration
784
+ this.context.logger.info('Legacy TB_ANALYSIS-IGNORE format detected. Consider migrating to TB-STATUS-IGNORE.');
785
+ }
786
+ return tbStatus;
787
+ }
788
+ catch (error) {
789
+ if (error.code === 'ENOENT') {
790
+ // File doesn't exist
791
+ return null;
792
+ }
793
+ this.context.logger.warn('Failed to parse TB status:', error);
794
+ return null;
795
+ }
796
+ }
797
+ /**
798
+ * Update repository context YAML section with current analysis
799
+ */
800
+ async updateRepositoryContext(analysis, contextFilePath = 'CLAUDE.md') {
801
+ try {
802
+ const repoRoot = getRepoRoot();
803
+ const fullPath = path.join(repoRoot, contextFilePath);
804
+ // Read existing content
805
+ let existingContent = '';
806
+ try {
807
+ existingContent = await fs.readFile(fullPath, 'utf-8');
808
+ }
809
+ catch {
810
+ // File doesn't exist
811
+ return;
812
+ }
813
+ // Generate new YAML content
814
+ const newYamlContent = this.formatRepositoryContextYaml(analysis);
815
+ // Find and replace the Repository Context section
816
+ const contextHeaderRegex = /### Repository Context\n```yaml\n.*?\n```/s;
817
+ const updatedContent = existingContent.replace(contextHeaderRegex, `### Repository Context\n${newYamlContent}`);
818
+ // Write updated content
819
+ await fs.writeFile(fullPath, updatedContent, 'utf-8');
820
+ this.context.logger.info('Updated repository context YAML section');
821
+ }
822
+ catch (error) {
823
+ this.context.logger.error('Failed to update repository context:', error);
824
+ throw error;
825
+ }
826
+ }
827
+ /**
828
+ * Format phase-based agent usage content
829
+ */
830
+ formatPhaseBasedAgentUsage(installedAgents, phaseStructure) {
831
+ // Suppress unused parameter warning - installedAgents used implicitly through phaseStructure
832
+ void installedAgents;
833
+ let content = '### When to Use Agents\n\n';
834
+ const phaseNames = {
835
+ ideation: 'Phase 1: Ideation',
836
+ development: 'Phase 2: Development',
837
+ testing: 'Phase 3: Testing & Quality',
838
+ operations: 'Phase 4: Operations & Analysis'
839
+ };
840
+ // Generate phase sections
841
+ Object.entries(phaseStructure.phases).forEach(([phaseKey, phase]) => {
842
+ const phaseName = phaseNames[phaseKey] || phaseKey;
843
+ content += `#### ${phaseName}\n`;
844
+ // List agents
845
+ phase.agents.forEach(agentName => {
846
+ const description = this.getAgentDescription(agentName);
847
+ content += `- \`${agentName}\` - ${description}\n`;
848
+ });
849
+ // Add handoffs if any
850
+ if (phase.handoffs.length > 0) {
851
+ content += `\n**${phaseName.split(':')[0]} Handoffs:**\n`;
852
+ phase.handoffs.forEach(handoff => {
853
+ content += `- ${handoff.replace(/(\w+)/g, '`$1`')}\n`;
854
+ });
855
+ }
856
+ content += '\n';
857
+ });
858
+ // Add cross-phase handoffs
859
+ if (phaseStructure.crossPhaseHandoffs.length > 0) {
860
+ content += '### Cross-Phase Handoffs\n';
861
+ phaseStructure.crossPhaseHandoffs.forEach(handoff => {
862
+ const fromPhase = phaseNames[handoff.from] || handoff.from;
863
+ const toPhase = phaseNames[handoff.to] || handoff.to;
864
+ content += `**${fromPhase.split(':')[1]?.trim()} → ${toPhase.split(':')[1]?.trim()}:**\n`;
865
+ content += `- ${handoff.description}\n\n`;
866
+ });
867
+ }
868
+ // Add prerequisites
869
+ if (Object.keys(phaseStructure.prerequisites).length > 0) {
870
+ content += '### Agent Prerequisites\n';
871
+ Object.entries(phaseStructure.prerequisites).forEach(([agent, prereqs]) => {
872
+ if (Array.isArray(prereqs)) {
873
+ const formattedPrereqs = prereqs.map(p => `\`${p}\``).join(', ');
874
+ content += `- \`${agent}\`: ${formattedPrereqs}\n`;
875
+ }
876
+ else if (typeof prereqs === 'string') {
877
+ content += `- \`${agent}\`: ${prereqs}\n`;
878
+ }
879
+ });
880
+ }
881
+ return content;
882
+ }
883
+ /**
884
+ * Update tiny-brain section in content with new agent usage
885
+ */
886
+ updateTinyBrainSection(existingContent, agentUsageContent) {
887
+ const startMarker = RepoService.REPO_BLOCK_START;
888
+ const endMarker = RepoService.REPO_BLOCK_END;
889
+ if (existingContent.includes(startMarker)) {
890
+ // Replace existing tiny-brain section
891
+ const startIndex = existingContent.indexOf(startMarker);
892
+ const endIndex = existingContent.indexOf(endMarker);
893
+ if (endIndex > -1) {
894
+ // Replace content between markers
895
+ const beforeSection = existingContent.substring(0, startIndex);
896
+ const afterSection = existingContent.substring(endIndex + endMarker.length);
897
+ return beforeSection + this.buildTinyBrainSection(agentUsageContent) + afterSection;
898
+ }
899
+ }
900
+ // Add new tiny-brain section
901
+ return existingContent.trimEnd() + '\n\n' + this.buildTinyBrainSection(agentUsageContent);
902
+ }
903
+ /**
904
+ * Build complete tiny-brain section
905
+ */
906
+ buildTinyBrainSection(agentUsageContent) {
907
+ return `${RepoService.REPO_BLOCK_START}\n` +
908
+ `${RepoService.SUBAGENT_PROTOCOL_HEADER}\n\n` +
909
+ agentUsageContent + '\n' +
910
+ `${RepoService.REPO_BLOCK_END}`;
911
+ }
912
+ /**
913
+ * Add TB status to content (create tiny-brain section if needed)
914
+ */
915
+ addStatusToContent(existingContent, statusIgnore) {
916
+ const startMarker = RepoService.REPO_BLOCK_START;
917
+ const endMarker = RepoService.REPO_BLOCK_END;
918
+ if (existingContent.includes(startMarker) && existingContent.includes(endMarker)) {
919
+ // Add before end marker
920
+ const endIndex = existingContent.indexOf(endMarker);
921
+ return existingContent.substring(0, endIndex) +
922
+ statusIgnore + '\n' +
923
+ existingContent.substring(endIndex);
924
+ }
925
+ else {
926
+ // Create minimal tiny-brain section
927
+ return existingContent.trimEnd() + '\n\n' +
928
+ `${startMarker}\n${statusIgnore}\n${endMarker}\n`;
929
+ }
930
+ }
931
+ /**
932
+ * Generate handoffs for agents within a phase
933
+ */
934
+ generatePhaseHandoffs(agents) {
935
+ const handoffs = [];
936
+ // Generate common handoff patterns
937
+ if (agents.includes('prd-researcher') && agents.includes('system-architect')) {
938
+ handoffs.push('prd-researcher → system-architect: Requirements analysis to system design');
939
+ }
940
+ if (agents.includes('system-architect') && agents.includes('backend-architect')) {
941
+ handoffs.push('system-architect → backend-architect: System design to backend architecture');
942
+ }
943
+ if (agents.includes('feature-developer') && agents.includes('component-developer')) {
944
+ handoffs.push('feature-developer → component-developer: Feature specs to UI implementation');
945
+ }
946
+ if (agents.includes('feature-developer') && agents.includes('typescript-developer')) {
947
+ handoffs.push('feature-developer → typescript-developer: Feature specs to type-safe implementation');
948
+ }
949
+ return handoffs;
950
+ }
951
+ /**
952
+ * Generate cross-phase handoffs based on available phases
953
+ */
954
+ generateCrossPhaseHandoffs(phases) {
955
+ const handoffs = [];
956
+ if (phases.ideation && phases.development) {
957
+ handoffs.push({
958
+ from: 'ideation',
959
+ to: 'development',
960
+ description: 'Architecture outputs → Development agents'
961
+ });
962
+ }
963
+ if (phases.development && phases.testing) {
964
+ handoffs.push({
965
+ from: 'development',
966
+ to: 'testing',
967
+ description: 'Implementation outputs → Testing agents'
968
+ });
969
+ }
970
+ if (phases.testing && phases.operations) {
971
+ handoffs.push({
972
+ from: 'testing',
973
+ to: 'operations',
974
+ description: 'Quality metrics → Operational monitoring'
975
+ });
976
+ }
977
+ return handoffs;
978
+ }
979
+ /**
980
+ * Generate prerequisites for agents
981
+ */
982
+ generatePrerequisites(installedAgents) {
983
+ const prerequisites = {};
984
+ const agentNames = installedAgents.map(a => a.name);
985
+ agentNames.forEach(agent => {
986
+ if (agent === 'feature-developer' && agentNames.includes('system-architect')) {
987
+ prerequisites[agent] = ['system-architect'];
988
+ }
989
+ if (agent === 'component-developer' && agentNames.includes('system-architect')) {
990
+ prerequisites[agent] = ['system-architect'];
991
+ }
992
+ if (agent === 'typescript-developer' &&
993
+ (agentNames.includes('backend-architect') || agentNames.includes('system-architect'))) {
994
+ prerequisites[agent] = agentNames.includes('backend-architect') ?
995
+ ['backend-architect', 'system-architect'] : ['system-architect'];
996
+ }
997
+ if (agent === 'tdd-validator' && agentNames.some(a => a.includes('developer'))) {
998
+ prerequisites[agent] = agentNames.filter(a => a.includes('developer'));
999
+ }
1000
+ });
1001
+ return prerequisites;
1002
+ }
1003
+ /**
1004
+ * Get description for an agent (placeholder - could be enhanced)
1005
+ */
1006
+ getAgentDescription(agentName) {
1007
+ const descriptions = {
1008
+ 'prd-researcher': 'Research and analyze Product Requirements Documents to generate comprehensive technical tasks and implementation strategies',
1009
+ 'system-architect': 'Design comprehensive system architectures that integrate frontend, backend, and infrastructure components',
1010
+ 'backend-architect': 'Design and architect reliable backend systems with focus on data integrity, security, and fault tolerance',
1011
+ 'feature-developer': 'Feature implementation specialist who transforms requirements into working functionality',
1012
+ 'component-developer': 'UI component development specialist focused on building reusable, accessible, and well-tested components',
1013
+ 'typescript-developer': 'TypeScript development specialist focused on type-safe, maintainable code following modern JavaScript/TypeScript best practices',
1014
+ 'tdd-validator': 'Test-Driven Development methodology validator ensuring rigorous TDD compliance and quality gates',
1015
+ 'functional-tester': 'Functional testing specialist who validates software functionality against requirements',
1016
+ 'refactoring-expert': 'Code refactoring specialist focused on improving code quality, maintainability, and performance without changing functionality',
1017
+ 'metrics-collector': 'Metrics and observability specialist who gathers, analyzes, and reports on system and business metrics',
1018
+ 'root-cause-analyst': 'Incident investigation specialist who diagnoses system issues and identifies root causes'
1019
+ };
1020
+ return descriptions[agentName] || `${agentName.replace(/-/g, ' ')} specialist`;
1021
+ }
1022
+ }
1023
+ //# sourceMappingURL=repo-service.js.map