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,404 @@
1
+ // Static Analysis — Component Detector
2
+ // Detects project components via workspaces, conventional directories, build configs, or single-app fallback
3
+ import { slugify, assignLayer } from "./utils.js";
4
+ // Map well-known deps to architecturally significant tech names
5
+ const TECH_MAP = {
6
+ react: "React", "react-dom": "React", "next": "Next.js", "vue": "Vue", "nuxt": "Nuxt",
7
+ svelte: "Svelte", angular: "Angular", "@angular/core": "Angular",
8
+ express: "Express", fastify: "Fastify", "@nestjs/core": "NestJS", hono: "Hono", koa: "Koa",
9
+ prisma: "Prisma", "@prisma/client": "Prisma", typeorm: "TypeORM", drizzle: "Drizzle",
10
+ sequelize: "Sequelize", mongoose: "Mongoose", pg: "PostgreSQL", mysql2: "MySQL",
11
+ redis: "Redis", ioredis: "Redis",
12
+ graphql: "GraphQL", "@apollo/server": "Apollo GraphQL",
13
+ "socket.io": "Socket.IO", ws: "WebSocket",
14
+ tailwindcss: "Tailwind CSS", "@xyflow/react": "React Flow",
15
+ commander: "Commander.js", yargs: "Yargs",
16
+ vite: "Vite", webpack: "Webpack", esbuild: "esbuild",
17
+ typescript: "TypeScript",
18
+ kafkajs: "Kafka", amqplib: "RabbitMQ", bullmq: "BullMQ", bull: "Bull",
19
+ stripe: "Stripe", "@stripe/stripe-js": "Stripe",
20
+ playwright: "Playwright", "@playwright/test": "Playwright",
21
+ jest: "Jest", vitest: "Vitest", mocha: "Mocha",
22
+ flutter: "Flutter",
23
+ };
24
+ // Conventional directory → type mapping (hint-based, not exclusive)
25
+ const DIR_TYPE_HINTS = {
26
+ ui: { type: "frontend", label: "UI" },
27
+ web: { type: "frontend", label: "Web App" },
28
+ frontend: { type: "frontend", label: "Frontend" },
29
+ client: { type: "frontend", label: "Client" },
30
+ app: { type: "frontend", label: "App" },
31
+ server: { type: "api", label: "Server" },
32
+ api: { type: "api", label: "API" },
33
+ backend: { type: "api", label: "Backend" },
34
+ gateway: { type: "api", label: "Gateway" },
35
+ cli: { type: "service", label: "CLI" },
36
+ agents: { type: "library", label: "Agents" },
37
+ lib: { type: "library", label: "Library" },
38
+ packages: { type: "library", label: "Packages" },
39
+ shared: { type: "library", label: "Shared" },
40
+ common: { type: "library", label: "Common" },
41
+ cloud: { type: "service", label: "Cloud" },
42
+ infra: { type: "service", label: "Infrastructure" },
43
+ deploy: { type: "service", label: "Deployment" },
44
+ deployment: { type: "service", label: "Deployment" },
45
+ workers: { type: "worker", label: "Workers" },
46
+ jobs: { type: "worker", label: "Jobs" },
47
+ scripts: { type: "service", label: "Scripts" },
48
+ e2e: { type: "service", label: "E2E Tests" },
49
+ tests: { type: "service", label: "Tests" },
50
+ tools: { type: "service", label: "Tools" },
51
+ homepage: { type: "frontend", label: "Homepage" },
52
+ docs: { type: "service", label: "Docs" },
53
+ };
54
+ // Build config files that indicate a directory is a standalone component
55
+ const BUILD_CONFIG_FILES = [
56
+ "package.json",
57
+ "Cargo.toml",
58
+ "go.mod",
59
+ "pyproject.toml",
60
+ "requirements.txt",
61
+ "setup.py",
62
+ "pubspec.yaml",
63
+ "build.gradle",
64
+ "pom.xml",
65
+ "Gemfile",
66
+ "Makefile",
67
+ "Dockerfile",
68
+ "wrangler.toml",
69
+ "tsconfig.json",
70
+ ];
71
+ // Skip these dirs — not components
72
+ const SKIP_DIRS = new Set([
73
+ "node_modules", "dist", "build", "target", ".git",
74
+ "coverage", "tmp", ".cache", "venv", "__pycache__",
75
+ ]);
76
+ export async function detectComponents(tk, structure) {
77
+ // Strategy 1: Monorepo workspaces
78
+ if (structure.isMonorepo) {
79
+ const components = await detectFromWorkspaces(tk, structure);
80
+ if (components.length > 0)
81
+ return { components };
82
+ }
83
+ // Strategy 2: Scan ALL top-level directories for build configs + conventional names
84
+ const components = await detectAllComponents(tk, structure);
85
+ if (components.length > 0)
86
+ return { components };
87
+ // Strategy 3: Single app fallback
88
+ return { components: [buildSingleAppComponent(structure)] };
89
+ }
90
+ async function detectFromWorkspaces(tk, structure) {
91
+ // Collect workspace patterns from package.json OR pnpm-workspace.yaml
92
+ const workspaces = await resolveWorkspacePatterns(tk);
93
+ if (workspaces.length === 0)
94
+ return [];
95
+ const components = [];
96
+ for (const wsPattern of workspaces) {
97
+ // Skip root workspace (`.`)
98
+ if (wsPattern === ".")
99
+ continue;
100
+ // For glob patterns like "extensions/*", find matching package.json files
101
+ const matches = await tk.globFiles(`${wsPattern}/package.json`);
102
+ for (const match of matches) {
103
+ const wsDir = match.replace(/\/package\.json$/, "");
104
+ const wsPkg = await tk.readJSON(match);
105
+ if (!wsPkg)
106
+ continue;
107
+ const name = wsPkg.name ?? wsDir.split("/").pop() ?? wsDir;
108
+ const type = detectTypeFromDeps(wsPkg);
109
+ const techs = extractTechStack(wsPkg);
110
+ components.push({
111
+ id: slugify(name) ?? wsDir,
112
+ name,
113
+ type,
114
+ layer: assignLayer(type),
115
+ path: wsDir,
116
+ description: wsPkg.description ?? "",
117
+ technologies: techs,
118
+ });
119
+ }
120
+ }
121
+ // Also detect non-workspace components (Cargo.toml, Python, native apps, etc.)
122
+ const wsPaths = new Set(components.map((c) => c.path));
123
+ const allComponents = await detectAllComponents(tk, structure);
124
+ for (const c of allComponents) {
125
+ if (!wsPaths.has(c.path)) {
126
+ components.push(c);
127
+ }
128
+ }
129
+ return components;
130
+ }
131
+ /**
132
+ * Resolve workspace patterns from package.json workspaces OR pnpm-workspace.yaml.
133
+ * Deterministic — just reads config files.
134
+ */
135
+ async function resolveWorkspacePatterns(tk) {
136
+ // 1. Try package.json workspaces
137
+ const pkg = await tk.readJSON("package.json");
138
+ if (pkg?.workspaces) {
139
+ return Array.isArray(pkg.workspaces)
140
+ ? pkg.workspaces
141
+ : (pkg.workspaces.packages ?? []);
142
+ }
143
+ // 2. Try pnpm-workspace.yaml
144
+ const pnpmWs = await tk.readYAML("pnpm-workspace.yaml");
145
+ if (pnpmWs?.packages && Array.isArray(pnpmWs.packages)) {
146
+ return pnpmWs.packages;
147
+ }
148
+ // 3. Try lerna.json
149
+ const lerna = await tk.readJSON("lerna.json");
150
+ if (lerna?.packages && Array.isArray(lerna.packages)) {
151
+ return lerna.packages;
152
+ }
153
+ return [];
154
+ }
155
+ /**
156
+ * Scan every top-level directory. If it has a build config, README, Dockerfile,
157
+ * or code files, treat it as a component. Use dir name hints + config file analysis
158
+ * to determine type.
159
+ */
160
+ async function detectAllComponents(tk, structure) {
161
+ const components = [];
162
+ const dirs = Object.keys(structure.directories);
163
+ for (const dir of dirs) {
164
+ if (SKIP_DIRS.has(dir) || dir.startsWith("."))
165
+ continue;
166
+ const entries = await tk.listDir(dir);
167
+ if (entries.length === 0)
168
+ continue;
169
+ const fileNames = entries.map((e) => e.name);
170
+ // Check for build config files
171
+ const hasBuildConfig = BUILD_CONFIG_FILES.some((f) => fileNames.includes(f));
172
+ const hasCode = entries.some((e) => e.type === "file" &&
173
+ /\.(ts|js|tsx|jsx|py|go|rs|java|rb|dart|kt|swift|c|cpp|cs)$/.test(e.name));
174
+ const hasSrcDir = entries.some((e) => e.type === "directory" && (e.name === "src" || e.name === "lib" || e.name === "app"));
175
+ const hasReadme = fileNames.includes("README.md") || fileNames.includes("readme.md");
176
+ // Must have at least some signal that this is a real component
177
+ if (!hasBuildConfig && !hasCode && !hasSrcDir)
178
+ continue;
179
+ // Detect language + type + tech stack from config files
180
+ const detected = await detectFromBuildConfigs(tk, dir, fileNames);
181
+ const hint = DIR_TYPE_HINTS[dir.toLowerCase()];
182
+ const type = detected.type ?? hint?.type ?? "service";
183
+ const label = hint?.label ?? capitalize(dir);
184
+ const name = label;
185
+ // Read description from sub-README if available
186
+ let description = detected.description;
187
+ if (!description && hasReadme) {
188
+ const readme = await tk.readFileSafe(`${dir}/README.md`);
189
+ if (readme) {
190
+ description = extractFirstParagraph(readme);
191
+ }
192
+ }
193
+ components.push({
194
+ id: slugify(dir) ?? dir,
195
+ name,
196
+ type,
197
+ layer: assignLayer(type),
198
+ path: dir,
199
+ description: description ?? "",
200
+ technologies: detected.technologies,
201
+ });
202
+ }
203
+ return components;
204
+ }
205
+ async function detectFromBuildConfigs(tk, dir, fileNames) {
206
+ const info = {
207
+ type: null,
208
+ description: "",
209
+ technologies: [],
210
+ language: null,
211
+ };
212
+ // package.json — richest source of info
213
+ if (fileNames.includes("package.json")) {
214
+ const pkg = await tk.readJSON(`${dir}/package.json`);
215
+ if (pkg) {
216
+ info.description = pkg.description ?? "";
217
+ info.type = detectTypeFromDeps(pkg);
218
+ info.technologies = extractTechStack(pkg);
219
+ info.language = "TypeScript/JavaScript";
220
+ }
221
+ }
222
+ // Cargo.toml — Rust
223
+ if (fileNames.includes("Cargo.toml")) {
224
+ const cargo = await tk.readFileSafe(`${dir}/Cargo.toml`);
225
+ if (cargo) {
226
+ info.language = "Rust";
227
+ info.technologies.push("Rust");
228
+ // Detect type from Cargo deps
229
+ if (cargo.includes("actix") || cargo.includes("axum") || cargo.includes("rocket") || cargo.includes("warp")) {
230
+ info.type = info.type ?? "api";
231
+ if (cargo.includes("axum"))
232
+ info.technologies.push("Axum");
233
+ if (cargo.includes("actix"))
234
+ info.technologies.push("Actix");
235
+ }
236
+ if (cargo.includes("wasm") || cargo.includes("wasm-bindgen")) {
237
+ info.type = info.type ?? "frontend";
238
+ info.technologies.push("WebAssembly");
239
+ }
240
+ if (cargo.includes("clap") || cargo.includes("structopt")) {
241
+ info.type = info.type ?? "service";
242
+ }
243
+ // Extract description
244
+ const descMatch = cargo.match(/description\s*=\s*"([^"]+)"/);
245
+ if (descMatch && !info.description)
246
+ info.description = descMatch[1];
247
+ }
248
+ }
249
+ // wrangler.toml — Cloudflare Workers
250
+ if (fileNames.includes("wrangler.toml")) {
251
+ info.technologies.push("Cloudflare Workers");
252
+ info.type = info.type ?? "service";
253
+ }
254
+ // requirements.txt or pyproject.toml — Python
255
+ if (fileNames.includes("requirements.txt") || fileNames.includes("pyproject.toml")) {
256
+ info.language = "Python";
257
+ info.technologies.push("Python");
258
+ const reqs = await tk.readFileSafe(`${dir}/requirements.txt`);
259
+ if (reqs) {
260
+ if (reqs.includes("django")) {
261
+ info.type = info.type ?? "api";
262
+ info.technologies.push("Django");
263
+ }
264
+ if (reqs.includes("fastapi")) {
265
+ info.type = info.type ?? "api";
266
+ info.technologies.push("FastAPI");
267
+ }
268
+ if (reqs.includes("flask")) {
269
+ info.type = info.type ?? "api";
270
+ info.technologies.push("Flask");
271
+ }
272
+ if (reqs.includes("celery")) {
273
+ info.type = info.type ?? "worker";
274
+ info.technologies.push("Celery");
275
+ }
276
+ if (reqs.includes("streamlit")) {
277
+ info.type = info.type ?? "frontend";
278
+ info.technologies.push("Streamlit");
279
+ }
280
+ }
281
+ }
282
+ // pubspec.yaml — Dart/Flutter
283
+ if (fileNames.includes("pubspec.yaml")) {
284
+ const pubspec = await tk.readYAML(`${dir}/pubspec.yaml`);
285
+ info.language = "Dart";
286
+ info.technologies.push("Flutter");
287
+ info.type = info.type ?? "frontend";
288
+ if (pubspec?.description) {
289
+ info.description = info.description || pubspec.description;
290
+ }
291
+ }
292
+ // go.mod — Go
293
+ if (fileNames.includes("go.mod")) {
294
+ info.language = "Go";
295
+ info.technologies.push("Go");
296
+ const goMod = await tk.readFileSafe(`${dir}/go.mod`);
297
+ if (goMod) {
298
+ if (goMod.includes("gin-gonic")) {
299
+ info.type = info.type ?? "api";
300
+ info.technologies.push("Gin");
301
+ }
302
+ if (goMod.includes("labstack/echo")) {
303
+ info.type = info.type ?? "api";
304
+ info.technologies.push("Echo");
305
+ }
306
+ }
307
+ }
308
+ // Dockerfile — any language, suggests deployable service
309
+ if (fileNames.includes("Dockerfile")) {
310
+ info.technologies.push("Docker");
311
+ }
312
+ // docker-compose.yml — infrastructure component
313
+ if (fileNames.includes("docker-compose.yml") || fileNames.includes("docker-compose.yaml")) {
314
+ info.technologies.push("Docker Compose");
315
+ }
316
+ // K8s manifests
317
+ if (fileNames.some((f) => f.endsWith(".yaml") || f.endsWith(".yml"))) {
318
+ const entries = await tk.listDir(dir);
319
+ const yamlDirs = entries.filter((e) => e.type === "directory").map((e) => e.name);
320
+ if (yamlDirs.some((d) => ["base", "overlays", "local"].includes(d)) ||
321
+ fileNames.includes("kustomization.yaml")) {
322
+ info.type = info.type ?? "service";
323
+ info.technologies.push("Kubernetes");
324
+ }
325
+ }
326
+ // Playwright / test config
327
+ if (fileNames.includes("playwright.config.ts") || fileNames.includes("playwright.config.js")) {
328
+ info.type = info.type ?? "service";
329
+ info.technologies.push("Playwright");
330
+ }
331
+ // Keep top 5 techs
332
+ info.technologies = [...new Set(info.technologies)].slice(0, 5);
333
+ return info;
334
+ }
335
+ function buildSingleAppComponent(structure) {
336
+ return {
337
+ id: slugify(structure.projectName) ?? "app",
338
+ name: structure.projectName || "Application",
339
+ type: structure.framework
340
+ ? (["React", "Vue", "Svelte", "Angular", "Next.js", "Nuxt"].includes(structure.framework)
341
+ ? "frontend"
342
+ : "api")
343
+ : "service",
344
+ layer: "application",
345
+ path: ".",
346
+ description: "",
347
+ technologies: [structure.language, structure.framework].filter(Boolean),
348
+ };
349
+ }
350
+ function detectTypeFromDeps(pkg) {
351
+ const deps = Object.keys({
352
+ ...pkg.dependencies,
353
+ ...pkg.devDependencies,
354
+ });
355
+ // Frontend indicators
356
+ if (deps.some((d) => ["react", "react-dom", "vue", "svelte", "@angular/core", "next", "nuxt"].includes(d))) {
357
+ return "frontend";
358
+ }
359
+ // API indicators
360
+ if (deps.some((d) => ["express", "fastify", "@nestjs/core", "hono", "koa"].includes(d))) {
361
+ return "api";
362
+ }
363
+ // Worker indicators
364
+ if (deps.some((d) => ["bullmq", "bull", "bee-queue"].includes(d))) {
365
+ return "worker";
366
+ }
367
+ // Test indicators
368
+ if (deps.some((d) => ["@playwright/test", "playwright", "cypress"].includes(d))) {
369
+ return "service";
370
+ }
371
+ return "service";
372
+ }
373
+ function extractTechStack(pkg) {
374
+ const deps = Object.keys({
375
+ ...pkg.dependencies,
376
+ ...pkg.devDependencies,
377
+ });
378
+ const techs = [];
379
+ for (const dep of deps) {
380
+ if (TECH_MAP[dep] && !techs.includes(TECH_MAP[dep])) {
381
+ techs.push(TECH_MAP[dep]);
382
+ }
383
+ }
384
+ return techs.slice(0, 5);
385
+ }
386
+ function capitalize(s) {
387
+ return s.charAt(0).toUpperCase() + s.slice(1);
388
+ }
389
+ function extractFirstParagraph(readme) {
390
+ const lines = readme.split("\n");
391
+ let capturing = false;
392
+ const descLines = [];
393
+ for (const line of lines) {
394
+ if (!capturing && /^#\s+/.test(line)) {
395
+ capturing = true;
396
+ continue;
397
+ }
398
+ if (capturing && /^##\s+/.test(line))
399
+ break;
400
+ if (capturing)
401
+ descLines.push(line);
402
+ }
403
+ return descLines.join("\n").trim().replace(/\n{3,}/g, "\n\n").slice(0, 300);
404
+ }
@@ -0,0 +1,3 @@
1
+ import type { ConnectionResult, StaticComponent, InfraResult, EventResult } from "./types.js";
2
+ import type { StaticToolkit } from "./utils.js";
3
+ export declare function mapConnections(tk: StaticToolkit, components: StaticComponent[], infra: InfraResult, events: EventResult): Promise<ConnectionResult>;
@@ -0,0 +1,280 @@
1
+ // Static Analysis — Connection Mapper
2
+ // Maps connections between components via imports, Docker, K8s, env vars, known SDKs
3
+ // Only deterministic matches — ambiguous mappings are left as gaps for the LLM
4
+ import { slugify } from "./utils.js";
5
+ export async function mapConnections(tk, components, infra, events) {
6
+ const connections = [];
7
+ const componentIds = new Set(components.map((c) => c.id));
8
+ // Run all detection methods in parallel
9
+ await Promise.all([
10
+ detectDockerDependencies(infra, components, connections),
11
+ detectK8sIngress(infra, components, connections),
12
+ detectImportConnections(tk, components, connections),
13
+ detectDatabaseConnections(tk, components, connections),
14
+ detectServerServesUI(tk, components, connections),
15
+ detectKnownSDKConnections(components, connections),
16
+ ]);
17
+ // Deduplicate and filter
18
+ const seen = new Set();
19
+ const filtered = connections.filter((c) => {
20
+ // Filter self-connections
21
+ if (c.from === c.to)
22
+ return false;
23
+ // Filter connections to/from unknown components
24
+ if (!componentIds.has(c.from) && !componentIds.has(c.to))
25
+ return false;
26
+ // Dedup
27
+ const key = `${c.from}::${c.to}::${c.type}`;
28
+ if (seen.has(key))
29
+ return false;
30
+ seen.add(key);
31
+ return true;
32
+ });
33
+ return { connections: filtered, flows: [] };
34
+ }
35
+ async function detectDockerDependencies(infra, components, connections) {
36
+ if (!infra.docker.composeFile)
37
+ return;
38
+ // Build a map from docker service names to component IDs
39
+ // Only uses deterministic matching: build context path resolution + exact name match
40
+ const dockerToComponent = new Map();
41
+ const composeDir = infra.docker.composeFilePath ?? "";
42
+ for (const svc of infra.docker.services) {
43
+ let id = null;
44
+ // 1. Try build context resolved relative to compose file directory (deterministic)
45
+ if (svc.buildContext) {
46
+ const resolvedPath = resolveBuildContext(composeDir, svc.buildContext);
47
+ if (resolvedPath) {
48
+ id = findComponentByPath(resolvedPath, components);
49
+ }
50
+ }
51
+ // 2. Exact match on service name → component id/path/name
52
+ if (!id) {
53
+ id = findComponentExact(svc.name, components);
54
+ }
55
+ if (id)
56
+ dockerToComponent.set(svc.name, id);
57
+ // If no match found → gap collector will flag this as "unresolved_docker_service"
58
+ }
59
+ for (const svc of infra.docker.services) {
60
+ const fromId = dockerToComponent.get(svc.name);
61
+ if (!fromId)
62
+ continue;
63
+ // depends_on connections
64
+ for (const dep of svc.dependsOn ?? []) {
65
+ const toId = dockerToComponent.get(dep);
66
+ if (toId && fromId !== toId) {
67
+ const depSvc = infra.docker.services.find((s) => s.name === dep);
68
+ const type = inferDockerConnectionType(depSvc?.image);
69
+ connections.push({
70
+ from: fromId,
71
+ to: toId,
72
+ type,
73
+ description: `Docker depends_on: ${svc.name} → ${dep}`,
74
+ confidence: 100,
75
+ async: false,
76
+ });
77
+ }
78
+ }
79
+ // Environment variable connections (e.g. SERVICE_BASE_URL: http://other-service:8000)
80
+ if (svc.environment) {
81
+ for (const [, value] of Object.entries(svc.environment)) {
82
+ if (!value)
83
+ continue;
84
+ const urlMatch = value.match(/https?:\/\/([a-z0-9_-]+):\d+/i);
85
+ if (urlMatch) {
86
+ const targetName = urlMatch[1];
87
+ const toId = dockerToComponent.get(targetName) ?? findComponentExact(targetName, components);
88
+ if (toId && fromId !== toId) {
89
+ connections.push({
90
+ from: fromId,
91
+ to: toId,
92
+ type: "http",
93
+ description: `Docker env: ${svc.name} → ${targetName} (${value})`,
94
+ confidence: 100,
95
+ async: false,
96
+ });
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }
103
+ async function detectK8sIngress(infra, components, connections) {
104
+ const ingresses = infra.kubernetes.resources.filter((r) => r.kind === "Ingress");
105
+ const services = infra.kubernetes.resources.filter((r) => r.kind === "Service");
106
+ for (const ingress of ingresses) {
107
+ for (const svc of services) {
108
+ const toId = findComponentExact(svc.name, components);
109
+ if (toId) {
110
+ connections.push({
111
+ from: ingress.name,
112
+ to: toId,
113
+ type: "http",
114
+ description: `K8s ingress → ${svc.name}`,
115
+ confidence: 100,
116
+ async: false,
117
+ });
118
+ }
119
+ }
120
+ }
121
+ }
122
+ async function detectImportConnections(tk, components, connections) {
123
+ for (const source of components) {
124
+ if (source.path === ".")
125
+ continue;
126
+ const connectedTargets = new Set();
127
+ for (const target of components) {
128
+ if (target.id === source.id)
129
+ continue;
130
+ if (target.path === ".")
131
+ continue;
132
+ const results = await tk.grepFiles(`from .*\\.\\./${target.path}/`, source.path);
133
+ if (results.length > 0 && !connectedTargets.has(target.id)) {
134
+ connectedTargets.add(target.id);
135
+ connections.push({
136
+ from: source.id,
137
+ to: target.id,
138
+ type: "import",
139
+ description: `Import: ${source.name} → ${target.name}`,
140
+ confidence: 90,
141
+ async: false,
142
+ });
143
+ }
144
+ }
145
+ }
146
+ }
147
+ async function detectDatabaseConnections(tk, components, connections) {
148
+ const dbComp = components.find((c) => c.type === "database" || c.layer === "data");
149
+ for (const comp of components) {
150
+ if (comp.path === ".")
151
+ continue;
152
+ if (comp.id === dbComp?.id)
153
+ continue;
154
+ const dbResults = await tk.grepFiles("DATABASE_URL|DB_HOST|MONGO_URI|REDIS_URL", comp.path);
155
+ if (dbResults.length > 0 && dbComp) {
156
+ connections.push({
157
+ from: comp.id,
158
+ to: dbComp.id,
159
+ type: "database",
160
+ description: `Database connection from ${comp.name}`,
161
+ confidence: 95,
162
+ async: false,
163
+ });
164
+ }
165
+ }
166
+ }
167
+ async function detectServerServesUI(tk, components, connections) {
168
+ const serverComp = components.find((c) => c.type === "api");
169
+ const uiComp = components.find((c) => c.type === "frontend");
170
+ if (!serverComp || !uiComp)
171
+ return;
172
+ const staticResults = await tk.grepFiles("ui/dist|express\\.static|serveStatic|sendFile", serverComp.path);
173
+ if (staticResults.length > 0) {
174
+ connections.push({
175
+ from: serverComp.id,
176
+ to: uiComp.id,
177
+ type: "http",
178
+ description: `${serverComp.name} serves ${uiComp.name} static files`,
179
+ confidence: 85,
180
+ async: false,
181
+ });
182
+ }
183
+ const apiResults = await tk.grepFiles("fetch\\(|axios|useQuery|EventSource", uiComp.path);
184
+ if (apiResults.length > 0) {
185
+ connections.push({
186
+ from: uiComp.id,
187
+ to: serverComp.id,
188
+ type: "http",
189
+ description: `${uiComp.name} calls ${serverComp.name} API`,
190
+ confidence: 80,
191
+ async: false,
192
+ });
193
+ }
194
+ }
195
+ async function detectKnownSDKConnections(components, connections) {
196
+ const externalTechs = {
197
+ Stripe: "stripe-api",
198
+ Firebase: "firebase",
199
+ Supabase: "supabase",
200
+ Redis: "redis",
201
+ Kafka: "kafka",
202
+ RabbitMQ: "rabbitmq",
203
+ "Socket.IO": "websocket",
204
+ PostgreSQL: "database",
205
+ MySQL: "database",
206
+ MongoDB: "database",
207
+ };
208
+ for (const comp of components) {
209
+ for (const tech of comp.technologies) {
210
+ const externalId = externalTechs[tech];
211
+ if (externalId) {
212
+ const target = components.find((c) => c.id !== comp.id && (c.technologies.includes(tech) ||
213
+ c.type === "database" && (externalId === "database")));
214
+ if (target) {
215
+ connections.push({
216
+ from: comp.id,
217
+ to: target.id,
218
+ type: externalId === "database" ? "database" : "http",
219
+ description: `${comp.name} uses ${tech}`,
220
+ confidence: 95,
221
+ async: false,
222
+ });
223
+ }
224
+ }
225
+ }
226
+ }
227
+ }
228
+ /**
229
+ * Exact match only: id, slug, path, or name must match exactly.
230
+ * No fuzzy matching, synonyms, or partial matches — those are for the LLM.
231
+ */
232
+ function findComponentExact(name, components) {
233
+ const lower = name.toLowerCase();
234
+ const slug = slugify(name);
235
+ const match = components.find((c) => c.id === slug ||
236
+ c.id === lower ||
237
+ c.path.toLowerCase() === lower ||
238
+ c.name.toLowerCase() === lower);
239
+ return match?.id ?? null;
240
+ }
241
+ /**
242
+ * Resolve a docker build context path relative to the compose file directory.
243
+ * Pure path arithmetic — deterministic.
244
+ */
245
+ function resolveBuildContext(composeDir, context) {
246
+ const ctx = context.replace(/\/$/, "");
247
+ if (ctx === "." || ctx === "./") {
248
+ return composeDir || null;
249
+ }
250
+ if (ctx.startsWith("../")) {
251
+ const remaining = ctx.replace(/^\.\.\//, "");
252
+ return remaining || null;
253
+ }
254
+ if (ctx.startsWith("./")) {
255
+ const remaining = ctx.replace(/^\.\//, "");
256
+ return composeDir ? `${composeDir}/${remaining}` : remaining;
257
+ }
258
+ return composeDir ? `${composeDir}/${ctx}` : ctx;
259
+ }
260
+ /**
261
+ * Find a component by its directory path (exact match).
262
+ */
263
+ function findComponentByPath(dirPath, components) {
264
+ const lower = dirPath.toLowerCase();
265
+ const comp = components.find((c) => c.path.toLowerCase() === lower);
266
+ return comp?.id ?? null;
267
+ }
268
+ function inferDockerConnectionType(image) {
269
+ if (!image)
270
+ return "docker";
271
+ const img = image.toLowerCase();
272
+ if (img.includes("postgres") || img.includes("mysql") || img.includes("mongo") || img.includes("mariadb")) {
273
+ return "database";
274
+ }
275
+ if (img.includes("redis") || img.includes("memcached"))
276
+ return "cache";
277
+ if (img.includes("rabbit") || img.includes("kafka") || img.includes("nats"))
278
+ return "queue";
279
+ return "docker";
280
+ }
@@ -0,0 +1,3 @@
1
+ import type { DocResult } from "./types.js";
2
+ import type { StaticToolkit } from "./utils.js";
3
+ export declare function parseDocs(tk: StaticToolkit): Promise<DocResult>;