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