feature-architect-agent 1.0.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/README.md +704 -0
- package/bin/feature-architect.js +2 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +63 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +125 -0
- package/dist/commands/plan.d.ts +6 -0
- package/dist/commands/plan.d.ts.map +1 -0
- package/dist/commands/plan.js +147 -0
- package/dist/commands/verify.d.ts +2 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +101 -0
- package/dist/config/api.config.d.ts +49 -0
- package/dist/config/api.config.d.ts.map +1 -0
- package/dist/config/api.config.js +78 -0
- package/dist/llm/Claude.d.ts +8 -0
- package/dist/llm/Claude.d.ts.map +1 -0
- package/dist/llm/Claude.js +44 -0
- package/dist/llm/OpenAI.d.ts +8 -0
- package/dist/llm/OpenAI.d.ts.map +1 -0
- package/dist/llm/OpenAI.js +43 -0
- package/dist/llm/factory.d.ts +9 -0
- package/dist/llm/factory.d.ts.map +1 -0
- package/dist/llm/factory.js +36 -0
- package/dist/llm/types.d.ts +8 -0
- package/dist/llm/types.d.ts.map +1 -0
- package/dist/llm/types.js +2 -0
- package/dist/services/Analyzer.d.ts +18 -0
- package/dist/services/Analyzer.d.ts.map +1 -0
- package/dist/services/Analyzer.js +235 -0
- package/dist/services/ContextManager.d.ts +16 -0
- package/dist/services/ContextManager.d.ts.map +1 -0
- package/dist/services/ContextManager.js +82 -0
- package/dist/services/Planner.d.ts +16 -0
- package/dist/services/Planner.d.ts.map +1 -0
- package/dist/services/Planner.js +181 -0
- package/dist/services/Scanner.d.ts +17 -0
- package/dist/services/Scanner.d.ts.map +1 -0
- package/dist/services/Scanner.js +75 -0
- package/dist/types/index.d.ts +90 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/utils/logger.d.ts +22 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +56 -0
- package/package.json +48 -0
- package/src/config/api.config.ts +79 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpenAIProvider = void 0;
|
|
4
|
+
class OpenAIProvider {
|
|
5
|
+
apiKey;
|
|
6
|
+
model;
|
|
7
|
+
constructor(apiKey, model = 'gpt-4-turbo') {
|
|
8
|
+
this.apiKey = apiKey;
|
|
9
|
+
this.model = model;
|
|
10
|
+
}
|
|
11
|
+
async generate(prompt, options = {}) {
|
|
12
|
+
const temperature = options.temperature ?? 0.7;
|
|
13
|
+
const maxTokens = options.maxTokens ?? 4000;
|
|
14
|
+
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: {
|
|
17
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
18
|
+
'Content-Type': 'application/json'
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify({
|
|
21
|
+
model: this.model,
|
|
22
|
+
messages: [
|
|
23
|
+
{
|
|
24
|
+
role: 'user',
|
|
25
|
+
content: prompt
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
temperature,
|
|
29
|
+
max_tokens: maxTokens
|
|
30
|
+
})
|
|
31
|
+
});
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
const error = await response.text();
|
|
34
|
+
throw new Error(`OpenAI API error: ${response.status} ${error}`);
|
|
35
|
+
}
|
|
36
|
+
const data = await response.json();
|
|
37
|
+
if (data.error) {
|
|
38
|
+
throw new Error(`OpenAI API error: ${data.error.message}`);
|
|
39
|
+
}
|
|
40
|
+
return data.choices[0].message.content;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.OpenAIProvider = OpenAIProvider;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LLMProvider } from './types.js';
|
|
2
|
+
export type ProviderType = 'claude' | 'anthropic' | 'openai' | 'gemini' | 'google' | 'opencode' | 'ollama';
|
|
3
|
+
export interface ProviderConfig {
|
|
4
|
+
provider: ProviderType;
|
|
5
|
+
apiKey?: string;
|
|
6
|
+
model?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function createProvider(config: ProviderConfig): LLMProvider;
|
|
9
|
+
//# sourceMappingURL=factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/llm/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAI9C,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE3G,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,YAAY,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAWD,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,WAAW,CAmClE"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createProvider = createProvider;
|
|
4
|
+
const Claude_js_1 = require("./Claude.js");
|
|
5
|
+
const OpenAI_js_1 = require("./OpenAI.js");
|
|
6
|
+
/**
|
|
7
|
+
* Normalize provider name - accepts both 'claude'/'anthropic' and 'gemini'/'google'
|
|
8
|
+
*/
|
|
9
|
+
function normalizeProvider(provider) {
|
|
10
|
+
if (provider === 'anthropic')
|
|
11
|
+
return 'claude';
|
|
12
|
+
if (provider === 'google')
|
|
13
|
+
return 'gemini';
|
|
14
|
+
return provider;
|
|
15
|
+
}
|
|
16
|
+
function createProvider(config) {
|
|
17
|
+
const normalizedProvider = normalizeProvider(config.provider);
|
|
18
|
+
const apiKey = config.apiKey || process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY;
|
|
19
|
+
if (!apiKey && ['claude', 'openai'].includes(normalizedProvider)) {
|
|
20
|
+
throw new Error(`API key required for ${config.provider}. Set ${config.provider.toUpperCase()}_API_KEY environment variable.`);
|
|
21
|
+
}
|
|
22
|
+
switch (normalizedProvider) {
|
|
23
|
+
case 'claude':
|
|
24
|
+
return new Claude_js_1.ClaudeProvider(apiKey, config.model || 'claude-3-5-sonnet-20241022');
|
|
25
|
+
case 'openai':
|
|
26
|
+
return new OpenAI_js_1.OpenAIProvider(apiKey, config.model || 'gpt-4o');
|
|
27
|
+
case 'gemini':
|
|
28
|
+
throw new Error('Gemini provider not yet implemented');
|
|
29
|
+
case 'opencode':
|
|
30
|
+
throw new Error('OpenCode provider not yet implemented');
|
|
31
|
+
case 'ollama':
|
|
32
|
+
throw new Error('Ollama provider not yet implemented');
|
|
33
|
+
default:
|
|
34
|
+
throw new Error(`Unknown provider: ${config.provider}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/llm/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACtE;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CodebasePatterns } from '../types/index.js';
|
|
2
|
+
export declare class AnalyzerService {
|
|
3
|
+
analyze(files: string[]): Promise<CodebasePatterns>;
|
|
4
|
+
private analyzeTypeScriptFile;
|
|
5
|
+
private analyzeSQLFile;
|
|
6
|
+
private analyzePrismaFile;
|
|
7
|
+
private isReactComponent;
|
|
8
|
+
private isAPIRoute;
|
|
9
|
+
private extractComponentName;
|
|
10
|
+
private getComponentType;
|
|
11
|
+
private extractProps;
|
|
12
|
+
private extractAPIRoutes;
|
|
13
|
+
private extractTypeDefinitions;
|
|
14
|
+
private extractTables;
|
|
15
|
+
private extractPrismaModels;
|
|
16
|
+
private findLine;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=Analyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Analyzer.d.ts","sourceRoot":"","sources":["../../src/services/Analyzer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAuD,MAAM,mBAAmB,CAAC;AAE/G,qBAAa,eAAe;IACpB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA+BzD,OAAO,CAAC,qBAAqB;IAkC7B,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,UAAU;IAWlB,OAAO,CAAC,oBAAoB;IAgB5B,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,gBAAgB;IAwCxB,OAAO,CAAC,sBAAsB;IA2C9B,OAAO,CAAC,aAAa;IAgCrB,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,QAAQ;CAQjB"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AnalyzerService = void 0;
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
class AnalyzerService {
|
|
10
|
+
async analyze(files) {
|
|
11
|
+
const patterns = {
|
|
12
|
+
apiRoutes: [],
|
|
13
|
+
databaseSchemas: [],
|
|
14
|
+
components: [],
|
|
15
|
+
types: [],
|
|
16
|
+
utilities: []
|
|
17
|
+
};
|
|
18
|
+
for (const file of files) {
|
|
19
|
+
const ext = path_1.default.extname(file);
|
|
20
|
+
try {
|
|
21
|
+
const content = await promises_1.default.readFile(file, 'utf-8');
|
|
22
|
+
const lines = content.split('\n');
|
|
23
|
+
if (['.ts', '.tsx', '.js', '.jsx'].includes(ext)) {
|
|
24
|
+
this.analyzeTypeScriptFile(file, content, lines, patterns);
|
|
25
|
+
}
|
|
26
|
+
else if (ext === '.sql') {
|
|
27
|
+
this.analyzeSQLFile(file, content, lines, patterns);
|
|
28
|
+
}
|
|
29
|
+
else if (ext === '.prisma') {
|
|
30
|
+
this.analyzePrismaFile(file, content, lines, patterns);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
// Skip files that can't be read
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return patterns;
|
|
38
|
+
}
|
|
39
|
+
analyzeTypeScriptFile(file, content, lines, patterns) {
|
|
40
|
+
const isComponent = this.isReactComponent(file, content);
|
|
41
|
+
const isRoute = this.isAPIRoute(file, content);
|
|
42
|
+
// Extract components
|
|
43
|
+
if (isComponent) {
|
|
44
|
+
const componentName = this.extractComponentName(content);
|
|
45
|
+
if (componentName) {
|
|
46
|
+
patterns.components.push({
|
|
47
|
+
file,
|
|
48
|
+
line: this.findLine(lines, componentName),
|
|
49
|
+
name: componentName,
|
|
50
|
+
type: this.getComponentType(file),
|
|
51
|
+
props: this.extractProps(content)
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Extract API routes
|
|
56
|
+
if (isRoute) {
|
|
57
|
+
const routes = this.extractAPIRoutes(file, lines);
|
|
58
|
+
patterns.apiRoutes.push(...routes);
|
|
59
|
+
}
|
|
60
|
+
// Extract type definitions
|
|
61
|
+
const types = this.extractTypeDefinitions(file, lines);
|
|
62
|
+
patterns.types.push(...types);
|
|
63
|
+
}
|
|
64
|
+
analyzeSQLFile(file, content, lines, patterns) {
|
|
65
|
+
const tables = this.extractTables(file, lines);
|
|
66
|
+
patterns.databaseSchemas.push(...tables);
|
|
67
|
+
}
|
|
68
|
+
analyzePrismaFile(file, content, lines, patterns) {
|
|
69
|
+
const models = this.extractPrismaModels(file, lines);
|
|
70
|
+
patterns.databaseSchemas.push(...models);
|
|
71
|
+
}
|
|
72
|
+
isReactComponent(file, content) {
|
|
73
|
+
return ((file.endsWith('.tsx') || file.endsWith('.jsx')) ||
|
|
74
|
+
content.includes('React.FC') ||
|
|
75
|
+
content.includes('export function') && content.includes('JSX'));
|
|
76
|
+
}
|
|
77
|
+
isAPIRoute(file, content) {
|
|
78
|
+
return (file.includes('/api/') ||
|
|
79
|
+
file.includes('/routes/') ||
|
|
80
|
+
file.includes('router.') ||
|
|
81
|
+
content.includes('express.Router') ||
|
|
82
|
+
content.includes('.get(') ||
|
|
83
|
+
content.includes('.post('));
|
|
84
|
+
}
|
|
85
|
+
extractComponentName(content) {
|
|
86
|
+
// Match: export function ComponentName
|
|
87
|
+
const funcMatch = content.match(/export\s+function\s+(\w+)/);
|
|
88
|
+
if (funcMatch)
|
|
89
|
+
return funcMatch[1];
|
|
90
|
+
// Match: const ComponentName = () =>
|
|
91
|
+
const arrowMatch = content.match(/(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\(/);
|
|
92
|
+
if (arrowMatch)
|
|
93
|
+
return arrowMatch[1];
|
|
94
|
+
// Match: export class ComponentName
|
|
95
|
+
const classMatch = content.match(/export\s+class\s+(\w+)\s+extends/);
|
|
96
|
+
if (classMatch)
|
|
97
|
+
return classMatch[1];
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
getComponentType(file) {
|
|
101
|
+
if (file.includes('/pages/') || file.includes('/app/'))
|
|
102
|
+
return 'Page';
|
|
103
|
+
if (file.includes('/components/'))
|
|
104
|
+
return 'Component';
|
|
105
|
+
if (file.includes('/layouts/'))
|
|
106
|
+
return 'Layout';
|
|
107
|
+
return 'Component';
|
|
108
|
+
}
|
|
109
|
+
extractProps(content) {
|
|
110
|
+
const interfaceMatch = content.match(/interface\s+(\w+Props)\s*{([^}]+)}/s);
|
|
111
|
+
if (interfaceMatch) {
|
|
112
|
+
return interfaceMatch[2].trim();
|
|
113
|
+
}
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
extractAPIRoutes(file, lines) {
|
|
117
|
+
const routes = [];
|
|
118
|
+
for (let i = 0; i < lines.length; i++) {
|
|
119
|
+
const line = lines[i];
|
|
120
|
+
// Match: router.get('/path', handler)
|
|
121
|
+
const routeMatch = line.match(/\.(get|post|put|patch|delete|options|head)\s*\(\s*['"`]([^'"`]+)['"`]\s*,\s*(\w+)/);
|
|
122
|
+
if (routeMatch) {
|
|
123
|
+
routes.push({
|
|
124
|
+
file,
|
|
125
|
+
line: i + 1,
|
|
126
|
+
method: routeMatch[1].toUpperCase(),
|
|
127
|
+
path: routeMatch[2],
|
|
128
|
+
handler: routeMatch[3]
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// Match: app.get('/path', handler)
|
|
132
|
+
const appMatch = line.match(/\b(app|router)\.(get|post|put|patch|delete)\s*\(\s*['"`]([^'"`]+)['"`]/);
|
|
133
|
+
if (appMatch) {
|
|
134
|
+
routes.push({
|
|
135
|
+
file,
|
|
136
|
+
line: i + 1,
|
|
137
|
+
method: appMatch[2].toUpperCase(),
|
|
138
|
+
path: appMatch[3],
|
|
139
|
+
handler: 'anonymous'
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return routes;
|
|
144
|
+
}
|
|
145
|
+
extractTypeDefinitions(file, lines) {
|
|
146
|
+
const types = [];
|
|
147
|
+
for (let i = 0; i < lines.length; i++) {
|
|
148
|
+
const line = lines[i];
|
|
149
|
+
// Match: interface Name
|
|
150
|
+
const interfaceMatch = line.match(/(?:export\s+)?interface\s+(\w+)/);
|
|
151
|
+
if (interfaceMatch) {
|
|
152
|
+
types.push({
|
|
153
|
+
file,
|
|
154
|
+
line: i + 1,
|
|
155
|
+
name: interfaceMatch[1],
|
|
156
|
+
kind: 'interface'
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// Match: type Name =
|
|
160
|
+
const typeMatch = line.match(/(?:export\s+)?type\s+(\w+)\s*=/);
|
|
161
|
+
if (typeMatch) {
|
|
162
|
+
types.push({
|
|
163
|
+
file,
|
|
164
|
+
line: i + 1,
|
|
165
|
+
name: typeMatch[1],
|
|
166
|
+
kind: 'type'
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
// Match: enum Name
|
|
170
|
+
const enumMatch = line.match(/(?:export\s+)?enum\s+(\w+)/);
|
|
171
|
+
if (enumMatch) {
|
|
172
|
+
types.push({
|
|
173
|
+
file,
|
|
174
|
+
line: i + 1,
|
|
175
|
+
name: enumMatch[1],
|
|
176
|
+
kind: 'enum'
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return types;
|
|
181
|
+
}
|
|
182
|
+
extractTables(file, lines) {
|
|
183
|
+
const schemas = [];
|
|
184
|
+
for (let i = 0; i < lines.length; i++) {
|
|
185
|
+
const line = lines[i];
|
|
186
|
+
// Match: CREATE TABLE name
|
|
187
|
+
const tableMatch = line.match(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:`?(\w+)`?)/i);
|
|
188
|
+
if (tableMatch) {
|
|
189
|
+
schemas.push({
|
|
190
|
+
file,
|
|
191
|
+
line: i + 1,
|
|
192
|
+
type: 'table',
|
|
193
|
+
name: tableMatch[1]
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
// Match: CREATE VIEW name
|
|
197
|
+
const viewMatch = line.match(/CREATE\s+VIEW\s+(?:`?(\w+)`?)/i);
|
|
198
|
+
if (viewMatch) {
|
|
199
|
+
schemas.push({
|
|
200
|
+
file,
|
|
201
|
+
line: i + 1,
|
|
202
|
+
type: 'view',
|
|
203
|
+
name: viewMatch[1]
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return schemas;
|
|
208
|
+
}
|
|
209
|
+
extractPrismaModels(file, lines) {
|
|
210
|
+
const schemas = [];
|
|
211
|
+
for (let i = 0; i < lines.length; i++) {
|
|
212
|
+
const line = lines[i];
|
|
213
|
+
// Match: model Name {
|
|
214
|
+
const modelMatch = line.match(/model\s+(\w+)\s*{/);
|
|
215
|
+
if (modelMatch) {
|
|
216
|
+
schemas.push({
|
|
217
|
+
file,
|
|
218
|
+
line: i + 1,
|
|
219
|
+
type: 'table',
|
|
220
|
+
name: modelMatch[1]
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return schemas;
|
|
225
|
+
}
|
|
226
|
+
findLine(lines, text) {
|
|
227
|
+
for (let i = 0; i < lines.length; i++) {
|
|
228
|
+
if (lines[i].includes(text)) {
|
|
229
|
+
return i + 1;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return 1;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
exports.AnalyzerService = AnalyzerService;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CodebaseContext } from '../types/index.js';
|
|
2
|
+
export declare class ContextManager {
|
|
3
|
+
private contextDir;
|
|
4
|
+
private contextFile;
|
|
5
|
+
constructor(root?: string);
|
|
6
|
+
saveContext(context: CodebaseContext): Promise<void>;
|
|
7
|
+
loadContext(): Promise<CodebaseContext | null>;
|
|
8
|
+
updateContext(updates: Partial<CodebaseContext>): Promise<void>;
|
|
9
|
+
contextExists(): Promise<boolean>;
|
|
10
|
+
getContextPath(): Promise<string>;
|
|
11
|
+
exportContext(outputPath: string): Promise<void>;
|
|
12
|
+
importContext(inputPath: string): Promise<void>;
|
|
13
|
+
private ensureContextDir;
|
|
14
|
+
private generateHash;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=ContextManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContextManager.d.ts","sourceRoot":"","sources":["../../src/services/ContextManager.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD,qBAAa,cAAc;IACzB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAS;gBAEhB,IAAI,GAAE,MAAsB;IAKlC,WAAW,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAapD,WAAW,IAAI,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAS9C,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAU/D,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC;IASjC,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC;IAIjC,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAShD,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAQvC,gBAAgB;IAQ9B,OAAO,CAAC,YAAY;CAIrB"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ContextManager = void 0;
|
|
7
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
10
|
+
class ContextManager {
|
|
11
|
+
contextDir;
|
|
12
|
+
contextFile;
|
|
13
|
+
constructor(root = process.cwd()) {
|
|
14
|
+
this.contextDir = path_1.default.join(root, '.feature-architect');
|
|
15
|
+
this.contextFile = path_1.default.join(this.contextDir, 'context.json');
|
|
16
|
+
}
|
|
17
|
+
async saveContext(context) {
|
|
18
|
+
await this.ensureContextDir();
|
|
19
|
+
// Update metadata
|
|
20
|
+
context.metadata = {
|
|
21
|
+
version: '1.0.0',
|
|
22
|
+
analyzedAt: new Date().toISOString(),
|
|
23
|
+
hash: this.generateHash(context)
|
|
24
|
+
};
|
|
25
|
+
await promises_1.default.writeFile(this.contextFile, JSON.stringify(context, null, 2), 'utf-8');
|
|
26
|
+
}
|
|
27
|
+
async loadContext() {
|
|
28
|
+
try {
|
|
29
|
+
const content = await promises_1.default.readFile(this.contextFile, 'utf-8');
|
|
30
|
+
return JSON.parse(content);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async updateContext(updates) {
|
|
37
|
+
const current = await this.loadContext();
|
|
38
|
+
if (!current) {
|
|
39
|
+
throw new Error('No context found. Run init first.');
|
|
40
|
+
}
|
|
41
|
+
const updated = { ...current, ...updates };
|
|
42
|
+
await this.saveContext(updated);
|
|
43
|
+
}
|
|
44
|
+
async contextExists() {
|
|
45
|
+
try {
|
|
46
|
+
await promises_1.default.access(this.contextFile);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async getContextPath() {
|
|
54
|
+
return this.contextFile;
|
|
55
|
+
}
|
|
56
|
+
async exportContext(outputPath) {
|
|
57
|
+
const context = await this.loadContext();
|
|
58
|
+
if (!context) {
|
|
59
|
+
throw new Error('No context found. Run init first.');
|
|
60
|
+
}
|
|
61
|
+
await promises_1.default.writeFile(outputPath, JSON.stringify(context, null, 2), 'utf-8');
|
|
62
|
+
}
|
|
63
|
+
async importContext(inputPath) {
|
|
64
|
+
const content = await promises_1.default.readFile(inputPath, 'utf-8');
|
|
65
|
+
const context = JSON.parse(content);
|
|
66
|
+
await this.ensureContextDir();
|
|
67
|
+
await this.saveContext(context);
|
|
68
|
+
}
|
|
69
|
+
async ensureContextDir() {
|
|
70
|
+
try {
|
|
71
|
+
await promises_1.default.mkdir(this.contextDir, { recursive: true });
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Ignore if already exists
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
generateHash(context) {
|
|
78
|
+
const content = JSON.stringify(context);
|
|
79
|
+
return crypto_1.default.createHash('sha256').update(content).digest('hex').substring(0, 16);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.ContextManager = ContextManager;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { LLMProvider } from '../llm/types.js';
|
|
2
|
+
import type { CodebaseContext, FeaturePlan } from '../types/index.js';
|
|
3
|
+
interface PlanOptions {
|
|
4
|
+
output?: string;
|
|
5
|
+
includeDiagrams?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare class PlannerService {
|
|
8
|
+
private llm;
|
|
9
|
+
constructor(llm: LLMProvider);
|
|
10
|
+
planFeature(feature: string, context: CodebaseContext, options?: PlanOptions): Promise<FeaturePlan>;
|
|
11
|
+
private buildPrompt;
|
|
12
|
+
private generateId;
|
|
13
|
+
private slugify;
|
|
14
|
+
}
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=Planner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Planner.d.ts","sourceRoot":"","sources":["../../src/services/Planner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEtE,UAAU,WAAW;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,qBAAa,cAAc;IACb,OAAO,CAAC,GAAG;gBAAH,GAAG,EAAE,WAAW;IAE9B,WAAW,CACf,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,eAAe,EACxB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,WAAW,CAAC;IAqBvB,OAAO,CAAC,WAAW;IAgJnB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,OAAO;CAUhB"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PlannerService = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
class PlannerService {
|
|
9
|
+
llm;
|
|
10
|
+
constructor(llm) {
|
|
11
|
+
this.llm = llm;
|
|
12
|
+
}
|
|
13
|
+
async planFeature(feature, context, options = {}) {
|
|
14
|
+
const prompt = this.buildPrompt(feature, context);
|
|
15
|
+
const response = await this.llm.generate(prompt, {
|
|
16
|
+
temperature: 0.7,
|
|
17
|
+
maxTokens: 8000
|
|
18
|
+
});
|
|
19
|
+
const slug = this.slugify(feature);
|
|
20
|
+
const outputFile = options.output || path_1.default.join('docs', 'features', `${slug}.md`);
|
|
21
|
+
const plan = {
|
|
22
|
+
id: this.generateId(),
|
|
23
|
+
feature,
|
|
24
|
+
slug,
|
|
25
|
+
createdAt: new Date().toISOString(),
|
|
26
|
+
markdown: response
|
|
27
|
+
};
|
|
28
|
+
return plan;
|
|
29
|
+
}
|
|
30
|
+
buildPrompt(feature, context) {
|
|
31
|
+
const patterns = context.patterns;
|
|
32
|
+
let relevantPatterns = '';
|
|
33
|
+
// API Routes
|
|
34
|
+
if (patterns.apiRoutes.length > 0) {
|
|
35
|
+
relevantPatterns += `\n### Existing API Routes (${patterns.apiRoutes.length}):\n`;
|
|
36
|
+
patterns.apiRoutes.slice(0, 20).forEach(route => {
|
|
37
|
+
relevantPatterns += `- ${route.method} ${route.path} (${path_1.default.basename(route.file)})\n`;
|
|
38
|
+
});
|
|
39
|
+
if (patterns.apiRoutes.length > 20) {
|
|
40
|
+
relevantPatterns += `... and ${patterns.apiRoutes.length - 20} more\n`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Database Tables
|
|
44
|
+
if (patterns.databaseSchemas.length > 0) {
|
|
45
|
+
relevantPatterns += `\n### Existing Database Tables (${patterns.databaseSchemas.length}):\n`;
|
|
46
|
+
patterns.databaseSchemas.slice(0, 20).forEach(schema => {
|
|
47
|
+
relevantPatterns += `- ${schema.name} (${schema.type})\n`;
|
|
48
|
+
});
|
|
49
|
+
if (patterns.databaseSchemas.length > 20) {
|
|
50
|
+
relevantPatterns += `... and ${patterns.databaseSchemas.length - 20} more\n`;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Components
|
|
54
|
+
if (patterns.components.length > 0) {
|
|
55
|
+
relevantPatterns += `\n### Existing Components (${patterns.components.length}):\n`;
|
|
56
|
+
patterns.components.slice(0, 20).forEach(component => {
|
|
57
|
+
relevantPatterns += `- ${component.name} (${component.type})\n`;
|
|
58
|
+
});
|
|
59
|
+
if (patterns.components.length > 20) {
|
|
60
|
+
relevantPatterns += `... and ${patterns.components.length - 20} more\n`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Frameworks
|
|
64
|
+
const frameworks = Object.values(context.frameworks).filter(Boolean).join(', ') || 'Unknown';
|
|
65
|
+
return `You are a senior software architect. Plan the following feature based on the existing codebase context.
|
|
66
|
+
|
|
67
|
+
FEATURE TO PLAN: ${feature}
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## CODEBASE CONTEXT
|
|
72
|
+
|
|
73
|
+
**Project Type:** ${context.project.type}
|
|
74
|
+
**Frameworks:** ${frameworks}
|
|
75
|
+
**Total Files:** ${context.summary.totalFiles}
|
|
76
|
+
**Languages:** ${Object.entries(context.summary.languages)
|
|
77
|
+
.map(([lang, count]) => `${lang} (${count})`)
|
|
78
|
+
.join(', ')}
|
|
79
|
+
|
|
80
|
+
${relevantPatterns}
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## YOUR TASK
|
|
85
|
+
|
|
86
|
+
Generate a complete, detailed feature plan that:
|
|
87
|
+
|
|
88
|
+
1. **Follows existing patterns** in the codebase
|
|
89
|
+
2. **Uses the same frameworks and conventions** detected above
|
|
90
|
+
3. **Integrates seamlessly** with existing API routes and database tables
|
|
91
|
+
|
|
92
|
+
## OUTPUT FORMAT (Markdown)
|
|
93
|
+
|
|
94
|
+
# Feature Plan: ${feature}
|
|
95
|
+
|
|
96
|
+
## Overview
|
|
97
|
+
[Brief description of what this feature does and why it's needed]
|
|
98
|
+
|
|
99
|
+
## Use Cases
|
|
100
|
+
| ID | Description | Priority |
|
|
101
|
+
|----|-------------|----------|
|
|
102
|
+
| UC1 | [Specific use case] | Must Have |
|
|
103
|
+
| UC2 | [Specific use case] | Should Have |
|
|
104
|
+
...
|
|
105
|
+
|
|
106
|
+
## Database Design
|
|
107
|
+
### New Tables
|
|
108
|
+
\`\`\`sql
|
|
109
|
+
[SQL CREATE statements for any new tables]
|
|
110
|
+
\`\`\`
|
|
111
|
+
|
|
112
|
+
### Modified Tables
|
|
113
|
+
\`\`\`sql
|
|
114
|
+
[SQL ALTER statements for any modifications to existing tables]
|
|
115
|
+
\`\`\`
|
|
116
|
+
|
|
117
|
+
## Backend API
|
|
118
|
+
### New Endpoints
|
|
119
|
+
| Method | Endpoint | Description | Auth |
|
|
120
|
+
|--------|----------|-------------|------|
|
|
121
|
+
| GET | /api/v1/... | [Description] | Required |
|
|
122
|
+
...
|
|
123
|
+
|
|
124
|
+
### Request/Response Examples
|
|
125
|
+
\`\`\`typescript
|
|
126
|
+
[TypeScript interfaces for requests and responses]
|
|
127
|
+
\`\`\`
|
|
128
|
+
|
|
129
|
+
## Frontend Components
|
|
130
|
+
### Component Structure
|
|
131
|
+
\`\`\`
|
|
132
|
+
src/features/[feature-name]/
|
|
133
|
+
├── ComponentName.tsx
|
|
134
|
+
└── ...
|
|
135
|
+
\`\`\`
|
|
136
|
+
|
|
137
|
+
### Component Specs
|
|
138
|
+
\`\`\`typescript
|
|
139
|
+
[Component interfaces and props]
|
|
140
|
+
\`\`\`
|
|
141
|
+
|
|
142
|
+
## Architecture Flow
|
|
143
|
+
\`\`\`mermaid
|
|
144
|
+
sequenceDiagram
|
|
145
|
+
[Sequence diagram showing the flow]
|
|
146
|
+
\`\`\`
|
|
147
|
+
|
|
148
|
+
## Implementation Tasks
|
|
149
|
+
### Phase 1: Database (X days)
|
|
150
|
+
- [ ] [Task 1]
|
|
151
|
+
- [ ] [Task 2]
|
|
152
|
+
|
|
153
|
+
### Phase 2: Backend (X days)
|
|
154
|
+
- [ ] [Task 1]
|
|
155
|
+
|
|
156
|
+
...
|
|
157
|
+
|
|
158
|
+
## Edge Cases
|
|
159
|
+
| Case | Handling |
|
|
160
|
+
|------|----------|
|
|
161
|
+
| [Edge case] | [How to handle] |
|
|
162
|
+
...
|
|
163
|
+
|
|
164
|
+
Generate the complete plan now. Be specific and detailed.
|
|
165
|
+
`;
|
|
166
|
+
}
|
|
167
|
+
generateId() {
|
|
168
|
+
return `plan_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
169
|
+
}
|
|
170
|
+
slugify(text) {
|
|
171
|
+
return text
|
|
172
|
+
.toString()
|
|
173
|
+
.toLowerCase()
|
|
174
|
+
.trim()
|
|
175
|
+
.replace(/\s+/g, '-')
|
|
176
|
+
.replace(/[^\w\-]+/g, '')
|
|
177
|
+
.replace(/\-\-+/g, '-')
|
|
178
|
+
.replace(/[^a-z0-9\-]/g, '');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
exports.PlannerService = PlannerService;
|