@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.
- package/dist/agents/formatters/claude-code-formatter.d.ts +37 -0
- package/dist/agents/formatters/claude-code-formatter.d.ts.map +1 -0
- package/dist/agents/formatters/claude-code-formatter.js +217 -0
- package/dist/agents/formatters/claude-code-formatter.js.map +1 -0
- package/dist/agents/formatters/formatter-factory.d.ts +25 -0
- package/dist/agents/formatters/formatter-factory.d.ts.map +1 -0
- package/dist/agents/formatters/formatter-factory.js +61 -0
- package/dist/agents/formatters/formatter-factory.js.map +1 -0
- package/dist/agents/types.d.ts +68 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/types.js +12 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/analyser/analyzers/script-analyzer.d.ts +10 -0
- package/dist/analyser/analyzers/script-analyzer.d.ts.map +1 -0
- package/dist/analyser/analyzers/script-analyzer.js +205 -0
- package/dist/analyser/analyzers/script-analyzer.js.map +1 -0
- package/dist/analyser/detectors/base-detector.d.ts +12 -0
- package/dist/analyser/detectors/base-detector.d.ts.map +1 -0
- package/dist/analyser/detectors/base-detector.js +50 -0
- package/dist/analyser/detectors/base-detector.js.map +1 -0
- package/dist/analyser/detectors/javascript-detector.d.ts +19 -0
- package/dist/analyser/detectors/javascript-detector.d.ts.map +1 -0
- package/dist/analyser/detectors/javascript-detector.js +347 -0
- package/dist/analyser/detectors/javascript-detector.js.map +1 -0
- package/dist/analyser/index.d.ts +5 -0
- package/dist/analyser/index.d.ts.map +1 -0
- package/dist/analyser/index.js +315 -0
- package/dist/analyser/index.js.map +1 -0
- package/dist/analyser/types.d.ts +2 -0
- package/dist/analyser/types.d.ts.map +1 -0
- package/dist/analyser/types.js +2 -0
- package/dist/analyser/types.js.map +1 -0
- package/dist/analyser/utils.d.ts +5 -0
- package/dist/analyser/utils.d.ts.map +1 -0
- package/dist/analyser/utils.js +24 -0
- package/dist/analyser/utils.js.map +1 -0
- package/dist/cli/cli-factory.d.ts.map +1 -1
- package/dist/cli/cli-factory.js +17 -0
- package/dist/cli/cli-factory.js.map +1 -1
- package/dist/cli/commands/analyse.command.d.ts +7 -0
- package/dist/cli/commands/analyse.command.d.ts.map +1 -0
- package/dist/cli/commands/analyse.command.js +130 -0
- package/dist/cli/commands/analyse.command.js.map +1 -0
- package/dist/cli/commands/status.command.d.ts.map +1 -1
- package/dist/cli/commands/status.command.js +3 -1
- package/dist/cli/commands/status.command.js.map +1 -1
- package/dist/core/mcp-server.d.ts +10 -8
- package/dist/core/mcp-server.d.ts.map +1 -1
- package/dist/core/mcp-server.js +93 -85
- package/dist/core/mcp-server.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -8
- package/dist/index.js.map +1 -1
- package/dist/prompts/persona/persona.prompt.js +8 -8
- package/dist/prompts/persona/persona.prompt.js.map +1 -1
- package/dist/prompts/planning/planning.prompt.d.ts +0 -8
- package/dist/prompts/planning/planning.prompt.d.ts.map +1 -1
- package/dist/prompts/planning/planning.prompt.js +0 -175
- package/dist/prompts/planning/planning.prompt.js.map +1 -1
- package/dist/services/agent-installation-service.d.ts +101 -0
- package/dist/services/agent-installation-service.d.ts.map +1 -0
- package/dist/services/agent-installation-service.js +328 -0
- package/dist/services/agent-installation-service.js.map +1 -0
- package/dist/services/agent-manager.d.ts +45 -0
- package/dist/services/agent-manager.d.ts.map +1 -0
- package/dist/services/agent-manager.js +154 -0
- package/dist/services/agent-manager.js.map +1 -0
- package/dist/services/agent-service.d.ts +70 -0
- package/dist/services/agent-service.d.ts.map +1 -0
- package/dist/services/agent-service.js +273 -0
- package/dist/services/agent-service.js.map +1 -0
- package/dist/services/analyse-service.d.ts +97 -0
- package/dist/services/analyse-service.d.ts.map +1 -0
- package/dist/services/analyse-service.js +370 -0
- package/dist/services/analyse-service.js.map +1 -0
- package/dist/services/dashboard-launcher.service.d.ts +20 -0
- package/dist/services/dashboard-launcher.service.d.ts.map +1 -0
- package/dist/services/dashboard-launcher.service.js +30 -0
- package/dist/services/dashboard-launcher.service.js.map +1 -0
- package/dist/services/persona-enhancer.d.ts +52 -0
- package/dist/services/persona-enhancer.d.ts.map +1 -0
- package/dist/services/persona-enhancer.js +252 -0
- package/dist/services/persona-enhancer.js.map +1 -0
- package/dist/services/persona-grouper.d.ts +29 -0
- package/dist/services/persona-grouper.d.ts.map +1 -0
- package/dist/services/persona-grouper.js +111 -0
- package/dist/services/persona-grouper.js.map +1 -0
- package/dist/services/persona-service.d.ts +52 -0
- package/dist/services/persona-service.d.ts.map +1 -0
- package/dist/services/{enhanced-persona-service.js → persona-service.js} +125 -7
- package/dist/services/persona-service.js.map +1 -0
- package/dist/services/remote/auth-token-service.d.ts.map +1 -1
- package/dist/services/remote/auth-token-service.js +10 -3
- package/dist/services/remote/auth-token-service.js.map +1 -1
- package/dist/services/remote/system-persona-service.d.ts.map +1 -1
- package/dist/services/remote/system-persona-service.js +41 -10
- package/dist/services/remote/system-persona-service.js.map +1 -1
- package/dist/services/repo-service.d.ts +195 -0
- package/dist/services/repo-service.d.ts.map +1 -0
- package/dist/services/repo-service.js +1023 -0
- package/dist/services/repo-service.js.map +1 -0
- package/dist/services/types/persona-types.d.ts +84 -0
- package/dist/services/types/persona-types.d.ts.map +1 -0
- package/dist/services/types/persona-types.js +5 -0
- package/dist/services/types/persona-types.js.map +1 -0
- package/dist/services/versioning-service.d.ts +79 -0
- package/dist/services/versioning-service.d.ts.map +1 -0
- package/dist/services/versioning-service.js +191 -0
- package/dist/services/versioning-service.js.map +1 -0
- package/dist/storage/local-filesystem-adapter.d.ts +1 -0
- package/dist/storage/local-filesystem-adapter.d.ts.map +1 -1
- package/dist/storage/local-filesystem-adapter.js +47 -3
- package/dist/storage/local-filesystem-adapter.js.map +1 -1
- package/dist/storage/platform-config-adapter.d.ts +9 -0
- package/dist/storage/platform-config-adapter.d.ts.map +1 -1
- package/dist/storage/platform-config-adapter.js +55 -1
- package/dist/storage/platform-config-adapter.js.map +1 -1
- package/dist/tools/analyse.tool.d.ts +17 -0
- package/dist/tools/analyse.tool.d.ts.map +1 -0
- package/dist/tools/analyse.tool.js +124 -0
- package/dist/tools/analyse.tool.js.map +1 -0
- package/dist/tools/persona/as.tool.d.ts +32 -11
- package/dist/tools/persona/as.tool.d.ts.map +1 -1
- package/dist/tools/persona/as.tool.js +452 -317
- package/dist/tools/persona/as.tool.js.map +1 -1
- package/dist/tools/persona/persona.tool.js +2 -2
- package/dist/tools/persona/persona.tool.js.map +1 -1
- package/dist/tools/plan/plan.tool.d.ts +3 -3
- package/dist/tools/plan/plan.tool.d.ts.map +1 -1
- package/dist/tools/plan/plan.tool.js +78 -55
- package/dist/tools/plan/plan.tool.js.map +1 -1
- package/dist/tools/tool-registry.d.ts.map +1 -1
- package/dist/tools/tool-registry.js +4 -0
- package/dist/tools/tool-registry.js.map +1 -1
- package/dist/utils/repo-utils.d.ts +10 -0
- package/dist/utils/repo-utils.d.ts.map +1 -0
- package/dist/utils/repo-utils.js +55 -0
- package/dist/utils/repo-utils.js.map +1 -0
- package/package.json +6 -2
- package/dist/services/enhanced-persona-service.d.ts +0 -22
- package/dist/services/enhanced-persona-service.d.ts.map +0 -1
- package/dist/services/enhanced-persona-service.js.map +0 -1
- package/dist/services/plan-watcher.service.d.ts +0 -141
- package/dist/services/plan-watcher.service.d.ts.map +0 -1
- package/dist/services/plan-watcher.service.js +0 -1010
- 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
|