@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
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tech Context Service
|
|
3
|
+
*
|
|
4
|
+
* Manages reading and writing of tech context files:
|
|
5
|
+
* - .tiny-brain/analysis.json - repo analysis data
|
|
6
|
+
* - .tiny-brain/tech/*.md - per-tech expertise files
|
|
7
|
+
* - .tiny-brain/tech/config.json - agent mode configuration
|
|
8
|
+
* - .claude/agents/tech-*.md - tech agent files (when enableAgentic=true)
|
|
9
|
+
*/
|
|
10
|
+
import type { RepoAnalysisFile, TechExpertiseFrontmatter, TechExpertiseFile, TechConfig } from '@magic-ingredients/tiny-brain-core';
|
|
11
|
+
/** Input analysis data from repository analysis */
|
|
12
|
+
export interface AnalysisInput {
|
|
13
|
+
languages: string[];
|
|
14
|
+
frameworks: string[];
|
|
15
|
+
testingTools: string[];
|
|
16
|
+
buildTools: string[];
|
|
17
|
+
hasTests: boolean;
|
|
18
|
+
testFileCount: number;
|
|
19
|
+
testPatterns: string[];
|
|
20
|
+
isPolyglot?: boolean;
|
|
21
|
+
primaryLanguage?: string;
|
|
22
|
+
documentationPattern?: 'single-readme' | 'folder-readmes' | 'docs-folder' | 'mixed';
|
|
23
|
+
documentationLocations?: string[];
|
|
24
|
+
}
|
|
25
|
+
export declare class TechContextService {
|
|
26
|
+
private readonly tinyBrainDir;
|
|
27
|
+
private readonly techDir;
|
|
28
|
+
private readonly agentsDir;
|
|
29
|
+
constructor(repoPath: string);
|
|
30
|
+
/** Get the .tiny-brain directory path */
|
|
31
|
+
getTinyBrainDir(): string;
|
|
32
|
+
/** Get the .tiny-brain/tech directory path */
|
|
33
|
+
getTechDir(): string;
|
|
34
|
+
/** Get the .claude/agents directory path */
|
|
35
|
+
getAgentsDir(): string;
|
|
36
|
+
/** Ensure required directories exist */
|
|
37
|
+
ensureDirectories(): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Write analysis data to .tiny-brain/analysis.json
|
|
40
|
+
*/
|
|
41
|
+
writeAnalysis(analysis: AnalysisInput): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Write a tech expertise file with YAML frontmatter
|
|
44
|
+
*/
|
|
45
|
+
writeTechFile(name: string, frontmatter: TechExpertiseFrontmatter, content: string): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Write raw markdown content to a tech file
|
|
48
|
+
* Used when receiving complete markdown from TBR API
|
|
49
|
+
*/
|
|
50
|
+
writeTechFileRaw(name: string, content: string): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Read analysis data from .tiny-brain/analysis.json
|
|
53
|
+
*/
|
|
54
|
+
readAnalysis(): Promise<RepoAnalysisFile | null>;
|
|
55
|
+
/**
|
|
56
|
+
* Read all tech expertise files from .tiny-brain/tech/
|
|
57
|
+
*/
|
|
58
|
+
readTechFiles(): Promise<TechExpertiseFile[]>;
|
|
59
|
+
/**
|
|
60
|
+
* Get tech files that match a given file path based on filePatterns
|
|
61
|
+
*/
|
|
62
|
+
getTechForFile(filePath: string): Promise<TechExpertiseFile[]>;
|
|
63
|
+
/**
|
|
64
|
+
* Check if the tech stack has changed compared to previous analysis
|
|
65
|
+
*/
|
|
66
|
+
hasStackChanged(analysis: AnalysisInput): Promise<boolean>;
|
|
67
|
+
/**
|
|
68
|
+
* Write config to .tiny-brain/tech/config.json
|
|
69
|
+
*/
|
|
70
|
+
writeConfig(config: Omit<TechConfig, 'lastSynced'>): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Read config from .tiny-brain/tech/config.json
|
|
73
|
+
*/
|
|
74
|
+
readConfig(): Promise<TechConfig>;
|
|
75
|
+
/**
|
|
76
|
+
* Install tech agents to .claude/agents/
|
|
77
|
+
* Converts tech context files into proper Claude Code sub-agents with:
|
|
78
|
+
* - name: tech-{name} (matches subagent_type="tech-react")
|
|
79
|
+
* - description: for Claude Code auto-delegation
|
|
80
|
+
* - Sub-agent invocation context
|
|
81
|
+
*/
|
|
82
|
+
installTechAgents(): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Remove only tech-*.md files from .claude/agents/
|
|
85
|
+
*/
|
|
86
|
+
removeTechAgents(): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Sync agents based on enableAgentic preference
|
|
89
|
+
*/
|
|
90
|
+
syncAgents(enableAgentic: boolean): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Get versions of all local tech context files
|
|
93
|
+
* @returns Map of tech name → version
|
|
94
|
+
*/
|
|
95
|
+
getLocalTechVersions(): Promise<Map<string, string>>;
|
|
96
|
+
/**
|
|
97
|
+
* Compare versions to determine if TBS version is newer
|
|
98
|
+
* @returns true if tbsVersion is newer than localVersion
|
|
99
|
+
*/
|
|
100
|
+
shouldUpdateTech(localVersion: string, tbsVersion: string): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Parse YAML frontmatter from a markdown file
|
|
103
|
+
*/
|
|
104
|
+
private parseFrontmatter;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=tech-context-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tech-context-service.d.ts","sourceRoot":"","sources":["../../src/services/tech-context-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EACV,gBAAgB,EAGhB,wBAAwB,EACxB,iBAAiB,EACjB,UAAU,EACX,MAAM,oCAAoC,CAAC;AAE5C,mDAAmD;AACnD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,eAAe,GAAG,gBAAgB,GAAG,aAAa,GAAG,OAAO,CAAC;IACpF,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC;CACnC;AAED,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,QAAQ,EAAE,MAAM;IAM5B,yCAAyC;IACzC,eAAe,IAAI,MAAM;IAIzB,8CAA8C;IAC9C,UAAU,IAAI,MAAM;IAIpB,4CAA4C;IAC5C,YAAY,IAAI,MAAM;IAItB,wCAAwC;IAClC,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAKxC;;OAEG;IACG,aAAa,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAoC3D;;OAEG;IACG,aAAa,CACjB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,wBAAwB,EACrC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC;IAmBhB;;;OAGG;IACG,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMpE;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAWtD;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IA0BnD;;OAEG;IACG,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAwBpE;;OAEG;IACG,eAAe,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IA4BhE;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAYxE;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC;IAWvC;;;;;;OAMG;IACG,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAkCxC;;OAEG;IACG,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAcvC;;OAEG;IACG,UAAU,CAAC,aAAa,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvD;;;OAGG;IACG,oBAAoB,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAW1D;;;OAGG;IACH,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO;IAsBnE;;OAEG;IACH,OAAO,CAAC,gBAAgB;CA+CzB"}
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tech Context Service
|
|
3
|
+
*
|
|
4
|
+
* Manages reading and writing of tech context files:
|
|
5
|
+
* - .tiny-brain/analysis.json - repo analysis data
|
|
6
|
+
* - .tiny-brain/tech/*.md - per-tech expertise files
|
|
7
|
+
* - .tiny-brain/tech/config.json - agent mode configuration
|
|
8
|
+
* - .claude/agents/tech-*.md - tech agent files (when enableAgentic=true)
|
|
9
|
+
*/
|
|
10
|
+
import { promises as fs } from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import crypto from 'crypto';
|
|
13
|
+
import minimatch from 'minimatch';
|
|
14
|
+
export class TechContextService {
|
|
15
|
+
tinyBrainDir;
|
|
16
|
+
techDir;
|
|
17
|
+
agentsDir;
|
|
18
|
+
constructor(repoPath) {
|
|
19
|
+
this.tinyBrainDir = path.join(repoPath, '.tiny-brain');
|
|
20
|
+
this.techDir = path.join(this.tinyBrainDir, 'tech');
|
|
21
|
+
this.agentsDir = path.join(repoPath, '.claude', 'agents');
|
|
22
|
+
}
|
|
23
|
+
/** Get the .tiny-brain directory path */
|
|
24
|
+
getTinyBrainDir() {
|
|
25
|
+
return this.tinyBrainDir;
|
|
26
|
+
}
|
|
27
|
+
/** Get the .tiny-brain/tech directory path */
|
|
28
|
+
getTechDir() {
|
|
29
|
+
return this.techDir;
|
|
30
|
+
}
|
|
31
|
+
/** Get the .claude/agents directory path */
|
|
32
|
+
getAgentsDir() {
|
|
33
|
+
return this.agentsDir;
|
|
34
|
+
}
|
|
35
|
+
/** Ensure required directories exist */
|
|
36
|
+
async ensureDirectories() {
|
|
37
|
+
await fs.mkdir(this.tinyBrainDir, { recursive: true });
|
|
38
|
+
await fs.mkdir(this.techDir, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Write analysis data to .tiny-brain/analysis.json
|
|
42
|
+
*/
|
|
43
|
+
async writeAnalysis(analysis) {
|
|
44
|
+
await this.ensureDirectories();
|
|
45
|
+
const stack = {
|
|
46
|
+
languages: analysis.languages,
|
|
47
|
+
frameworks: analysis.frameworks,
|
|
48
|
+
testing: analysis.testingTools,
|
|
49
|
+
build: analysis.buildTools,
|
|
50
|
+
};
|
|
51
|
+
const analysisData = {
|
|
52
|
+
hasTests: analysis.hasTests,
|
|
53
|
+
testFileCount: analysis.testFileCount,
|
|
54
|
+
testPatterns: analysis.testPatterns,
|
|
55
|
+
isPolyglot: analysis.isPolyglot ?? false,
|
|
56
|
+
primaryLanguage: analysis.primaryLanguage ?? analysis.languages[0] ?? 'unknown',
|
|
57
|
+
documentationPattern: analysis.documentationPattern,
|
|
58
|
+
documentationLocations: analysis.documentationLocations,
|
|
59
|
+
};
|
|
60
|
+
// Generate hash from stable analysis input
|
|
61
|
+
const hashInput = JSON.stringify({ stack, analysis: analysisData });
|
|
62
|
+
const analysisHash = crypto.createHash('sha256').update(hashInput).digest('hex');
|
|
63
|
+
const file = {
|
|
64
|
+
version: '1.0',
|
|
65
|
+
detectedAt: new Date().toISOString(),
|
|
66
|
+
analysisHash,
|
|
67
|
+
stack,
|
|
68
|
+
analysis: analysisData,
|
|
69
|
+
};
|
|
70
|
+
const filePath = path.join(this.tinyBrainDir, 'analysis.json');
|
|
71
|
+
await fs.writeFile(filePath, JSON.stringify(file, null, 2), 'utf-8');
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Write a tech expertise file with YAML frontmatter
|
|
75
|
+
*/
|
|
76
|
+
async writeTechFile(name, frontmatter, content) {
|
|
77
|
+
await this.ensureDirectories();
|
|
78
|
+
const yamlFrontmatter = [
|
|
79
|
+
'---',
|
|
80
|
+
`name: ${frontmatter.name}`,
|
|
81
|
+
`version: ${frontmatter.version}`,
|
|
82
|
+
`domain: ${frontmatter.domain}`,
|
|
83
|
+
`filePatterns:`,
|
|
84
|
+
...frontmatter.filePatterns.map(p => ` - "${p}"`),
|
|
85
|
+
frontmatter.description ? `description: "${frontmatter.description}"` : null,
|
|
86
|
+
'---',
|
|
87
|
+
].filter(Boolean).join('\n');
|
|
88
|
+
const fileContent = `${yamlFrontmatter}\n\n${content}`;
|
|
89
|
+
const filePath = path.join(this.techDir, `${name}.md`);
|
|
90
|
+
await fs.writeFile(filePath, fileContent, 'utf-8');
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Write raw markdown content to a tech file
|
|
94
|
+
* Used when receiving complete markdown from TBR API
|
|
95
|
+
*/
|
|
96
|
+
async writeTechFileRaw(name, content) {
|
|
97
|
+
await this.ensureDirectories();
|
|
98
|
+
const filePath = path.join(this.techDir, `${name}.md`);
|
|
99
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Read analysis data from .tiny-brain/analysis.json
|
|
103
|
+
*/
|
|
104
|
+
async readAnalysis() {
|
|
105
|
+
const filePath = path.join(this.tinyBrainDir, 'analysis.json');
|
|
106
|
+
try {
|
|
107
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
108
|
+
return JSON.parse(content);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Read all tech expertise files from .tiny-brain/tech/
|
|
116
|
+
*/
|
|
117
|
+
async readTechFiles() {
|
|
118
|
+
try {
|
|
119
|
+
const files = await fs.readdir(this.techDir);
|
|
120
|
+
const techFiles = [];
|
|
121
|
+
for (const file of files) {
|
|
122
|
+
if (!file.endsWith('.md'))
|
|
123
|
+
continue;
|
|
124
|
+
const filePath = path.join(this.techDir, file);
|
|
125
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
126
|
+
const parsed = this.parseFrontmatter(content);
|
|
127
|
+
if (parsed) {
|
|
128
|
+
techFiles.push({
|
|
129
|
+
...parsed,
|
|
130
|
+
filePath,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return techFiles;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get tech files that match a given file path based on filePatterns
|
|
142
|
+
*/
|
|
143
|
+
async getTechForFile(filePath) {
|
|
144
|
+
const techFiles = await this.readTechFiles();
|
|
145
|
+
const matches = [];
|
|
146
|
+
const basename = path.basename(filePath);
|
|
147
|
+
for (const techFile of techFiles) {
|
|
148
|
+
for (const pattern of techFile.frontmatter.filePatterns) {
|
|
149
|
+
// Check multiple matching strategies:
|
|
150
|
+
// 1. Full path glob match
|
|
151
|
+
// 2. Basename match (for patterns like *.tsx)
|
|
152
|
+
// 3. Directory prefix match (for patterns like components/)
|
|
153
|
+
if (minimatch(filePath, pattern) ||
|
|
154
|
+
minimatch(basename, pattern) ||
|
|
155
|
+
minimatch(filePath, `**/${pattern}`) ||
|
|
156
|
+
filePath.includes(pattern.replace(/\/$/, ''))) {
|
|
157
|
+
matches.push(techFile);
|
|
158
|
+
break; // Don't add same file multiple times
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return matches;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Check if the tech stack has changed compared to previous analysis
|
|
166
|
+
*/
|
|
167
|
+
async hasStackChanged(analysis) {
|
|
168
|
+
const existing = await this.readAnalysis();
|
|
169
|
+
if (!existing)
|
|
170
|
+
return true;
|
|
171
|
+
// Generate hash for new analysis using same logic as writeAnalysis
|
|
172
|
+
const stack = {
|
|
173
|
+
languages: analysis.languages,
|
|
174
|
+
frameworks: analysis.frameworks,
|
|
175
|
+
testing: analysis.testingTools,
|
|
176
|
+
build: analysis.buildTools,
|
|
177
|
+
};
|
|
178
|
+
const analysisData = {
|
|
179
|
+
hasTests: analysis.hasTests,
|
|
180
|
+
testFileCount: analysis.testFileCount,
|
|
181
|
+
testPatterns: analysis.testPatterns,
|
|
182
|
+
isPolyglot: analysis.isPolyglot ?? false,
|
|
183
|
+
primaryLanguage: analysis.primaryLanguage ?? analysis.languages[0] ?? 'unknown',
|
|
184
|
+
documentationPattern: analysis.documentationPattern,
|
|
185
|
+
documentationLocations: analysis.documentationLocations,
|
|
186
|
+
};
|
|
187
|
+
const hashInput = JSON.stringify({ stack, analysis: analysisData });
|
|
188
|
+
const newHash = crypto.createHash('sha256').update(hashInput).digest('hex');
|
|
189
|
+
return newHash !== existing.analysisHash;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Write config to .tiny-brain/tech/config.json
|
|
193
|
+
*/
|
|
194
|
+
async writeConfig(config) {
|
|
195
|
+
await this.ensureDirectories();
|
|
196
|
+
const fullConfig = {
|
|
197
|
+
...config,
|
|
198
|
+
lastSynced: new Date().toISOString(),
|
|
199
|
+
};
|
|
200
|
+
const filePath = path.join(this.techDir, 'config.json');
|
|
201
|
+
await fs.writeFile(filePath, JSON.stringify(fullConfig, null, 2), 'utf-8');
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Read config from .tiny-brain/tech/config.json
|
|
205
|
+
*/
|
|
206
|
+
async readConfig() {
|
|
207
|
+
const filePath = path.join(this.techDir, 'config.json');
|
|
208
|
+
try {
|
|
209
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
210
|
+
return JSON.parse(content);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return { useAgents: false };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Install tech agents to .claude/agents/
|
|
218
|
+
* Converts tech context files into proper Claude Code sub-agents with:
|
|
219
|
+
* - name: tech-{name} (matches subagent_type="tech-react")
|
|
220
|
+
* - description: for Claude Code auto-delegation
|
|
221
|
+
* - Sub-agent invocation context
|
|
222
|
+
*/
|
|
223
|
+
async installTechAgents() {
|
|
224
|
+
await fs.mkdir(this.agentsDir, { recursive: true });
|
|
225
|
+
const techFiles = await this.readTechFiles();
|
|
226
|
+
for (const techFile of techFiles) {
|
|
227
|
+
const name = techFile.frontmatter.name;
|
|
228
|
+
const agentFileName = `tech-${name}.md`;
|
|
229
|
+
const agentPath = path.join(this.agentsDir, agentFileName);
|
|
230
|
+
// Build description from frontmatter or generate default
|
|
231
|
+
const description = techFile.frontmatter.description
|
|
232
|
+
|| `${name} development specialist. Use for ${techFile.frontmatter.domain} tasks involving ${name}.`;
|
|
233
|
+
// Create agent file with proper Claude Code sub-agent format
|
|
234
|
+
const agentContent = `---
|
|
235
|
+
name: tech-${name}
|
|
236
|
+
description: ${description}
|
|
237
|
+
version: ${techFile.frontmatter.version}
|
|
238
|
+
domain: ${techFile.frontmatter.domain}
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
# ${name} Sub-Agent
|
|
242
|
+
|
|
243
|
+
You are a specialized ${name} development agent invoked by the developer agent. Apply the expertise and patterns below to the task you've been given.
|
|
244
|
+
|
|
245
|
+
## Tech Expertise
|
|
246
|
+
|
|
247
|
+
${techFile.content}`;
|
|
248
|
+
await fs.writeFile(agentPath, agentContent, 'utf-8');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Remove only tech-*.md files from .claude/agents/
|
|
253
|
+
*/
|
|
254
|
+
async removeTechAgents() {
|
|
255
|
+
try {
|
|
256
|
+
const files = await fs.readdir(this.agentsDir);
|
|
257
|
+
for (const file of files) {
|
|
258
|
+
if (file.startsWith('tech-') && file.endsWith('.md')) {
|
|
259
|
+
await fs.unlink(path.join(this.agentsDir, file));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// Directory may not exist, ignore
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Sync agents based on enableAgentic preference
|
|
269
|
+
*/
|
|
270
|
+
async syncAgents(enableAgentic) {
|
|
271
|
+
await this.writeConfig({ useAgents: enableAgentic });
|
|
272
|
+
if (enableAgentic) {
|
|
273
|
+
await this.installTechAgents();
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
await this.removeTechAgents();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Get versions of all local tech context files
|
|
281
|
+
* @returns Map of tech name → version
|
|
282
|
+
*/
|
|
283
|
+
async getLocalTechVersions() {
|
|
284
|
+
const techFiles = await this.readTechFiles();
|
|
285
|
+
const versions = new Map();
|
|
286
|
+
for (const file of techFiles) {
|
|
287
|
+
versions.set(file.frontmatter.name, file.frontmatter.version);
|
|
288
|
+
}
|
|
289
|
+
return versions;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Compare versions to determine if TBS version is newer
|
|
293
|
+
* @returns true if tbsVersion is newer than localVersion
|
|
294
|
+
*/
|
|
295
|
+
shouldUpdateTech(localVersion, tbsVersion) {
|
|
296
|
+
const parseVersion = (v) => {
|
|
297
|
+
return v.split('.').map(s => parseInt(s, 10) || 0);
|
|
298
|
+
};
|
|
299
|
+
const local = parseVersion(localVersion);
|
|
300
|
+
const tbs = parseVersion(tbsVersion);
|
|
301
|
+
// Pad arrays to same length
|
|
302
|
+
const maxLen = Math.max(local.length, tbs.length);
|
|
303
|
+
while (local.length < maxLen)
|
|
304
|
+
local.push(0);
|
|
305
|
+
while (tbs.length < maxLen)
|
|
306
|
+
tbs.push(0);
|
|
307
|
+
// Compare each segment
|
|
308
|
+
for (let i = 0; i < maxLen; i++) {
|
|
309
|
+
if (tbs[i] > local[i])
|
|
310
|
+
return true;
|
|
311
|
+
if (tbs[i] < local[i])
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
return false; // Versions are equal
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Parse YAML frontmatter from a markdown file
|
|
318
|
+
*/
|
|
319
|
+
parseFrontmatter(content) {
|
|
320
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n\n?([\s\S]*)$/);
|
|
321
|
+
if (!match)
|
|
322
|
+
return null;
|
|
323
|
+
const yamlContent = match[1];
|
|
324
|
+
const markdownContent = match[2];
|
|
325
|
+
// Simple YAML parsing for our known structure
|
|
326
|
+
const frontmatter = {};
|
|
327
|
+
const lines = yamlContent.split('\n');
|
|
328
|
+
let currentKey = '';
|
|
329
|
+
let filePatterns = [];
|
|
330
|
+
for (const line of lines) {
|
|
331
|
+
const keyMatch = line.match(/^(\w+):\s*(.*)$/);
|
|
332
|
+
if (keyMatch) {
|
|
333
|
+
currentKey = keyMatch[1];
|
|
334
|
+
const value = keyMatch[2].replace(/^["']|["']$/g, '');
|
|
335
|
+
if (currentKey === 'filePatterns') {
|
|
336
|
+
filePatterns = [];
|
|
337
|
+
}
|
|
338
|
+
else if (currentKey === 'name') {
|
|
339
|
+
frontmatter.name = value;
|
|
340
|
+
}
|
|
341
|
+
else if (currentKey === 'version') {
|
|
342
|
+
frontmatter.version = value;
|
|
343
|
+
}
|
|
344
|
+
else if (currentKey === 'domain') {
|
|
345
|
+
frontmatter.domain = value;
|
|
346
|
+
}
|
|
347
|
+
else if (currentKey === 'description') {
|
|
348
|
+
frontmatter.description = value;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
else if (currentKey === 'filePatterns' && line.trim().startsWith('-')) {
|
|
352
|
+
const pattern = line.trim().replace(/^-\s*/, '').replace(/^["']|["']$/g, '');
|
|
353
|
+
filePatterns.push(pattern);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
frontmatter.filePatterns = filePatterns;
|
|
357
|
+
if (!frontmatter.name || !frontmatter.version || !frontmatter.domain) {
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
frontmatter: frontmatter,
|
|
362
|
+
content: markdownContent,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
}
|
|
@@ -2,10 +2,10 @@ import { type ToolArguments, type ToolResult } from './index.js';
|
|
|
2
2
|
import type { RequestContext } from '../types/request-context.js';
|
|
3
3
|
import { Tool as MCPTool } from '@modelcontextprotocol/sdk/types.js';
|
|
4
4
|
/**
|
|
5
|
-
* Analyse Tool - Analyze repository and
|
|
5
|
+
* Analyse Tool - Analyze repository and write tech context files
|
|
6
6
|
*
|
|
7
|
-
* This tool provides repository analysis and
|
|
8
|
-
* It analyzes the tech stack and
|
|
7
|
+
* This tool provides repository analysis and tech context management.
|
|
8
|
+
* It analyzes the tech stack and writes context files to .tiny-brain/.
|
|
9
9
|
*/
|
|
10
10
|
export declare class AnalyseTool {
|
|
11
11
|
static getToolDefinition(): MCPTool;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyse.tool.d.ts","sourceRoot":"","sources":["../../src/tools/analyse.tool.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"analyse.tool.d.ts","sourceRoot":"","sources":["../../src/tools/analyse.tool.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AACpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAOrE;;;;;GAKG;AACH,qBAAa,WAAW;IACtB,MAAM,CAAC,iBAAiB,IAAI,OAAO;IAmBnC;;OAEG;mBACkB,sBAAsB;IA4B3C;;OAEG;WACU,OAAO,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,UAAU,CAAC;CAoCxF"}
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { createErrorResult } from './index.js';
|
|
3
3
|
import { AnalyseService } from '../services/analyse-service.js';
|
|
4
|
-
import { AgentManager } from '../services/agent-manager.js';
|
|
5
4
|
import { ConfigService } from '@magic-ingredients/tiny-brain-core';
|
|
6
5
|
const AnalyseArgsSchema = z.object({
|
|
7
6
|
dryRun: z.boolean().optional().default(false),
|
|
8
|
-
confirmInstall: z.boolean().optional().default(false),
|
|
9
7
|
}).passthrough(); // Allow additional options to pass through
|
|
10
8
|
/**
|
|
11
|
-
* Analyse Tool - Analyze repository and
|
|
9
|
+
* Analyse Tool - Analyze repository and write tech context files
|
|
12
10
|
*
|
|
13
|
-
* This tool provides repository analysis and
|
|
14
|
-
* It analyzes the tech stack and
|
|
11
|
+
* This tool provides repository analysis and tech context management.
|
|
12
|
+
* It analyzes the tech stack and writes context files to .tiny-brain/.
|
|
15
13
|
*/
|
|
16
14
|
export class AnalyseTool {
|
|
17
15
|
static getToolDefinition() {
|
|
18
16
|
return {
|
|
19
17
|
name: 'analyse',
|
|
20
|
-
description: "🔍 REPOSITORY ANALYSIS 🔍\n\n⚡ ANALYZE & UPDATE: Analyze repository tech stack and
|
|
18
|
+
description: "🔍 REPOSITORY ANALYSIS 🔍\n\n⚡ ANALYZE & UPDATE: Analyze repository tech stack and write tech context files.\n\n✅ FEATURES:\n • Detect languages, frameworks, and tools\n • Write analysis to .tiny-brain/analysis.json\n • Write tech context files to .tiny-brain/tech/*.md\n • Auto-sync tech agents based on enableAgenticCoding config\n • Show Feature flags status (SDD, TDD, ADR, Agentic)\n • Dry-run mode for preview\n\n💡 USAGE:\n • analyse - Analyze repo and write tech context files\n • analyse --dry-run - Preview changes without writing\n\n📊 OUTPUT:\n • Feature flags status\n • Tech stack summary\n • Tech contexts detected\n • Directory initialization status",
|
|
21
19
|
inputSchema: {
|
|
22
20
|
type: 'object',
|
|
23
21
|
properties: {
|
|
@@ -26,11 +24,6 @@ export class AnalyseTool {
|
|
|
26
24
|
description: 'Preview changes without applying them',
|
|
27
25
|
default: false,
|
|
28
26
|
},
|
|
29
|
-
confirmInstall: {
|
|
30
|
-
type: 'boolean',
|
|
31
|
-
description: 'Confirm installation of agents (used internally)',
|
|
32
|
-
default: false,
|
|
33
|
-
},
|
|
34
27
|
},
|
|
35
28
|
required: [],
|
|
36
29
|
},
|
|
@@ -76,67 +69,11 @@ export class AnalyseTool {
|
|
|
76
69
|
const service = new AnalyseService(context);
|
|
77
70
|
const result = await service.performAnalysis(validatedArgs);
|
|
78
71
|
// Format output for display
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
(result.recommendations.remove?.length || 0) > 0;
|
|
85
|
-
if (hasRecommendations) {
|
|
86
|
-
// Check if user has confirmed installation
|
|
87
|
-
if (!validatedArgs.confirmInstall) {
|
|
88
|
-
// Return JSON asking for confirmation (for MCP context)
|
|
89
|
-
return {
|
|
90
|
-
content: [{
|
|
91
|
-
type: 'text',
|
|
92
|
-
text: JSON.stringify({
|
|
93
|
-
requiresConfirmation: true,
|
|
94
|
-
action: 'installAgents',
|
|
95
|
-
recommendations: {
|
|
96
|
-
install: result.recommendations.install?.length || 0,
|
|
97
|
-
update: result.recommendations.update?.length || 0,
|
|
98
|
-
remove: result.recommendations.remove?.length || 0,
|
|
99
|
-
},
|
|
100
|
-
message: output + '\n\nWould you like to install these agents?',
|
|
101
|
-
hint: 'To confirm installation, call the tool again with confirmInstall: true',
|
|
102
|
-
}, null, 2),
|
|
103
|
-
}],
|
|
104
|
-
isError: false,
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
// User has confirmed - proceed with installation
|
|
108
|
-
const selection = {
|
|
109
|
-
install: result.recommendations.install || [],
|
|
110
|
-
update: result.recommendations.update || [],
|
|
111
|
-
remove: result.recommendations.remove || []
|
|
112
|
-
};
|
|
113
|
-
// Apply the recommendations using AgentManager
|
|
114
|
-
output += '\n📦 Installing agents...\n';
|
|
115
|
-
const agentManager = new AgentManager(context);
|
|
116
|
-
const executionResult = await agentManager.executeRecommendations(selection, result.analysis);
|
|
117
|
-
// Report results
|
|
118
|
-
if (executionResult.installed.length > 0) {
|
|
119
|
-
output += `✅ Installed: ${executionResult.installed.join(', ')}\n`;
|
|
120
|
-
}
|
|
121
|
-
if (executionResult.updated.length > 0) {
|
|
122
|
-
output += `✅ Updated: ${executionResult.updated.join(', ')}\n`;
|
|
123
|
-
}
|
|
124
|
-
if (executionResult.removed.length > 0) {
|
|
125
|
-
output += `✅ Removed: ${executionResult.removed.join(', ')}\n`;
|
|
126
|
-
}
|
|
127
|
-
if (executionResult.skipped.length > 0) {
|
|
128
|
-
output += `⏭️ Skipped (already up-to-date): ${executionResult.skipped.join(', ')}\n`;
|
|
129
|
-
}
|
|
130
|
-
if (executionResult.errors.length > 0) {
|
|
131
|
-
output += '⚠️ Errors:\n';
|
|
132
|
-
executionResult.errors.forEach(e => {
|
|
133
|
-
output += ` - ${e.message}\n`;
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
output += '\n✅ Agent management completed!';
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
// Note: Repository registration now happens in AnalyseService.performAnalysis()
|
|
72
|
+
const output = preAnalysisFeedback
|
|
73
|
+
? preAnalysisFeedback + service.formatAnalysisOutput(result)
|
|
74
|
+
: service.formatAnalysisOutput(result);
|
|
75
|
+
// Note: Tech context syncing is now handled automatically by AnalyseService
|
|
76
|
+
// based on enableAgenticCoding config - no user confirmation needed
|
|
140
77
|
return {
|
|
141
78
|
content: [{
|
|
142
79
|
type: 'text',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.tool.d.ts","sourceRoot":"","sources":["../../../src/tools/config/config.tool.ts"],"names":[],"mappings":"AACA,OAAO,EAA0C,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"config.tool.d.ts","sourceRoot":"","sources":["../../../src/tools/config/config.tool.ts"],"names":[],"mappings":"AACA,OAAO,EAA0C,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAWrE,qBAAa,UAAU;IACrB,MAAM,CAAC,iBAAiB,IAAI,OAAO;WA4DtB,OAAO,CAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,UAAU,CAAC;mBA6BD,UAAU;mBAwCV,SAAS;mBAoCT,iBAAiB;mBAgEjB,SAAS;mBAuDT,WAAW;CAqBjC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { createSuccessResult, createErrorResult } from '../index.js';
|
|
3
3
|
import { ConfigService } from '@magic-ingredients/tiny-brain-core';
|
|
4
|
+
import { TechContextService } from '../../services/tech-context-service.js';
|
|
4
5
|
const ConfigArgsSchema = z.object({
|
|
5
6
|
operation: z.enum(['list', 'get', 'show-sources', 'set', 'reset']),
|
|
6
7
|
key: z.string().optional(),
|
|
@@ -79,7 +80,7 @@ export class ConfigTool {
|
|
|
79
80
|
case 'show-sources':
|
|
80
81
|
return await ConfigTool.handleShowSources(configService);
|
|
81
82
|
case 'set':
|
|
82
|
-
return await ConfigTool.handleSet(validatedArgs, configService);
|
|
83
|
+
return await ConfigTool.handleSet(validatedArgs, configService, context);
|
|
83
84
|
case 'reset':
|
|
84
85
|
return await ConfigTool.handleReset(configService);
|
|
85
86
|
default:
|
|
@@ -213,7 +214,7 @@ export class ConfigTool {
|
|
|
213
214
|
return createErrorResult(`Failed to show preference sources: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
214
215
|
}
|
|
215
216
|
}
|
|
216
|
-
static async handleSet(args, configService) {
|
|
217
|
+
static async handleSet(args, configService, context) {
|
|
217
218
|
if (!args.key) {
|
|
218
219
|
return createErrorResult('key is required for set operation');
|
|
219
220
|
}
|
|
@@ -226,6 +227,13 @@ export class ConfigTool {
|
|
|
226
227
|
if (!result.success) {
|
|
227
228
|
return createErrorResult(result.error);
|
|
228
229
|
}
|
|
230
|
+
// React to enableAgenticCoding changes for repo scope
|
|
231
|
+
// Tech files are repo-specific, so global scope doesn't trigger sync
|
|
232
|
+
if (args.key === 'enableAgenticCoding' && scope === 'repo' && context.repositoryRoot) {
|
|
233
|
+
const techContextService = new TechContextService(context.repositoryRoot);
|
|
234
|
+
const enableAgentic = args.value === 'true';
|
|
235
|
+
await techContextService.syncAgents(enableAgentic);
|
|
236
|
+
}
|
|
229
237
|
const scopeLabel = scope === 'global' ? 'Global' : 'Repository';
|
|
230
238
|
const displayValue = args.value === 'true' || args.value === 'false'
|
|
231
239
|
? args.value === 'true'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-registry.d.ts","sourceRoot":"","sources":["../../src/tools/tool-registry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"tool-registry.d.ts","sourceRoot":"","sources":["../../src/tools/tool-registry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAcH,OAAO,KAAK,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,qBAAa,YAAY;IACvB;;OAEG;IACH,MAAM,CAAC,QAAQ,IAAI,OAAO,EAAE;IAoC5B;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;CAmB9D;AAGD,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,8BAAqC,CAAC;AAChF,eAAO,MAAM,eAAe,gBAAyD,CAAC"}
|