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,307 @@
1
+ // Static Analysis — Orchestrator
2
+ // Runs all scanners in dependency order and returns a complete StaticAnalysisResult
3
+ import { StaticToolkit } from "./utils.js";
4
+ import { scanStructure } from "./structure-scanner.js";
5
+ import { parseDocs } from "./doc-parser.js";
6
+ import { detectComponents } from "./component-detector.js";
7
+ import { analyzeInfra } from "./infra-analyzer.js";
8
+ import { detectEvents } from "./event-detector.js";
9
+ import { detectEnvironments } from "./env-detector.js";
10
+ import { mapConnections } from "./connection-mapper.js";
11
+ import { validateAnalysis } from "./validator.js";
12
+ import { collectFileTree } from "./file-tree-collector.js";
13
+ import { collectCodeSamples } from "./code-sampler.js";
14
+ export { validateAnalysis } from "./validator.js";
15
+ /**
16
+ * Run all static analysis scanners.
17
+ *
18
+ * Pipeline:
19
+ * 1. Parallel: structure, docs, infra, events, envs
20
+ * 2. Sequential: components (needs structure), connections (needs components+infra+events)
21
+ * 3. Validation + auto-repair
22
+ * 4. Gap detection — identify what the LLM should resolve
23
+ */
24
+ export async function runStaticAnalysis(projectRoot, onProgress) {
25
+ const tk = new StaticToolkit(projectRoot);
26
+ // Phase 1: parallel scanners (no dependencies)
27
+ onProgress?.("Running parallel scanners...");
28
+ const [structure, docs, infra, events, envs] = await Promise.all([
29
+ scanStructure(tk),
30
+ parseDocs(tk),
31
+ analyzeInfra(tk),
32
+ detectEvents(tk),
33
+ detectEnvironments(tk),
34
+ ]);
35
+ onProgress?.(`Detected: ${structure.language}, ${structure.framework ?? "no framework"}, monorepo=${structure.isMonorepo}`);
36
+ // Phase 2: component detection (needs structure for monorepo info)
37
+ onProgress?.("Detecting components...");
38
+ const components = await detectComponents(tk, structure);
39
+ onProgress?.(`Found ${components.components.length} component(s)`);
40
+ // Phase 3: connection mapping (needs components + infra + events)
41
+ onProgress?.("Mapping connections...");
42
+ const connections = await mapConnections(tk, components.components, infra, events);
43
+ onProgress?.(`Found ${connections.connections.length} connection(s)`);
44
+ // Assemble result
45
+ const analysis = {
46
+ structure,
47
+ docs,
48
+ components,
49
+ infra,
50
+ events,
51
+ envs,
52
+ connections,
53
+ validation: { valid: true, repairs: [], errors: [] },
54
+ gaps: [],
55
+ };
56
+ // Phase 4: validate + auto-repair
57
+ onProgress?.("Validating...");
58
+ analysis.validation = validateAnalysis(analysis);
59
+ if (analysis.validation.repairs.length > 0) {
60
+ onProgress?.(`Applied ${analysis.validation.repairs.length} auto-repair(s)`);
61
+ }
62
+ // Phase 5: identify gaps for LLM resolution
63
+ analysis.gaps = await collectGaps(analysis, tk);
64
+ if (analysis.gaps.length > 0) {
65
+ onProgress?.(`Identified ${analysis.gaps.length} gap(s) for LLM resolution`);
66
+ }
67
+ return analysis;
68
+ }
69
+ /**
70
+ * Analyze the static results and identify gaps the LLM should resolve.
71
+ * Fully generic — works for any project structure.
72
+ */
73
+ async function collectGaps(analysis, tk) {
74
+ const gaps = [];
75
+ const componentIds = new Set(analysis.components.components.map((c) => c.id));
76
+ const connectedIds = new Set();
77
+ for (const c of analysis.connections.connections) {
78
+ connectedIds.add(c.from);
79
+ connectedIds.add(c.to);
80
+ }
81
+ // 1. Unknown primary language
82
+ if (analysis.structure.language === "unknown") {
83
+ gaps.push({
84
+ category: "unknown_primary_language",
85
+ description: "Could not determine the primary programming language from config files. Please infer from the component technologies and file structure.",
86
+ context: {
87
+ languages: analysis.structure.languages,
88
+ componentTechs: analysis.components.components.map((c) => ({
89
+ id: c.id,
90
+ technologies: c.technologies,
91
+ })),
92
+ },
93
+ });
94
+ }
95
+ // 2. Unresolved docker services — services that couldn't map to any component
96
+ if (analysis.infra.docker.composeFile) {
97
+ for (const svc of analysis.infra.docker.services) {
98
+ // Check if this service name or its build context maps to any component
99
+ const isConnected = analysis.connections.connections.some((c) => c.description.includes(svc.name));
100
+ const matchesComponent = analysis.components.components.some((c) => c.id === svc.name || c.path === svc.name ||
101
+ c.name.toLowerCase() === svc.name.toLowerCase());
102
+ if (!isConnected && !matchesComponent) {
103
+ gaps.push({
104
+ category: "unresolved_docker_service",
105
+ description: `Docker service "${svc.name}" could not be mapped to any detected component. What component does it belong to, or is it an external service (database, cache, queue)?`,
106
+ context: {
107
+ serviceName: svc.name,
108
+ image: svc.image,
109
+ buildContext: svc.buildContext,
110
+ ports: svc.ports,
111
+ dependsOn: svc.dependsOn,
112
+ environment: svc.environment,
113
+ },
114
+ });
115
+ }
116
+ }
117
+ }
118
+ // 3. Isolated components — no connections at all
119
+ for (const comp of analysis.components.components) {
120
+ if (!connectedIds.has(comp.id)) {
121
+ gaps.push({
122
+ category: "isolated_component",
123
+ description: `Component "${comp.name}" (${comp.id}) has no connections to other components. How does it communicate with the rest of the system?`,
124
+ relatedComponentIds: [comp.id],
125
+ context: {
126
+ type: comp.type,
127
+ path: comp.path,
128
+ technologies: comp.technologies,
129
+ },
130
+ });
131
+ }
132
+ }
133
+ // 4. Weak or missing descriptions
134
+ for (const comp of analysis.components.components) {
135
+ if (!comp.description || comp.description.length < 20) {
136
+ gaps.push({
137
+ category: "weak_description",
138
+ description: `Component "${comp.name}" has a weak or missing description. Please provide a 1-2 sentence description of its purpose and role in the system.`,
139
+ relatedComponentIds: [comp.id],
140
+ context: {
141
+ currentDescription: comp.description,
142
+ type: comp.type,
143
+ path: comp.path,
144
+ technologies: comp.technologies,
145
+ },
146
+ });
147
+ }
148
+ }
149
+ // 5. Missing database — DB env vars found in components but no database component exists
150
+ const hasDbComponent = analysis.components.components.some((c) => c.type === "database" || c.layer === "data");
151
+ if (!hasDbComponent) {
152
+ // Check if any docker service looks like a database
153
+ const dbDockerServices = analysis.infra.docker.services.filter((s) => {
154
+ const img = (s.image ?? "").toLowerCase();
155
+ return img.includes("postgres") || img.includes("mysql") || img.includes("mongo") ||
156
+ img.includes("mariadb") || img.includes("redis") || img.includes("sqlite");
157
+ });
158
+ // Check if docs mention databases
159
+ const dbMentionsInDocs = analysis.docs.externalDependencies.filter((d) => /postgres|mysql|mongo|redis|sqlite|database|supabase|firebase/i.test(d));
160
+ // Check if any component has DB env vars (this was detected by connection-mapper)
161
+ const dbConnections = analysis.connections.connections.filter((c) => c.type === "database");
162
+ if (dbDockerServices.length > 0 || dbMentionsInDocs.length > 0 || dbConnections.length > 0) {
163
+ gaps.push({
164
+ category: "missing_database",
165
+ description: "Database references were found (Docker services, env vars, or docs) but no database component was detected. What databases does this project use?",
166
+ context: {
167
+ dockerDbServices: dbDockerServices.map((s) => ({ name: s.name, image: s.image })),
168
+ docMentions: dbMentionsInDocs,
169
+ dbConnectionCount: dbConnections.length,
170
+ },
171
+ });
172
+ }
173
+ }
174
+ // 6. External services mentioned in docs but not modeled
175
+ if (analysis.docs.externalDependencies.length > 0) {
176
+ const modeledExternals = new Set(analysis.components.components
177
+ .filter((c) => c.layer === "external")
178
+ .flatMap((c) => c.technologies.map((t) => t.toLowerCase())));
179
+ const unmodeledServices = analysis.docs.externalDependencies.filter((dep) => !modeledExternals.has(dep.toLowerCase()));
180
+ if (unmodeledServices.length > 0) {
181
+ gaps.push({
182
+ category: "unmodeled_external_service",
183
+ description: `External services mentioned in documentation but not modeled as components: ${unmodeledServices.join(", ")}. Which components use these services, and how?`,
184
+ context: { services: unmodeledServices },
185
+ });
186
+ }
187
+ }
188
+ // 7. API endpoints found but not tied to connections
189
+ if (analysis.docs.apiEndpoints.length > 0 && analysis.connections.connections.length === 0) {
190
+ gaps.push({
191
+ category: "unmapped_api_endpoints",
192
+ description: `${analysis.docs.apiEndpoints.length} API endpoint(s) found in docs but no connections were detected. Which components expose and consume these APIs?`,
193
+ context: {
194
+ endpoints: analysis.docs.apiEndpoints.slice(0, 10),
195
+ },
196
+ });
197
+ }
198
+ // 8. Event-driven patterns detected but no event connections
199
+ if (analysis.events.hasEDA) {
200
+ const hasEventConnections = analysis.connections.connections.some((c) => c.type === "event" || c.type === "queue");
201
+ if (!hasEventConnections) {
202
+ gaps.push({
203
+ category: "event_pattern_no_connection",
204
+ description: `Event-driven patterns detected (${analysis.events.patterns.map((p) => p.technology).join(", ")}) but no event/queue connections were mapped. Which components publish and subscribe to events?`,
205
+ relatedComponentIds: analysis.components.components.map((c) => c.id),
206
+ context: {
207
+ patterns: analysis.events.patterns,
208
+ events: analysis.events.events.slice(0, 10),
209
+ },
210
+ });
211
+ }
212
+ }
213
+ // 9. Ambiguous component types — heuristic: Flutter/React Native should be "frontend" not "service"
214
+ for (const comp of analysis.components.components) {
215
+ const techs = comp.technologies.map((t) => t.toLowerCase());
216
+ const isFrontendTech = techs.some((t) => ["flutter", "react native", "react", "vue", "angular", "svelte", "next.js", "nuxt"].includes(t));
217
+ if (isFrontendTech && comp.type !== "frontend") {
218
+ gaps.push({
219
+ category: "ambiguous_component_type",
220
+ description: `Component "${comp.name}" uses ${comp.technologies.join(", ")} (frontend technologies) but is typed as "${comp.type}". Should it be "frontend"?`,
221
+ relatedComponentIds: [comp.id],
222
+ context: {
223
+ currentType: comp.type,
224
+ technologies: comp.technologies,
225
+ },
226
+ });
227
+ }
228
+ }
229
+ // 10. Large components that may contain multiple logical modules
230
+ const skipForSubdirScan = new Set(["node_modules", "dist", "build", ".git", "coverage", "venv", "__pycache__", "target"]);
231
+ for (const comp of analysis.components.components) {
232
+ if (comp.path === ".")
233
+ continue;
234
+ try {
235
+ const entries = await tk.listDir(comp.path);
236
+ const subdirs = entries
237
+ .filter((e) => e.type === "directory" && !skipForSubdirScan.has(e.name) && !e.name.startsWith("."))
238
+ .map((e) => e.name);
239
+ if (subdirs.length >= 8) {
240
+ gaps.push({
241
+ category: "isolated_component",
242
+ description: `Component "${comp.name}" at ${comp.path}/ has ${subdirs.length} subdirectories that may be separate logical modules. Should any of these be their own component? List: ${subdirs.join(", ")}`,
243
+ relatedComponentIds: [comp.id],
244
+ context: {
245
+ subdirectories: subdirs,
246
+ count: subdirs.length,
247
+ },
248
+ });
249
+ }
250
+ }
251
+ catch {
252
+ // Can't list dir, skip
253
+ }
254
+ }
255
+ // 11. Top-level directories with many subdirectories not detected as components
256
+ // (e.g. skills/, apps/ with 50+ items that aren't workspace members)
257
+ const componentPaths = new Set(analysis.components.components.map((c) => c.path));
258
+ const topDirs = Object.keys(analysis.structure.directories);
259
+ for (const dir of topDirs) {
260
+ if (componentPaths.has(dir))
261
+ continue;
262
+ if (skipForSubdirScan.has(dir) || dir.startsWith("."))
263
+ continue;
264
+ try {
265
+ const entries = await tk.listDir(dir);
266
+ const subdirs = entries
267
+ .filter((e) => e.type === "directory" && !e.name.startsWith("."))
268
+ .map((e) => e.name);
269
+ if (subdirs.length >= 5) {
270
+ gaps.push({
271
+ category: "isolated_component",
272
+ description: `Directory "${dir}/" has ${subdirs.length} subdirectories but was not detected as a component. These may be plugins, extensions, skills, or other modules: ${subdirs.slice(0, 20).join(", ")}${subdirs.length > 20 ? ` (+${subdirs.length - 20} more)` : ""}`,
273
+ context: {
274
+ directory: dir,
275
+ subdirectories: subdirs.slice(0, 30),
276
+ count: subdirs.length,
277
+ },
278
+ });
279
+ }
280
+ }
281
+ catch {
282
+ // Skip
283
+ }
284
+ }
285
+ return gaps;
286
+ }
287
+ /**
288
+ * Collect raw static context from all 7 scanners.
289
+ * This runs ONLY fact-collectors (no component-detector, connection-mapper, or validator).
290
+ * Output is consumed by the pipeline LLM agents.
291
+ */
292
+ export async function runStaticContextCollection(projectRoot, onProgress) {
293
+ const tk = new StaticToolkit(projectRoot);
294
+ onProgress?.("Collecting static context (7 scanners in parallel)...");
295
+ const [structure, docs, infra, events, envs, fileTree, codeSamples] = await Promise.all([
296
+ scanStructure(tk),
297
+ parseDocs(tk),
298
+ analyzeInfra(tk),
299
+ detectEvents(tk),
300
+ detectEnvironments(tk),
301
+ collectFileTree(tk),
302
+ collectCodeSamples(tk),
303
+ ]);
304
+ onProgress?.(`Context: ${fileTree.totalFiles} files, ${fileTree.totalDirs} dirs, ${codeSamples.configFiles.length} configs, ${codeSamples.samples.length} samples`);
305
+ onProgress?.(`Detected: ${structure.language}, ${structure.framework ?? "no framework"}, monorepo=${structure.isMonorepo}`);
306
+ return { structure, docs, infra, events, envs, fileTree, codeSamples };
307
+ }
@@ -0,0 +1,3 @@
1
+ import type { InfraResult } from "./types.js";
2
+ import type { StaticToolkit } from "./utils.js";
3
+ export declare function analyzeInfra(tk: StaticToolkit): Promise<InfraResult>;
@@ -0,0 +1,208 @@
1
+ // Static Analysis — Infrastructure Analyzer
2
+ // Parses Docker, Kubernetes, CI/CD, and cloud infrastructure via js-yaml
3
+ export async function analyzeInfra(tk) {
4
+ const result = {
5
+ docker: { services: [], composeFile: false },
6
+ kubernetes: { resources: [] },
7
+ cloud: { provider: null, services: [], iac: null },
8
+ ci: { platform: null, pipelines: [] },
9
+ };
10
+ await Promise.all([
11
+ parseDockerCompose(tk, result),
12
+ parseKubernetes(tk, result),
13
+ detectCI(tk, result),
14
+ detectCloud(tk, result),
15
+ ]);
16
+ return result;
17
+ }
18
+ async function parseDockerCompose(tk, result) {
19
+ // Try common compose file names at root and in subdirectories
20
+ const composeNames = ["docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"];
21
+ let composeData = null;
22
+ let composeDir = "";
23
+ // Check root first
24
+ for (const name of composeNames) {
25
+ composeData = await tk.readYAML(name);
26
+ if (composeData) {
27
+ composeDir = "";
28
+ break;
29
+ }
30
+ }
31
+ // If not at root, search subdirectories
32
+ if (!composeData) {
33
+ const rootEntries = await tk.listDir(".");
34
+ const dirs = rootEntries.filter((e) => e.type === "directory").map((e) => e.name);
35
+ for (const dir of dirs) {
36
+ if (dir.startsWith(".") || dir === "node_modules" || dir === "dist")
37
+ continue;
38
+ for (const name of composeNames) {
39
+ composeData = await tk.readYAML(`${dir}/${name}`);
40
+ if (composeData) {
41
+ composeDir = dir;
42
+ break;
43
+ }
44
+ }
45
+ if (composeData)
46
+ break;
47
+ }
48
+ }
49
+ if (!composeData || typeof composeData !== "object")
50
+ return;
51
+ result.docker.composeFile = true;
52
+ result.docker.composeFilePath = composeDir || undefined;
53
+ const compose = composeData;
54
+ const services = compose.services;
55
+ if (!services)
56
+ return;
57
+ for (const [name, svc] of Object.entries(services)) {
58
+ const ports = svc.ports?.map(String) ?? [];
59
+ const dependsOn = Array.isArray(svc.depends_on)
60
+ ? svc.depends_on
61
+ : svc.depends_on && typeof svc.depends_on === "object"
62
+ ? Object.keys(svc.depends_on)
63
+ : [];
64
+ const envObj = svc.environment;
65
+ const environment = {};
66
+ if (Array.isArray(envObj)) {
67
+ for (const e of envObj) {
68
+ const [k, v] = e.split("=");
69
+ if (k)
70
+ environment[k] = v ?? "";
71
+ }
72
+ }
73
+ else if (envObj && typeof envObj === "object") {
74
+ Object.assign(environment, envObj);
75
+ }
76
+ // Extract build context
77
+ let buildContext;
78
+ if (svc.build) {
79
+ if (typeof svc.build === "string") {
80
+ buildContext = svc.build;
81
+ }
82
+ else if (typeof svc.build === "object") {
83
+ const buildObj = svc.build;
84
+ buildContext = buildObj.context;
85
+ }
86
+ }
87
+ result.docker.services.push({
88
+ name,
89
+ image: svc.image,
90
+ buildContext,
91
+ ports,
92
+ dependsOn,
93
+ environment,
94
+ });
95
+ }
96
+ }
97
+ async function parseKubernetes(tk, result) {
98
+ const k8sFiles = await tk.globFiles("k8s/**/*.{yml,yaml}");
99
+ const k8sRoot = await tk.globFiles("*.{yml,yaml}");
100
+ // Also check common k8s dirs
101
+ const moreDirs = await Promise.all([
102
+ tk.globFiles("kubernetes/**/*.{yml,yaml}"),
103
+ tk.globFiles("deploy/**/*.{yml,yaml}"),
104
+ tk.globFiles("manifests/**/*.{yml,yaml}"),
105
+ ]);
106
+ const allFiles = [...k8sFiles, ...moreDirs.flat()];
107
+ // Filter out docker-compose and CI files
108
+ const k8sManifests = allFiles.filter((f) => !f.includes("docker-compose") && !f.includes("compose.y"));
109
+ for (const file of k8sManifests.slice(0, 20)) {
110
+ const docs = await tk.readYAMLAll(file);
111
+ for (const doc of docs) {
112
+ if (!doc || typeof doc !== "object")
113
+ continue;
114
+ const resource = doc;
115
+ if (resource.kind && resource.metadata) {
116
+ const meta = resource.metadata;
117
+ result.kubernetes.resources.push({
118
+ kind: resource.kind,
119
+ name: meta.name ?? "unknown",
120
+ namespace: meta.namespace,
121
+ });
122
+ }
123
+ }
124
+ }
125
+ }
126
+ async function detectCI(tk, result) {
127
+ // Check for CI configs in parallel
128
+ const [ghWorkflows, gitlabCI, circleci, jenkinsfile] = await Promise.all([
129
+ tk.globFiles(".github/workflows/*.{yml,yaml}"),
130
+ tk.readFileSafe(".gitlab-ci.yml"),
131
+ tk.readFileSafe(".circleci/config.yml"),
132
+ tk.readFileSafe("Jenkinsfile"),
133
+ ]);
134
+ if (ghWorkflows.length > 0) {
135
+ result.ci.platform = "GitHub Actions";
136
+ result.ci.pipelines = ghWorkflows.map((f) => f.replace(".github/workflows/", "").replace(/\.ya?ml$/, ""));
137
+ }
138
+ else if (gitlabCI) {
139
+ result.ci.platform = "GitLab CI";
140
+ result.ci.pipelines = ["gitlab-ci"];
141
+ }
142
+ else if (circleci) {
143
+ result.ci.platform = "CircleCI";
144
+ result.ci.pipelines = ["circleci"];
145
+ }
146
+ else if (jenkinsfile) {
147
+ result.ci.platform = "Jenkins";
148
+ result.ci.pipelines = ["Jenkinsfile"];
149
+ }
150
+ }
151
+ async function detectCloud(tk, result) {
152
+ // Check for IaC and cloud config in parallel
153
+ const [terraformFiles, wranglerToml, vercelJson, netlifyToml, awsCdk, samTemplate, serverlessYml,] = await Promise.all([
154
+ tk.globFiles("**/*.tf"),
155
+ tk.readFileSafe("wrangler.toml"),
156
+ tk.readJSON("vercel.json"),
157
+ tk.readFileSafe("netlify.toml"),
158
+ tk.readJSON("cdk.json"),
159
+ tk.readYAML("template.yaml"), // AWS SAM
160
+ tk.readYAML("serverless.yml"),
161
+ ]);
162
+ if (terraformFiles.length > 0) {
163
+ result.cloud.iac = "Terraform";
164
+ // Try to detect provider from tf files
165
+ for (const tf of terraformFiles.slice(0, 5)) {
166
+ const content = await tk.readFileSafe(tf);
167
+ if (content?.includes("aws_")) {
168
+ result.cloud.provider = "AWS";
169
+ break;
170
+ }
171
+ if (content?.includes("google_")) {
172
+ result.cloud.provider = "GCP";
173
+ break;
174
+ }
175
+ if (content?.includes("azurerm_")) {
176
+ result.cloud.provider = "Azure";
177
+ break;
178
+ }
179
+ if (content?.includes("digitalocean_")) {
180
+ result.cloud.provider = "DigitalOcean";
181
+ break;
182
+ }
183
+ }
184
+ }
185
+ if (wranglerToml) {
186
+ result.cloud.provider = result.cloud.provider ?? "Cloudflare";
187
+ result.cloud.services.push("Cloudflare Workers");
188
+ }
189
+ if (vercelJson) {
190
+ result.cloud.provider = result.cloud.provider ?? "Vercel";
191
+ result.cloud.services.push("Vercel");
192
+ }
193
+ if (netlifyToml) {
194
+ result.cloud.provider = result.cloud.provider ?? "Netlify";
195
+ result.cloud.services.push("Netlify");
196
+ }
197
+ if (awsCdk) {
198
+ result.cloud.iac = "AWS CDK";
199
+ result.cloud.provider = result.cloud.provider ?? "AWS";
200
+ }
201
+ if (samTemplate) {
202
+ result.cloud.iac = "AWS SAM";
203
+ result.cloud.provider = result.cloud.provider ?? "AWS";
204
+ }
205
+ if (serverlessYml) {
206
+ result.cloud.iac = "Serverless Framework";
207
+ }
208
+ }
@@ -0,0 +1,3 @@
1
+ import type { StructureResult } from "./types.js";
2
+ import type { StaticToolkit } from "./utils.js";
3
+ export declare function scanStructure(tk: StaticToolkit): Promise<StructureResult>;