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.
Files changed (142) hide show
  1. package/README.md +282 -0
  2. package/bin/archbyte.js +213 -0
  3. package/dist/agents/core/component-detector.d.ts +2 -0
  4. package/dist/agents/core/component-detector.js +57 -0
  5. package/dist/agents/core/connection-mapper.d.ts +2 -0
  6. package/dist/agents/core/connection-mapper.js +77 -0
  7. package/dist/agents/core/doc-parser.d.ts +2 -0
  8. package/dist/agents/core/doc-parser.js +64 -0
  9. package/dist/agents/core/env-detector.d.ts +2 -0
  10. package/dist/agents/core/env-detector.js +51 -0
  11. package/dist/agents/core/event-detector.d.ts +2 -0
  12. package/dist/agents/core/event-detector.js +59 -0
  13. package/dist/agents/core/infra-analyzer.d.ts +2 -0
  14. package/dist/agents/core/infra-analyzer.js +72 -0
  15. package/dist/agents/core/structure-scanner.d.ts +2 -0
  16. package/dist/agents/core/structure-scanner.js +55 -0
  17. package/dist/agents/core/validator.d.ts +2 -0
  18. package/dist/agents/core/validator.js +74 -0
  19. package/dist/agents/index.d.ts +24 -0
  20. package/dist/agents/index.js +73 -0
  21. package/dist/agents/llm/index.d.ts +8 -0
  22. package/dist/agents/llm/index.js +185 -0
  23. package/dist/agents/llm/prompt-builder.d.ts +3 -0
  24. package/dist/agents/llm/prompt-builder.js +251 -0
  25. package/dist/agents/llm/response-parser.d.ts +6 -0
  26. package/dist/agents/llm/response-parser.js +174 -0
  27. package/dist/agents/llm/types.d.ts +31 -0
  28. package/dist/agents/llm/types.js +2 -0
  29. package/dist/agents/pipeline/agents/component-identifier.d.ts +3 -0
  30. package/dist/agents/pipeline/agents/component-identifier.js +102 -0
  31. package/dist/agents/pipeline/agents/connection-mapper.d.ts +3 -0
  32. package/dist/agents/pipeline/agents/connection-mapper.js +126 -0
  33. package/dist/agents/pipeline/agents/flow-detector.d.ts +3 -0
  34. package/dist/agents/pipeline/agents/flow-detector.js +101 -0
  35. package/dist/agents/pipeline/agents/service-describer.d.ts +3 -0
  36. package/dist/agents/pipeline/agents/service-describer.js +100 -0
  37. package/dist/agents/pipeline/agents/validator.d.ts +3 -0
  38. package/dist/agents/pipeline/agents/validator.js +102 -0
  39. package/dist/agents/pipeline/index.d.ts +13 -0
  40. package/dist/agents/pipeline/index.js +128 -0
  41. package/dist/agents/pipeline/merger.d.ts +7 -0
  42. package/dist/agents/pipeline/merger.js +212 -0
  43. package/dist/agents/pipeline/response-parser.d.ts +5 -0
  44. package/dist/agents/pipeline/response-parser.js +43 -0
  45. package/dist/agents/pipeline/types.d.ts +92 -0
  46. package/dist/agents/pipeline/types.js +3 -0
  47. package/dist/agents/prompt-data.d.ts +1 -0
  48. package/dist/agents/prompt-data.js +15 -0
  49. package/dist/agents/prompts-encode.d.ts +9 -0
  50. package/dist/agents/prompts-encode.js +26 -0
  51. package/dist/agents/prompts.d.ts +12 -0
  52. package/dist/agents/prompts.js +30 -0
  53. package/dist/agents/providers/anthropic.d.ts +10 -0
  54. package/dist/agents/providers/anthropic.js +117 -0
  55. package/dist/agents/providers/google.d.ts +10 -0
  56. package/dist/agents/providers/google.js +136 -0
  57. package/dist/agents/providers/ollama.d.ts +9 -0
  58. package/dist/agents/providers/ollama.js +162 -0
  59. package/dist/agents/providers/openai.d.ts +9 -0
  60. package/dist/agents/providers/openai.js +142 -0
  61. package/dist/agents/providers/router.d.ts +7 -0
  62. package/dist/agents/providers/router.js +55 -0
  63. package/dist/agents/runtime/orchestrator.d.ts +34 -0
  64. package/dist/agents/runtime/orchestrator.js +193 -0
  65. package/dist/agents/runtime/registry.d.ts +23 -0
  66. package/dist/agents/runtime/registry.js +56 -0
  67. package/dist/agents/runtime/types.d.ts +117 -0
  68. package/dist/agents/runtime/types.js +29 -0
  69. package/dist/agents/static/code-sampler.d.ts +3 -0
  70. package/dist/agents/static/code-sampler.js +153 -0
  71. package/dist/agents/static/component-detector.d.ts +3 -0
  72. package/dist/agents/static/component-detector.js +404 -0
  73. package/dist/agents/static/connection-mapper.d.ts +3 -0
  74. package/dist/agents/static/connection-mapper.js +280 -0
  75. package/dist/agents/static/doc-parser.d.ts +3 -0
  76. package/dist/agents/static/doc-parser.js +358 -0
  77. package/dist/agents/static/env-detector.d.ts +3 -0
  78. package/dist/agents/static/env-detector.js +73 -0
  79. package/dist/agents/static/event-detector.d.ts +3 -0
  80. package/dist/agents/static/event-detector.js +70 -0
  81. package/dist/agents/static/file-tree-collector.d.ts +3 -0
  82. package/dist/agents/static/file-tree-collector.js +51 -0
  83. package/dist/agents/static/index.d.ts +19 -0
  84. package/dist/agents/static/index.js +307 -0
  85. package/dist/agents/static/infra-analyzer.d.ts +3 -0
  86. package/dist/agents/static/infra-analyzer.js +208 -0
  87. package/dist/agents/static/structure-scanner.d.ts +3 -0
  88. package/dist/agents/static/structure-scanner.js +195 -0
  89. package/dist/agents/static/types.d.ts +165 -0
  90. package/dist/agents/static/types.js +2 -0
  91. package/dist/agents/static/utils.d.ts +21 -0
  92. package/dist/agents/static/utils.js +146 -0
  93. package/dist/agents/static/validator.d.ts +2 -0
  94. package/dist/agents/static/validator.js +75 -0
  95. package/dist/agents/tools/claude-code.d.ts +38 -0
  96. package/dist/agents/tools/claude-code.js +129 -0
  97. package/dist/agents/tools/local-fs.d.ts +12 -0
  98. package/dist/agents/tools/local-fs.js +112 -0
  99. package/dist/agents/tools/tool-definitions.d.ts +6 -0
  100. package/dist/agents/tools/tool-definitions.js +66 -0
  101. package/dist/cli/analyze.d.ts +27 -0
  102. package/dist/cli/analyze.js +586 -0
  103. package/dist/cli/auth.d.ts +46 -0
  104. package/dist/cli/auth.js +397 -0
  105. package/dist/cli/config.d.ts +11 -0
  106. package/dist/cli/config.js +177 -0
  107. package/dist/cli/diff.d.ts +10 -0
  108. package/dist/cli/diff.js +144 -0
  109. package/dist/cli/export.d.ts +10 -0
  110. package/dist/cli/export.js +321 -0
  111. package/dist/cli/gate.d.ts +13 -0
  112. package/dist/cli/gate.js +131 -0
  113. package/dist/cli/generate.d.ts +10 -0
  114. package/dist/cli/generate.js +213 -0
  115. package/dist/cli/license-gate.d.ts +27 -0
  116. package/dist/cli/license-gate.js +121 -0
  117. package/dist/cli/patrol.d.ts +15 -0
  118. package/dist/cli/patrol.js +212 -0
  119. package/dist/cli/run.d.ts +11 -0
  120. package/dist/cli/run.js +24 -0
  121. package/dist/cli/serve.d.ts +9 -0
  122. package/dist/cli/serve.js +65 -0
  123. package/dist/cli/setup.d.ts +1 -0
  124. package/dist/cli/setup.js +233 -0
  125. package/dist/cli/shared.d.ts +68 -0
  126. package/dist/cli/shared.js +275 -0
  127. package/dist/cli/stats.d.ts +9 -0
  128. package/dist/cli/stats.js +158 -0
  129. package/dist/cli/ui.d.ts +18 -0
  130. package/dist/cli/ui.js +144 -0
  131. package/dist/cli/validate.d.ts +54 -0
  132. package/dist/cli/validate.js +315 -0
  133. package/dist/cli/workflow.d.ts +10 -0
  134. package/dist/cli/workflow.js +594 -0
  135. package/dist/server/src/generator/index.d.ts +123 -0
  136. package/dist/server/src/generator/index.js +254 -0
  137. package/dist/server/src/index.d.ts +8 -0
  138. package/dist/server/src/index.js +1311 -0
  139. package/package.json +62 -0
  140. package/ui/dist/assets/index-B66Til39.js +70 -0
  141. package/ui/dist/assets/index-BE2OWbzu.css +1 -0
  142. 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,3 @@
1
+ import type { StaticAnalysisResult } from "../static/types.js";
2
+ export declare function buildSystemPrompt(): string;
3
+ export declare function buildUserPrompt(analysis: StaticAnalysisResult, projectRoot: string): Promise<string>;
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ // LLM Enhancement — Types
2
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { PipelineAgent } from "../types.js";
2
+ import "../../prompt-data.js";
3
+ export declare const componentIdentifier: PipelineAgent;