archbyte 0.1.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 +282 -0
- package/bin/archbyte.js +213 -0
- package/dist/agents/core/component-detector.d.ts +2 -0
- package/dist/agents/core/component-detector.js +57 -0
- package/dist/agents/core/connection-mapper.d.ts +2 -0
- package/dist/agents/core/connection-mapper.js +77 -0
- package/dist/agents/core/doc-parser.d.ts +2 -0
- package/dist/agents/core/doc-parser.js +64 -0
- package/dist/agents/core/env-detector.d.ts +2 -0
- package/dist/agents/core/env-detector.js +51 -0
- package/dist/agents/core/event-detector.d.ts +2 -0
- package/dist/agents/core/event-detector.js +59 -0
- package/dist/agents/core/infra-analyzer.d.ts +2 -0
- package/dist/agents/core/infra-analyzer.js +72 -0
- package/dist/agents/core/structure-scanner.d.ts +2 -0
- package/dist/agents/core/structure-scanner.js +55 -0
- package/dist/agents/core/validator.d.ts +2 -0
- package/dist/agents/core/validator.js +74 -0
- package/dist/agents/index.d.ts +24 -0
- package/dist/agents/index.js +73 -0
- package/dist/agents/llm/index.d.ts +8 -0
- package/dist/agents/llm/index.js +185 -0
- package/dist/agents/llm/prompt-builder.d.ts +3 -0
- package/dist/agents/llm/prompt-builder.js +251 -0
- package/dist/agents/llm/response-parser.d.ts +6 -0
- package/dist/agents/llm/response-parser.js +174 -0
- package/dist/agents/llm/types.d.ts +31 -0
- package/dist/agents/llm/types.js +2 -0
- package/dist/agents/pipeline/agents/component-identifier.d.ts +3 -0
- package/dist/agents/pipeline/agents/component-identifier.js +102 -0
- package/dist/agents/pipeline/agents/connection-mapper.d.ts +3 -0
- package/dist/agents/pipeline/agents/connection-mapper.js +126 -0
- package/dist/agents/pipeline/agents/flow-detector.d.ts +3 -0
- package/dist/agents/pipeline/agents/flow-detector.js +101 -0
- package/dist/agents/pipeline/agents/service-describer.d.ts +3 -0
- package/dist/agents/pipeline/agents/service-describer.js +100 -0
- package/dist/agents/pipeline/agents/validator.d.ts +3 -0
- package/dist/agents/pipeline/agents/validator.js +102 -0
- package/dist/agents/pipeline/index.d.ts +13 -0
- package/dist/agents/pipeline/index.js +128 -0
- package/dist/agents/pipeline/merger.d.ts +7 -0
- package/dist/agents/pipeline/merger.js +212 -0
- package/dist/agents/pipeline/response-parser.d.ts +5 -0
- package/dist/agents/pipeline/response-parser.js +43 -0
- package/dist/agents/pipeline/types.d.ts +92 -0
- package/dist/agents/pipeline/types.js +3 -0
- package/dist/agents/prompt-data.d.ts +1 -0
- package/dist/agents/prompt-data.js +15 -0
- package/dist/agents/prompts-encode.d.ts +9 -0
- package/dist/agents/prompts-encode.js +26 -0
- package/dist/agents/prompts.d.ts +12 -0
- package/dist/agents/prompts.js +30 -0
- package/dist/agents/providers/anthropic.d.ts +10 -0
- package/dist/agents/providers/anthropic.js +117 -0
- package/dist/agents/providers/google.d.ts +10 -0
- package/dist/agents/providers/google.js +136 -0
- package/dist/agents/providers/ollama.d.ts +9 -0
- package/dist/agents/providers/ollama.js +162 -0
- package/dist/agents/providers/openai.d.ts +9 -0
- package/dist/agents/providers/openai.js +142 -0
- package/dist/agents/providers/router.d.ts +7 -0
- package/dist/agents/providers/router.js +55 -0
- package/dist/agents/runtime/orchestrator.d.ts +34 -0
- package/dist/agents/runtime/orchestrator.js +193 -0
- package/dist/agents/runtime/registry.d.ts +23 -0
- package/dist/agents/runtime/registry.js +56 -0
- package/dist/agents/runtime/types.d.ts +117 -0
- package/dist/agents/runtime/types.js +29 -0
- package/dist/agents/static/code-sampler.d.ts +3 -0
- package/dist/agents/static/code-sampler.js +153 -0
- package/dist/agents/static/component-detector.d.ts +3 -0
- package/dist/agents/static/component-detector.js +404 -0
- package/dist/agents/static/connection-mapper.d.ts +3 -0
- package/dist/agents/static/connection-mapper.js +280 -0
- package/dist/agents/static/doc-parser.d.ts +3 -0
- package/dist/agents/static/doc-parser.js +358 -0
- package/dist/agents/static/env-detector.d.ts +3 -0
- package/dist/agents/static/env-detector.js +73 -0
- package/dist/agents/static/event-detector.d.ts +3 -0
- package/dist/agents/static/event-detector.js +70 -0
- package/dist/agents/static/file-tree-collector.d.ts +3 -0
- package/dist/agents/static/file-tree-collector.js +51 -0
- package/dist/agents/static/index.d.ts +19 -0
- package/dist/agents/static/index.js +307 -0
- package/dist/agents/static/infra-analyzer.d.ts +3 -0
- package/dist/agents/static/infra-analyzer.js +208 -0
- package/dist/agents/static/structure-scanner.d.ts +3 -0
- package/dist/agents/static/structure-scanner.js +195 -0
- package/dist/agents/static/types.d.ts +165 -0
- package/dist/agents/static/types.js +2 -0
- package/dist/agents/static/utils.d.ts +21 -0
- package/dist/agents/static/utils.js +146 -0
- package/dist/agents/static/validator.d.ts +2 -0
- package/dist/agents/static/validator.js +75 -0
- package/dist/agents/tools/claude-code.d.ts +38 -0
- package/dist/agents/tools/claude-code.js +129 -0
- package/dist/agents/tools/local-fs.d.ts +12 -0
- package/dist/agents/tools/local-fs.js +112 -0
- package/dist/agents/tools/tool-definitions.d.ts +6 -0
- package/dist/agents/tools/tool-definitions.js +66 -0
- package/dist/cli/analyze.d.ts +27 -0
- package/dist/cli/analyze.js +586 -0
- package/dist/cli/auth.d.ts +46 -0
- package/dist/cli/auth.js +397 -0
- package/dist/cli/config.d.ts +11 -0
- package/dist/cli/config.js +177 -0
- package/dist/cli/diff.d.ts +10 -0
- package/dist/cli/diff.js +144 -0
- package/dist/cli/export.d.ts +10 -0
- package/dist/cli/export.js +321 -0
- package/dist/cli/gate.d.ts +13 -0
- package/dist/cli/gate.js +131 -0
- package/dist/cli/generate.d.ts +10 -0
- package/dist/cli/generate.js +213 -0
- package/dist/cli/license-gate.d.ts +27 -0
- package/dist/cli/license-gate.js +121 -0
- package/dist/cli/patrol.d.ts +15 -0
- package/dist/cli/patrol.js +212 -0
- package/dist/cli/run.d.ts +11 -0
- package/dist/cli/run.js +24 -0
- package/dist/cli/serve.d.ts +9 -0
- package/dist/cli/serve.js +65 -0
- package/dist/cli/setup.d.ts +1 -0
- package/dist/cli/setup.js +233 -0
- package/dist/cli/shared.d.ts +68 -0
- package/dist/cli/shared.js +275 -0
- package/dist/cli/stats.d.ts +9 -0
- package/dist/cli/stats.js +158 -0
- package/dist/cli/ui.d.ts +18 -0
- package/dist/cli/ui.js +144 -0
- package/dist/cli/validate.d.ts +54 -0
- package/dist/cli/validate.js +315 -0
- package/dist/cli/workflow.d.ts +10 -0
- package/dist/cli/workflow.js +594 -0
- package/dist/server/src/generator/index.d.ts +123 -0
- package/dist/server/src/generator/index.js +254 -0
- package/dist/server/src/index.d.ts +8 -0
- package/dist/server/src/index.js +1311 -0
- package/package.json +62 -0
- package/ui/dist/assets/index-B66Til39.js +70 -0
- package/ui/dist/assets/index-BE2OWbzu.css +1 -0
- package/ui/dist/index.html +14 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// LLM Enhancement — Single Call Enhancer
|
|
2
|
+
// One chat() call, NO tools. Fails gracefully → static results unchanged.
|
|
3
|
+
import { resolveModel } from "../runtime/types.js";
|
|
4
|
+
import { buildSystemPrompt, buildUserPrompt } from "./prompt-builder.js";
|
|
5
|
+
import { parseLLMResponse } from "./response-parser.js";
|
|
6
|
+
/**
|
|
7
|
+
* Enhance static analysis with a single LLM call.
|
|
8
|
+
* Returns the merged analysis. On any failure, returns the original unchanged.
|
|
9
|
+
*/
|
|
10
|
+
export async function enhanceWithLLM(analysis, provider, config, projectRoot, onProgress) {
|
|
11
|
+
try {
|
|
12
|
+
onProgress?.("Building LLM prompt...");
|
|
13
|
+
const systemPrompt = buildSystemPrompt();
|
|
14
|
+
const userPrompt = await buildUserPrompt(analysis, projectRoot);
|
|
15
|
+
if (analysis.gaps.length > 0) {
|
|
16
|
+
onProgress?.(`Including ${analysis.gaps.length} gap(s) for LLM resolution`);
|
|
17
|
+
}
|
|
18
|
+
const model = resolveModel(config.provider, "standard", config.modelOverrides);
|
|
19
|
+
onProgress?.(`Calling ${model} for enhancement...`);
|
|
20
|
+
const response = await provider.chat({
|
|
21
|
+
model,
|
|
22
|
+
system: systemPrompt,
|
|
23
|
+
messages: [{ role: "user", content: userPrompt }],
|
|
24
|
+
maxTokens: 4096,
|
|
25
|
+
// NO tools — pure text→JSON
|
|
26
|
+
});
|
|
27
|
+
// Extract text from response
|
|
28
|
+
const text = response.content
|
|
29
|
+
.filter((b) => b.type === "text")
|
|
30
|
+
.map((b) => b.text ?? "")
|
|
31
|
+
.join("");
|
|
32
|
+
if (!text) {
|
|
33
|
+
onProgress?.("LLM returned empty response, using static results");
|
|
34
|
+
return analysis;
|
|
35
|
+
}
|
|
36
|
+
onProgress?.(`LLM response: ${response.usage.inputTokens} in / ${response.usage.outputTokens} out tokens`);
|
|
37
|
+
// Parse with 4-strategy extraction
|
|
38
|
+
const componentIds = new Set(analysis.components.components.map((c) => c.id));
|
|
39
|
+
const enhancement = parseLLMResponse(text, componentIds);
|
|
40
|
+
if (!enhancement) {
|
|
41
|
+
onProgress?.("Could not parse LLM response, using static results");
|
|
42
|
+
return analysis;
|
|
43
|
+
}
|
|
44
|
+
// Log what the LLM resolved
|
|
45
|
+
const resolved = [];
|
|
46
|
+
if (enhancement.componentDescriptions)
|
|
47
|
+
resolved.push(`${Object.keys(enhancement.componentDescriptions).length} descriptions`);
|
|
48
|
+
if (enhancement.componentTypeCorrections)
|
|
49
|
+
resolved.push(`${Object.keys(enhancement.componentTypeCorrections).length} type corrections`);
|
|
50
|
+
if (enhancement.additionalConnections?.length)
|
|
51
|
+
resolved.push(`${enhancement.additionalConnections.length} connections`);
|
|
52
|
+
if (enhancement.databases?.length)
|
|
53
|
+
resolved.push(`${enhancement.databases.length} databases`);
|
|
54
|
+
if (enhancement.externalServices?.length)
|
|
55
|
+
resolved.push(`${enhancement.externalServices.length} external services`);
|
|
56
|
+
if (enhancement.flows?.length)
|
|
57
|
+
resolved.push(`${enhancement.flows.length} flows`);
|
|
58
|
+
if (enhancement.primaryLanguage)
|
|
59
|
+
resolved.push(`language=${enhancement.primaryLanguage}`);
|
|
60
|
+
if (resolved.length > 0) {
|
|
61
|
+
onProgress?.(`LLM resolved: ${resolved.join(", ")}`);
|
|
62
|
+
}
|
|
63
|
+
// Merge enhancement into analysis
|
|
64
|
+
return mergeEnhancement(analysis, enhancement);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
onProgress?.(`LLM enhancement failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
68
|
+
return analysis;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function mergeEnhancement(analysis, enhancement) {
|
|
72
|
+
// Clone to avoid mutation
|
|
73
|
+
const result = structuredClone(analysis);
|
|
74
|
+
// Project description: LLM wins if longer
|
|
75
|
+
if (enhancement.projectDescription &&
|
|
76
|
+
enhancement.projectDescription.length > (result.docs.projectDescription?.length ?? 0)) {
|
|
77
|
+
result.docs.projectDescription = enhancement.projectDescription;
|
|
78
|
+
}
|
|
79
|
+
// Primary language: LLM fills if unknown
|
|
80
|
+
if (enhancement.primaryLanguage && result.structure.language === "unknown") {
|
|
81
|
+
result.structure.language = enhancement.primaryLanguage;
|
|
82
|
+
}
|
|
83
|
+
// Component descriptions: LLM wins if longer than static
|
|
84
|
+
if (enhancement.componentDescriptions) {
|
|
85
|
+
for (const comp of result.components.components) {
|
|
86
|
+
const llmDesc = enhancement.componentDescriptions[comp.id];
|
|
87
|
+
if (llmDesc && llmDesc.length > (comp.description?.length ?? 0)) {
|
|
88
|
+
comp.description = llmDesc;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Component type corrections: LLM can fix mistyped components
|
|
93
|
+
if (enhancement.componentTypeCorrections) {
|
|
94
|
+
for (const comp of result.components.components) {
|
|
95
|
+
const corrected = enhancement.componentTypeCorrections[comp.id];
|
|
96
|
+
if (corrected) {
|
|
97
|
+
comp.type = corrected;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Additional connections: deduplicate against existing
|
|
102
|
+
if (enhancement.additionalConnections) {
|
|
103
|
+
const existingKeys = new Set(result.connections.connections.map((c) => `${c.from}::${c.to}::${c.type}`));
|
|
104
|
+
for (const conn of enhancement.additionalConnections) {
|
|
105
|
+
const key = `${conn.from}::${conn.to}::${conn.type}`;
|
|
106
|
+
if (!existingKeys.has(key)) {
|
|
107
|
+
existingKeys.add(key);
|
|
108
|
+
result.connections.connections.push({
|
|
109
|
+
from: conn.from,
|
|
110
|
+
to: conn.to,
|
|
111
|
+
type: conn.type,
|
|
112
|
+
description: conn.description,
|
|
113
|
+
confidence: 70, // LLM-inferred = lower confidence
|
|
114
|
+
async: false,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Databases: store on the result for buildAnalysisFromStatic to pick up
|
|
120
|
+
if (enhancement.databases && enhancement.databases.length > 0) {
|
|
121
|
+
// Store as components with type="database" and layer="data"
|
|
122
|
+
for (const db of enhancement.databases) {
|
|
123
|
+
const exists = result.components.components.some((c) => c.id === db.id);
|
|
124
|
+
if (!exists) {
|
|
125
|
+
result.components.components.push({
|
|
126
|
+
id: db.id,
|
|
127
|
+
name: db.name,
|
|
128
|
+
type: "database",
|
|
129
|
+
layer: "data",
|
|
130
|
+
path: "",
|
|
131
|
+
description: db.description,
|
|
132
|
+
technologies: [db.type],
|
|
133
|
+
});
|
|
134
|
+
// Add connections from components that use this database
|
|
135
|
+
for (const userId of db.usedBy) {
|
|
136
|
+
result.connections.connections.push({
|
|
137
|
+
from: userId,
|
|
138
|
+
to: db.id,
|
|
139
|
+
type: "database",
|
|
140
|
+
description: `${userId} uses ${db.name}`,
|
|
141
|
+
confidence: 70,
|
|
142
|
+
async: false,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// External services: store as components with layer="external"
|
|
149
|
+
if (enhancement.externalServices && enhancement.externalServices.length > 0) {
|
|
150
|
+
for (const svc of enhancement.externalServices) {
|
|
151
|
+
const exists = result.components.components.some((c) => c.id === svc.id);
|
|
152
|
+
if (!exists) {
|
|
153
|
+
result.components.components.push({
|
|
154
|
+
id: svc.id,
|
|
155
|
+
name: svc.name,
|
|
156
|
+
type: "service",
|
|
157
|
+
layer: "external",
|
|
158
|
+
path: "",
|
|
159
|
+
description: svc.description,
|
|
160
|
+
technologies: [svc.type],
|
|
161
|
+
});
|
|
162
|
+
// Add connections from components that use this service
|
|
163
|
+
for (const userId of svc.usedBy) {
|
|
164
|
+
result.connections.connections.push({
|
|
165
|
+
from: userId,
|
|
166
|
+
to: svc.id,
|
|
167
|
+
type: "http",
|
|
168
|
+
description: `${userId} uses ${svc.name}`,
|
|
169
|
+
confidence: 70,
|
|
170
|
+
async: false,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Flows: LLM provides these (static has none)
|
|
177
|
+
if (enhancement.flows && enhancement.flows.length > 0) {
|
|
178
|
+
result.connections.flows = enhancement.flows.map((f) => ({
|
|
179
|
+
name: f.name,
|
|
180
|
+
description: f.description,
|
|
181
|
+
steps: f.steps,
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
// LLM Enhancement — Prompt Builder
|
|
2
|
+
// Builds a structured prompt from static analysis results + gaps + docs + key file excerpts
|
|
3
|
+
import { LocalFSBackend } from "../tools/local-fs.js";
|
|
4
|
+
const MAX_FILE_LINES = 100;
|
|
5
|
+
const MAX_FILE_EXCERPTS = 8;
|
|
6
|
+
export function buildSystemPrompt() {
|
|
7
|
+
return `You are an architecture analysis assistant. You will receive:
|
|
8
|
+
1. Static analysis results for a software project (components, connections, infrastructure)
|
|
9
|
+
2. Documentation context (architecture notes, API endpoints, external dependencies)
|
|
10
|
+
3. **Gaps** — specific questions the static analyzer couldn't resolve
|
|
11
|
+
|
|
12
|
+
Your job is to:
|
|
13
|
+
1. **Resolve the gaps** — answer each question using the provided context
|
|
14
|
+
2. **Enhance descriptions** for components (1-2 sentences each)
|
|
15
|
+
3. **Add missing connections** between components
|
|
16
|
+
4. **Identify databases and external services** used by the project
|
|
17
|
+
5. **Suggest user flows** — 2-4 key end-to-end flows through the system
|
|
18
|
+
|
|
19
|
+
## Rules
|
|
20
|
+
- Respond with ONLY valid JSON matching the schema below. No markdown, no explanation.
|
|
21
|
+
- Component IDs in connections MUST match the provided component IDs exactly.
|
|
22
|
+
- Only add connections you are confident about (>80% confidence).
|
|
23
|
+
- Don't repeat connections that already exist.
|
|
24
|
+
- Keep descriptions concise (1-2 sentences max).
|
|
25
|
+
- For databases/externalServices, the "usedBy" array must contain valid component IDs.
|
|
26
|
+
- Flow steps format: "ComponentName → ComponentName" (use the component names, not IDs).
|
|
27
|
+
|
|
28
|
+
## JSON Schema
|
|
29
|
+
{
|
|
30
|
+
"projectDescription": "string — 1-2 sentence project description",
|
|
31
|
+
"primaryLanguage": "string — primary programming language (e.g. TypeScript, Python, Rust, Go)",
|
|
32
|
+
"componentDescriptions": { "component-id": "description string" },
|
|
33
|
+
"componentTypeCorrections": { "component-id": "corrected-type (frontend|api|service|cli|library|database|gateway|worker)" },
|
|
34
|
+
"additionalConnections": [
|
|
35
|
+
{ "from": "component-id", "to": "component-id", "type": "http|import|database|queue|event|grpc|websocket", "description": "string" }
|
|
36
|
+
],
|
|
37
|
+
"databases": [
|
|
38
|
+
{ "id": "slug-id", "name": "Display Name", "type": "postgresql|mysql|mongodb|redis|sqlite", "description": "string", "usedBy": ["component-id"] }
|
|
39
|
+
],
|
|
40
|
+
"externalServices": [
|
|
41
|
+
{ "id": "slug-id", "name": "Display Name", "type": "api|auth|storage|analytics|payment|ai|maps|messaging", "description": "string", "usedBy": ["component-id"] }
|
|
42
|
+
],
|
|
43
|
+
"flows": [
|
|
44
|
+
{ "name": "Flow Name", "description": "string", "steps": ["ComponentA → ComponentB", "ComponentB → ComponentC"] }
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Only include fields where you have meaningful additions. Omit empty arrays/objects.`;
|
|
49
|
+
}
|
|
50
|
+
export async function buildUserPrompt(analysis, projectRoot) {
|
|
51
|
+
const parts = [];
|
|
52
|
+
// Project summary
|
|
53
|
+
parts.push("## Project Summary");
|
|
54
|
+
parts.push(`Name: ${analysis.structure.projectName}`);
|
|
55
|
+
parts.push(`Language: ${analysis.structure.language}${analysis.structure.languages.length > 1 ? ` (also: ${analysis.structure.languages.join(", ")})` : ""}`);
|
|
56
|
+
parts.push(`Framework: ${analysis.structure.framework ?? "none"}`);
|
|
57
|
+
parts.push(`Monorepo: ${analysis.structure.isMonorepo}${analysis.structure.monorepoTool ? ` (${analysis.structure.monorepoTool})` : ""}`);
|
|
58
|
+
parts.push(`Package Manager: ${analysis.structure.packageManager ?? "unknown"}`);
|
|
59
|
+
if (analysis.docs.projectDescription) {
|
|
60
|
+
parts.push(`Description: ${analysis.docs.projectDescription.slice(0, 300)}`);
|
|
61
|
+
}
|
|
62
|
+
parts.push("");
|
|
63
|
+
// Components with IDs
|
|
64
|
+
parts.push("## Components (valid IDs for connections)");
|
|
65
|
+
for (const c of analysis.components.components) {
|
|
66
|
+
parts.push(`- **${c.id}** (type=${c.type}, layer=${c.layer}): ${c.name} @ \`${c.path}\``);
|
|
67
|
+
if (c.technologies.length > 0) {
|
|
68
|
+
parts.push(` Tech: ${c.technologies.join(", ")}`);
|
|
69
|
+
}
|
|
70
|
+
if (c.description) {
|
|
71
|
+
parts.push(` Desc: ${c.description.slice(0, 200)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
parts.push("");
|
|
75
|
+
// Existing connections
|
|
76
|
+
if (analysis.connections.connections.length > 0) {
|
|
77
|
+
parts.push("## Existing Connections (do NOT repeat these)");
|
|
78
|
+
for (const c of analysis.connections.connections) {
|
|
79
|
+
parts.push(`- ${c.from} → ${c.to} (${c.type}): ${c.description}`);
|
|
80
|
+
}
|
|
81
|
+
parts.push("");
|
|
82
|
+
}
|
|
83
|
+
// Infrastructure — Docker
|
|
84
|
+
if (analysis.infra.docker.composeFile) {
|
|
85
|
+
parts.push("## Docker Compose Services");
|
|
86
|
+
if (analysis.infra.docker.composeFilePath) {
|
|
87
|
+
parts.push(`Located in: ${analysis.infra.docker.composeFilePath}/`);
|
|
88
|
+
}
|
|
89
|
+
for (const s of analysis.infra.docker.services) {
|
|
90
|
+
const details = [];
|
|
91
|
+
if (s.image)
|
|
92
|
+
details.push(`image=${s.image}`);
|
|
93
|
+
if (s.buildContext)
|
|
94
|
+
details.push(`build=${s.buildContext}`);
|
|
95
|
+
if (s.ports?.length)
|
|
96
|
+
details.push(`ports=${s.ports.join(",")}`);
|
|
97
|
+
if (s.dependsOn?.length)
|
|
98
|
+
details.push(`depends_on=[${s.dependsOn.join(",")}]`);
|
|
99
|
+
parts.push(`- **${s.name}**: ${details.join(", ")}`);
|
|
100
|
+
if (s.environment && Object.keys(s.environment).length > 0) {
|
|
101
|
+
const envVars = Object.entries(s.environment)
|
|
102
|
+
.slice(0, 5)
|
|
103
|
+
.map(([k, v]) => `${k}=${v?.slice(0, 60) ?? ""}`)
|
|
104
|
+
.join(", ");
|
|
105
|
+
parts.push(` Env: ${envVars}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
parts.push("");
|
|
109
|
+
}
|
|
110
|
+
// Infrastructure — Kubernetes
|
|
111
|
+
if (analysis.infra.kubernetes.resources.length > 0) {
|
|
112
|
+
parts.push("## Kubernetes Resources");
|
|
113
|
+
for (const r of analysis.infra.kubernetes.resources.slice(0, 15)) {
|
|
114
|
+
parts.push(`- ${r.kind}: ${r.name}${r.namespace ? ` (ns: ${r.namespace})` : ""}`);
|
|
115
|
+
}
|
|
116
|
+
parts.push("");
|
|
117
|
+
}
|
|
118
|
+
// Infrastructure — Cloud
|
|
119
|
+
if (analysis.infra.cloud.provider) {
|
|
120
|
+
parts.push(`## Cloud: ${analysis.infra.cloud.provider}`);
|
|
121
|
+
if (analysis.infra.cloud.iac)
|
|
122
|
+
parts.push(`IaC: ${analysis.infra.cloud.iac}`);
|
|
123
|
+
if (analysis.infra.cloud.services.length > 0) {
|
|
124
|
+
parts.push(`Services: ${analysis.infra.cloud.services.join(", ")}`);
|
|
125
|
+
}
|
|
126
|
+
parts.push("");
|
|
127
|
+
}
|
|
128
|
+
// CI/CD
|
|
129
|
+
if (analysis.infra.ci.platform) {
|
|
130
|
+
parts.push(`## CI/CD: ${analysis.infra.ci.platform}`);
|
|
131
|
+
if (analysis.infra.ci.pipelines.length > 0) {
|
|
132
|
+
parts.push(`Pipelines: ${analysis.infra.ci.pipelines.join(", ")}`);
|
|
133
|
+
}
|
|
134
|
+
parts.push("");
|
|
135
|
+
}
|
|
136
|
+
// Event-driven patterns
|
|
137
|
+
if (analysis.events.hasEDA) {
|
|
138
|
+
parts.push("## Event-Driven Patterns");
|
|
139
|
+
for (const p of analysis.events.patterns) {
|
|
140
|
+
parts.push(`- ${p.technology} (${p.dependency})`);
|
|
141
|
+
}
|
|
142
|
+
if (analysis.events.events.length > 0) {
|
|
143
|
+
parts.push("Event usage:");
|
|
144
|
+
for (const e of analysis.events.events.slice(0, 8)) {
|
|
145
|
+
parts.push(` - ${e.type} in ${e.file}: ${e.pattern}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
parts.push("");
|
|
149
|
+
}
|
|
150
|
+
// Documentation context — architecture notes
|
|
151
|
+
if (analysis.docs.architectureNotes.length > 0) {
|
|
152
|
+
parts.push("## Architecture Notes (from documentation)");
|
|
153
|
+
for (const note of analysis.docs.architectureNotes.slice(0, 20)) {
|
|
154
|
+
parts.push(`- ${note}`);
|
|
155
|
+
}
|
|
156
|
+
parts.push("");
|
|
157
|
+
}
|
|
158
|
+
// Documentation context — API endpoints
|
|
159
|
+
if (analysis.docs.apiEndpoints.length > 0) {
|
|
160
|
+
parts.push("## API Endpoints (from docs/OpenAPI)");
|
|
161
|
+
for (const ep of analysis.docs.apiEndpoints.slice(0, 15)) {
|
|
162
|
+
parts.push(`- ${ep.method} ${ep.path}: ${ep.description}`);
|
|
163
|
+
}
|
|
164
|
+
parts.push("");
|
|
165
|
+
}
|
|
166
|
+
// Documentation context — external dependencies
|
|
167
|
+
if (analysis.docs.externalDependencies.length > 0) {
|
|
168
|
+
parts.push("## External Dependencies (mentioned in docs)");
|
|
169
|
+
parts.push(analysis.docs.externalDependencies.join(", "));
|
|
170
|
+
parts.push("");
|
|
171
|
+
}
|
|
172
|
+
// Environments
|
|
173
|
+
if (analysis.envs.environments.length > 0) {
|
|
174
|
+
parts.push("## Environments");
|
|
175
|
+
for (const env of analysis.envs.environments) {
|
|
176
|
+
parts.push(`- ${env.name}: ${env.variables.slice(0, 5).join(", ")}${env.variables.length > 5 ? ` (+${env.variables.length - 5} more)` : ""}`);
|
|
177
|
+
}
|
|
178
|
+
parts.push("");
|
|
179
|
+
}
|
|
180
|
+
// === GAPS — the key section ===
|
|
181
|
+
if (analysis.gaps.length > 0) {
|
|
182
|
+
parts.push("## ⚠ Gaps to Resolve");
|
|
183
|
+
parts.push("The static analyzer identified the following uncertainties. Please resolve as many as possible:");
|
|
184
|
+
parts.push("");
|
|
185
|
+
for (let i = 0; i < analysis.gaps.length; i++) {
|
|
186
|
+
const gap = analysis.gaps[i];
|
|
187
|
+
parts.push(`### Gap ${i + 1}: ${gap.category}`);
|
|
188
|
+
parts.push(gap.description);
|
|
189
|
+
if (gap.context) {
|
|
190
|
+
parts.push(`Context: ${JSON.stringify(gap.context)}`);
|
|
191
|
+
}
|
|
192
|
+
parts.push("");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Key file excerpts
|
|
196
|
+
parts.push("## Key File Excerpts");
|
|
197
|
+
const excerpts = await getKeyFileExcerpts(analysis, projectRoot);
|
|
198
|
+
for (const { file, content } of excerpts) {
|
|
199
|
+
parts.push(`### ${file}`);
|
|
200
|
+
parts.push("```");
|
|
201
|
+
parts.push(content);
|
|
202
|
+
parts.push("```");
|
|
203
|
+
parts.push("");
|
|
204
|
+
}
|
|
205
|
+
return parts.join("\n");
|
|
206
|
+
}
|
|
207
|
+
async function getKeyFileExcerpts(analysis, projectRoot) {
|
|
208
|
+
const fs = new LocalFSBackend(projectRoot);
|
|
209
|
+
const excerpts = [];
|
|
210
|
+
const candidateFiles = [];
|
|
211
|
+
// Entry points
|
|
212
|
+
for (const ep of analysis.structure.entryPoints) {
|
|
213
|
+
candidateFiles.push(ep);
|
|
214
|
+
}
|
|
215
|
+
// Main index/app files for each component
|
|
216
|
+
for (const comp of analysis.components.components) {
|
|
217
|
+
if (comp.path === ".")
|
|
218
|
+
continue;
|
|
219
|
+
candidateFiles.push(`${comp.path}/src/index.ts`, `${comp.path}/src/index.js`, `${comp.path}/src/main.ts`, `${comp.path}/src/main.rs`, `${comp.path}/src/app.ts`, `${comp.path}/src/app.py`, `${comp.path}/main.py`, `${comp.path}/index.ts`, `${comp.path}/index.js`, `${comp.path}/Cargo.toml`, `${comp.path}/pyproject.toml`);
|
|
220
|
+
}
|
|
221
|
+
// Docker compose (check subdirectories too)
|
|
222
|
+
candidateFiles.push("docker-compose.yml", "docker-compose.yaml", "compose.yml");
|
|
223
|
+
if (analysis.infra.docker.composeFilePath) {
|
|
224
|
+
const dir = analysis.infra.docker.composeFilePath;
|
|
225
|
+
candidateFiles.push(`${dir}/docker-compose.yml`, `${dir}/docker-compose.yaml`, `${dir}/compose.yml`);
|
|
226
|
+
}
|
|
227
|
+
// Root package.json
|
|
228
|
+
candidateFiles.push("package.json");
|
|
229
|
+
// Deduplicate
|
|
230
|
+
const seen = new Set();
|
|
231
|
+
const uniqueCandidates = candidateFiles.filter((f) => {
|
|
232
|
+
if (seen.has(f))
|
|
233
|
+
return false;
|
|
234
|
+
seen.add(f);
|
|
235
|
+
return true;
|
|
236
|
+
});
|
|
237
|
+
// Try each candidate, take first N that exist
|
|
238
|
+
for (const file of uniqueCandidates) {
|
|
239
|
+
if (excerpts.length >= MAX_FILE_EXCERPTS)
|
|
240
|
+
break;
|
|
241
|
+
try {
|
|
242
|
+
const content = await fs.readFile(file);
|
|
243
|
+
const lines = content.split("\n").slice(0, MAX_FILE_LINES);
|
|
244
|
+
excerpts.push({ file, content: lines.join("\n") });
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
// File doesn't exist, skip
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return excerpts;
|
|
251
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { LLMEnhancement } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Parse LLM response with 4 fallback strategies.
|
|
4
|
+
* Returns null if all fail (static results used unchanged).
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseLLMResponse(raw: string, validComponentIds: Set<string>): LLMEnhancement | null;
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// LLM Enhancement — Response Parser
|
|
2
|
+
// 4-strategy JSON extraction + ID validation
|
|
3
|
+
/**
|
|
4
|
+
* Parse LLM response with 4 fallback strategies.
|
|
5
|
+
* Returns null if all fail (static results used unchanged).
|
|
6
|
+
*/
|
|
7
|
+
export function parseLLMResponse(raw, validComponentIds) {
|
|
8
|
+
const parsed = extractJSON(raw);
|
|
9
|
+
if (!parsed)
|
|
10
|
+
return null;
|
|
11
|
+
return validateEnhancement(parsed, validComponentIds);
|
|
12
|
+
}
|
|
13
|
+
function extractJSON(raw) {
|
|
14
|
+
// Strategy 1: Direct parse
|
|
15
|
+
try {
|
|
16
|
+
const obj = JSON.parse(raw);
|
|
17
|
+
if (typeof obj === "object" && obj !== null)
|
|
18
|
+
return obj;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Continue to next strategy
|
|
22
|
+
}
|
|
23
|
+
// Strategy 2: Extract from markdown code block
|
|
24
|
+
const codeBlockMatch = raw.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
|
25
|
+
if (codeBlockMatch) {
|
|
26
|
+
try {
|
|
27
|
+
const obj = JSON.parse(codeBlockMatch[1]);
|
|
28
|
+
if (typeof obj === "object" && obj !== null)
|
|
29
|
+
return obj;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Continue
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Strategy 3: Extract first {...} block
|
|
36
|
+
const braceMatch = raw.match(/\{[\s\S]*\}/);
|
|
37
|
+
if (braceMatch) {
|
|
38
|
+
try {
|
|
39
|
+
const obj = JSON.parse(braceMatch[0]);
|
|
40
|
+
if (typeof obj === "object" && obj !== null)
|
|
41
|
+
return obj;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Continue
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// Strategy 4: Return null — static results unchanged
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
function validateEnhancement(raw, validIds) {
|
|
51
|
+
const result = {};
|
|
52
|
+
// Project description
|
|
53
|
+
if (typeof raw.projectDescription === "string" && raw.projectDescription.length > 20) {
|
|
54
|
+
result.projectDescription = raw.projectDescription;
|
|
55
|
+
}
|
|
56
|
+
// Primary language
|
|
57
|
+
if (typeof raw.primaryLanguage === "string" && raw.primaryLanguage.length > 0) {
|
|
58
|
+
result.primaryLanguage = raw.primaryLanguage;
|
|
59
|
+
}
|
|
60
|
+
// Component descriptions
|
|
61
|
+
if (raw.componentDescriptions && typeof raw.componentDescriptions === "object") {
|
|
62
|
+
const descs = {};
|
|
63
|
+
for (const [id, desc] of Object.entries(raw.componentDescriptions)) {
|
|
64
|
+
if (validIds.has(id) && typeof desc === "string" && desc.length > 0) {
|
|
65
|
+
descs[id] = desc;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (Object.keys(descs).length > 0) {
|
|
69
|
+
result.componentDescriptions = descs;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Component type corrections
|
|
73
|
+
if (raw.componentTypeCorrections && typeof raw.componentTypeCorrections === "object") {
|
|
74
|
+
const validTypes = new Set(["frontend", "api", "service", "cli", "library", "database", "gateway", "worker"]);
|
|
75
|
+
const corrections = {};
|
|
76
|
+
for (const [id, type] of Object.entries(raw.componentTypeCorrections)) {
|
|
77
|
+
if (validIds.has(id) && typeof type === "string" && validTypes.has(type)) {
|
|
78
|
+
corrections[id] = type;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (Object.keys(corrections).length > 0) {
|
|
82
|
+
result.componentTypeCorrections = corrections;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Additional connections — validate every from/to
|
|
86
|
+
if (Array.isArray(raw.additionalConnections)) {
|
|
87
|
+
const conns = [];
|
|
88
|
+
for (const c of raw.additionalConnections) {
|
|
89
|
+
if (typeof c === "object" && c !== null &&
|
|
90
|
+
typeof c.from === "string" && validIds.has(c.from) &&
|
|
91
|
+
typeof c.to === "string" && validIds.has(c.to) &&
|
|
92
|
+
c.from !== c.to &&
|
|
93
|
+
typeof c.type === "string") {
|
|
94
|
+
conns.push({
|
|
95
|
+
from: c.from,
|
|
96
|
+
to: c.to,
|
|
97
|
+
type: c.type,
|
|
98
|
+
description: typeof c.description === "string" ? c.description : "",
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// Invalid entries silently dropped
|
|
102
|
+
}
|
|
103
|
+
if (conns.length > 0) {
|
|
104
|
+
result.additionalConnections = conns;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Databases
|
|
108
|
+
if (Array.isArray(raw.databases)) {
|
|
109
|
+
const dbs = [];
|
|
110
|
+
for (const db of raw.databases) {
|
|
111
|
+
if (typeof db === "object" && db !== null &&
|
|
112
|
+
typeof db.id === "string" &&
|
|
113
|
+
typeof db.name === "string" &&
|
|
114
|
+
typeof db.type === "string") {
|
|
115
|
+
const usedBy = Array.isArray(db.usedBy)
|
|
116
|
+
? db.usedBy.filter((id) => validIds.has(id))
|
|
117
|
+
: [];
|
|
118
|
+
dbs.push({
|
|
119
|
+
id: db.id,
|
|
120
|
+
name: db.name,
|
|
121
|
+
type: db.type,
|
|
122
|
+
description: typeof db.description === "string" ? db.description : "",
|
|
123
|
+
usedBy,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (dbs.length > 0) {
|
|
128
|
+
result.databases = dbs;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// External services
|
|
132
|
+
if (Array.isArray(raw.externalServices)) {
|
|
133
|
+
const svcs = [];
|
|
134
|
+
for (const svc of raw.externalServices) {
|
|
135
|
+
if (typeof svc === "object" && svc !== null &&
|
|
136
|
+
typeof svc.id === "string" &&
|
|
137
|
+
typeof svc.name === "string" &&
|
|
138
|
+
typeof svc.type === "string") {
|
|
139
|
+
const usedBy = Array.isArray(svc.usedBy)
|
|
140
|
+
? svc.usedBy.filter((id) => validIds.has(id))
|
|
141
|
+
: [];
|
|
142
|
+
svcs.push({
|
|
143
|
+
id: svc.id,
|
|
144
|
+
name: svc.name,
|
|
145
|
+
type: svc.type,
|
|
146
|
+
description: typeof svc.description === "string" ? svc.description : "",
|
|
147
|
+
usedBy,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (svcs.length > 0) {
|
|
152
|
+
result.externalServices = svcs;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Flows
|
|
156
|
+
if (Array.isArray(raw.flows)) {
|
|
157
|
+
const flows = [];
|
|
158
|
+
for (const f of raw.flows) {
|
|
159
|
+
if (typeof f === "object" && f !== null &&
|
|
160
|
+
typeof f.name === "string" &&
|
|
161
|
+
Array.isArray(f.steps)) {
|
|
162
|
+
flows.push({
|
|
163
|
+
name: f.name,
|
|
164
|
+
description: typeof f.description === "string" ? f.description : "",
|
|
165
|
+
steps: f.steps.filter((s) => typeof s === "string"),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (flows.length > 0) {
|
|
170
|
+
result.flows = flows;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface LLMEnhancement {
|
|
2
|
+
projectDescription?: string;
|
|
3
|
+
primaryLanguage?: string;
|
|
4
|
+
componentDescriptions?: Record<string, string>;
|
|
5
|
+
componentTypeCorrections?: Record<string, string>;
|
|
6
|
+
additionalConnections?: Array<{
|
|
7
|
+
from: string;
|
|
8
|
+
to: string;
|
|
9
|
+
type: string;
|
|
10
|
+
description: string;
|
|
11
|
+
}>;
|
|
12
|
+
databases?: Array<{
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
type: string;
|
|
16
|
+
description: string;
|
|
17
|
+
usedBy: string[];
|
|
18
|
+
}>;
|
|
19
|
+
externalServices?: Array<{
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
type: string;
|
|
23
|
+
description: string;
|
|
24
|
+
usedBy: string[];
|
|
25
|
+
}>;
|
|
26
|
+
flows?: Array<{
|
|
27
|
+
name: string;
|
|
28
|
+
description: string;
|
|
29
|
+
steps: string[];
|
|
30
|
+
}>;
|
|
31
|
+
}
|