archbyte 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +282 -0
- package/bin/archbyte.js +213 -0
- package/dist/agents/core/component-detector.d.ts +2 -0
- package/dist/agents/core/component-detector.js +57 -0
- package/dist/agents/core/connection-mapper.d.ts +2 -0
- package/dist/agents/core/connection-mapper.js +77 -0
- package/dist/agents/core/doc-parser.d.ts +2 -0
- package/dist/agents/core/doc-parser.js +64 -0
- package/dist/agents/core/env-detector.d.ts +2 -0
- package/dist/agents/core/env-detector.js +51 -0
- package/dist/agents/core/event-detector.d.ts +2 -0
- package/dist/agents/core/event-detector.js +59 -0
- package/dist/agents/core/infra-analyzer.d.ts +2 -0
- package/dist/agents/core/infra-analyzer.js +72 -0
- package/dist/agents/core/structure-scanner.d.ts +2 -0
- package/dist/agents/core/structure-scanner.js +55 -0
- package/dist/agents/core/validator.d.ts +2 -0
- package/dist/agents/core/validator.js +74 -0
- package/dist/agents/index.d.ts +24 -0
- package/dist/agents/index.js +73 -0
- package/dist/agents/llm/index.d.ts +8 -0
- package/dist/agents/llm/index.js +185 -0
- package/dist/agents/llm/prompt-builder.d.ts +3 -0
- package/dist/agents/llm/prompt-builder.js +251 -0
- package/dist/agents/llm/response-parser.d.ts +6 -0
- package/dist/agents/llm/response-parser.js +174 -0
- package/dist/agents/llm/types.d.ts +31 -0
- package/dist/agents/llm/types.js +2 -0
- package/dist/agents/pipeline/agents/component-identifier.d.ts +3 -0
- package/dist/agents/pipeline/agents/component-identifier.js +102 -0
- package/dist/agents/pipeline/agents/connection-mapper.d.ts +3 -0
- package/dist/agents/pipeline/agents/connection-mapper.js +126 -0
- package/dist/agents/pipeline/agents/flow-detector.d.ts +3 -0
- package/dist/agents/pipeline/agents/flow-detector.js +101 -0
- package/dist/agents/pipeline/agents/service-describer.d.ts +3 -0
- package/dist/agents/pipeline/agents/service-describer.js +100 -0
- package/dist/agents/pipeline/agents/validator.d.ts +3 -0
- package/dist/agents/pipeline/agents/validator.js +102 -0
- package/dist/agents/pipeline/index.d.ts +13 -0
- package/dist/agents/pipeline/index.js +128 -0
- package/dist/agents/pipeline/merger.d.ts +7 -0
- package/dist/agents/pipeline/merger.js +212 -0
- package/dist/agents/pipeline/response-parser.d.ts +5 -0
- package/dist/agents/pipeline/response-parser.js +43 -0
- package/dist/agents/pipeline/types.d.ts +92 -0
- package/dist/agents/pipeline/types.js +3 -0
- package/dist/agents/prompt-data.d.ts +1 -0
- package/dist/agents/prompt-data.js +15 -0
- package/dist/agents/prompts-encode.d.ts +9 -0
- package/dist/agents/prompts-encode.js +26 -0
- package/dist/agents/prompts.d.ts +12 -0
- package/dist/agents/prompts.js +30 -0
- package/dist/agents/providers/anthropic.d.ts +10 -0
- package/dist/agents/providers/anthropic.js +117 -0
- package/dist/agents/providers/google.d.ts +10 -0
- package/dist/agents/providers/google.js +136 -0
- package/dist/agents/providers/ollama.d.ts +9 -0
- package/dist/agents/providers/ollama.js +162 -0
- package/dist/agents/providers/openai.d.ts +9 -0
- package/dist/agents/providers/openai.js +142 -0
- package/dist/agents/providers/router.d.ts +7 -0
- package/dist/agents/providers/router.js +55 -0
- package/dist/agents/runtime/orchestrator.d.ts +34 -0
- package/dist/agents/runtime/orchestrator.js +193 -0
- package/dist/agents/runtime/registry.d.ts +23 -0
- package/dist/agents/runtime/registry.js +56 -0
- package/dist/agents/runtime/types.d.ts +117 -0
- package/dist/agents/runtime/types.js +29 -0
- package/dist/agents/static/code-sampler.d.ts +3 -0
- package/dist/agents/static/code-sampler.js +153 -0
- package/dist/agents/static/component-detector.d.ts +3 -0
- package/dist/agents/static/component-detector.js +404 -0
- package/dist/agents/static/connection-mapper.d.ts +3 -0
- package/dist/agents/static/connection-mapper.js +280 -0
- package/dist/agents/static/doc-parser.d.ts +3 -0
- package/dist/agents/static/doc-parser.js +358 -0
- package/dist/agents/static/env-detector.d.ts +3 -0
- package/dist/agents/static/env-detector.js +73 -0
- package/dist/agents/static/event-detector.d.ts +3 -0
- package/dist/agents/static/event-detector.js +70 -0
- package/dist/agents/static/file-tree-collector.d.ts +3 -0
- package/dist/agents/static/file-tree-collector.js +51 -0
- package/dist/agents/static/index.d.ts +19 -0
- package/dist/agents/static/index.js +307 -0
- package/dist/agents/static/infra-analyzer.d.ts +3 -0
- package/dist/agents/static/infra-analyzer.js +208 -0
- package/dist/agents/static/structure-scanner.d.ts +3 -0
- package/dist/agents/static/structure-scanner.js +195 -0
- package/dist/agents/static/types.d.ts +165 -0
- package/dist/agents/static/types.js +2 -0
- package/dist/agents/static/utils.d.ts +21 -0
- package/dist/agents/static/utils.js +146 -0
- package/dist/agents/static/validator.d.ts +2 -0
- package/dist/agents/static/validator.js +75 -0
- package/dist/agents/tools/claude-code.d.ts +38 -0
- package/dist/agents/tools/claude-code.js +129 -0
- package/dist/agents/tools/local-fs.d.ts +12 -0
- package/dist/agents/tools/local-fs.js +112 -0
- package/dist/agents/tools/tool-definitions.d.ts +6 -0
- package/dist/agents/tools/tool-definitions.js +66 -0
- package/dist/cli/analyze.d.ts +27 -0
- package/dist/cli/analyze.js +586 -0
- package/dist/cli/auth.d.ts +46 -0
- package/dist/cli/auth.js +397 -0
- package/dist/cli/config.d.ts +11 -0
- package/dist/cli/config.js +177 -0
- package/dist/cli/diff.d.ts +10 -0
- package/dist/cli/diff.js +144 -0
- package/dist/cli/export.d.ts +10 -0
- package/dist/cli/export.js +321 -0
- package/dist/cli/gate.d.ts +13 -0
- package/dist/cli/gate.js +131 -0
- package/dist/cli/generate.d.ts +10 -0
- package/dist/cli/generate.js +213 -0
- package/dist/cli/license-gate.d.ts +27 -0
- package/dist/cli/license-gate.js +121 -0
- package/dist/cli/patrol.d.ts +15 -0
- package/dist/cli/patrol.js +212 -0
- package/dist/cli/run.d.ts +11 -0
- package/dist/cli/run.js +24 -0
- package/dist/cli/serve.d.ts +9 -0
- package/dist/cli/serve.js +65 -0
- package/dist/cli/setup.d.ts +1 -0
- package/dist/cli/setup.js +233 -0
- package/dist/cli/shared.d.ts +68 -0
- package/dist/cli/shared.js +275 -0
- package/dist/cli/stats.d.ts +9 -0
- package/dist/cli/stats.js +158 -0
- package/dist/cli/ui.d.ts +18 -0
- package/dist/cli/ui.js +144 -0
- package/dist/cli/validate.d.ts +54 -0
- package/dist/cli/validate.js +315 -0
- package/dist/cli/workflow.d.ts +10 -0
- package/dist/cli/workflow.js +594 -0
- package/dist/server/src/generator/index.d.ts +123 -0
- package/dist/server/src/generator/index.js +254 -0
- package/dist/server/src/index.d.ts +8 -0
- package/dist/server/src/index.js +1311 -0
- package/package.json +62 -0
- package/ui/dist/assets/index-B66Til39.js +70 -0
- package/ui/dist/assets/index-BE2OWbzu.css +1 -0
- package/ui/dist/index.html +14 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
// Static Analysis — Doc Parser
|
|
2
|
+
// Deep-scans README, CLAUDE.md, architecture docs, API docs, mermaid diagrams
|
|
3
|
+
// to extract project description, components, connections, endpoints, and external deps
|
|
4
|
+
const KNOWN_SERVICES = [
|
|
5
|
+
{ pattern: /aws\s*s3|amazon\s*s3|@aws-sdk\/client-s3/i, name: "AWS S3" },
|
|
6
|
+
{ pattern: /dynamodb|@aws-sdk\/client-dynamodb/i, name: "DynamoDB" },
|
|
7
|
+
{ pattern: /aws\s*lambda|@aws-sdk\/client-lambda/i, name: "AWS Lambda" },
|
|
8
|
+
{ pattern: /sqs|@aws-sdk\/client-sqs/i, name: "AWS SQS" },
|
|
9
|
+
{ pattern: /sns|@aws-sdk\/client-sns/i, name: "AWS SNS" },
|
|
10
|
+
{ pattern: /cloudflare\s*workers|wrangler/i, name: "Cloudflare Workers" },
|
|
11
|
+
{ pattern: /redis|ioredis|@upstash\/redis/i, name: "Redis" },
|
|
12
|
+
{ pattern: /postgres(?:ql)?|pg\b|@prisma|prisma|tokio-postgres|neon/i, name: "PostgreSQL" },
|
|
13
|
+
{ pattern: /mysql|mariadb/i, name: "MySQL" },
|
|
14
|
+
{ pattern: /mongodb|mongoose/i, name: "MongoDB" },
|
|
15
|
+
{ pattern: /firebase|firestore/i, name: "Firebase" },
|
|
16
|
+
{ pattern: /supabase/i, name: "Supabase" },
|
|
17
|
+
{ pattern: /stripe/i, name: "Stripe" },
|
|
18
|
+
{ pattern: /twilio/i, name: "Twilio" },
|
|
19
|
+
{ pattern: /sendgrid|@sendgrid/i, name: "SendGrid" },
|
|
20
|
+
{ pattern: /elasticsearch|opensearch/i, name: "Elasticsearch" },
|
|
21
|
+
{ pattern: /rabbitmq|amqplib/i, name: "RabbitMQ" },
|
|
22
|
+
{ pattern: /kafka|kafkajs/i, name: "Kafka" },
|
|
23
|
+
{ pattern: /nats\.io|nats/i, name: "NATS" },
|
|
24
|
+
{ pattern: /auth0/i, name: "Auth0" },
|
|
25
|
+
{ pattern: /clerk/i, name: "Clerk" },
|
|
26
|
+
{ pattern: /sentry/i, name: "Sentry" },
|
|
27
|
+
{ pattern: /datadog/i, name: "Datadog" },
|
|
28
|
+
{ pattern: /vercel/i, name: "Vercel" },
|
|
29
|
+
{ pattern: /netlify/i, name: "Netlify" },
|
|
30
|
+
{ pattern: /openai|gpt-4|gpt-3/i, name: "OpenAI" },
|
|
31
|
+
{ pattern: /anthropic|claude/i, name: "Anthropic" },
|
|
32
|
+
{ pattern: /mapbox/i, name: "Mapbox" },
|
|
33
|
+
{ pattern: /google\s*places|google\s*maps/i, name: "Google Maps/Places" },
|
|
34
|
+
{ pattern: /nginx/i, name: "NGINX" },
|
|
35
|
+
];
|
|
36
|
+
export async function parseDocs(tk) {
|
|
37
|
+
const result = {
|
|
38
|
+
projectDescription: "",
|
|
39
|
+
architectureNotes: [],
|
|
40
|
+
apiEndpoints: [],
|
|
41
|
+
externalDependencies: [],
|
|
42
|
+
};
|
|
43
|
+
// Discover all markdown files worth reading
|
|
44
|
+
const docFiles = await discoverDocFiles(tk);
|
|
45
|
+
// Read root README first for project description
|
|
46
|
+
const readme = await tk.readFileSafe("README.md");
|
|
47
|
+
if (readme) {
|
|
48
|
+
result.projectDescription = extractDescription(readme);
|
|
49
|
+
scanForExternalDeps(readme, result);
|
|
50
|
+
}
|
|
51
|
+
// Read all discovered doc files in parallel (batch of 15)
|
|
52
|
+
const contents = await Promise.all(docFiles.slice(0, 15).map(async (f) => ({
|
|
53
|
+
path: f,
|
|
54
|
+
content: await tk.readFileSafe(f),
|
|
55
|
+
})));
|
|
56
|
+
for (const { path, content } of contents) {
|
|
57
|
+
if (!content)
|
|
58
|
+
continue;
|
|
59
|
+
// Extract architecture notes from architecture/design docs
|
|
60
|
+
if (isArchitectureDoc(path)) {
|
|
61
|
+
const notes = extractArchitectureNotes(path, content);
|
|
62
|
+
result.architectureNotes.push(...notes);
|
|
63
|
+
}
|
|
64
|
+
// Extract API endpoints from docs
|
|
65
|
+
const endpoints = extractAPIEndpoints(content);
|
|
66
|
+
result.apiEndpoints.push(...endpoints);
|
|
67
|
+
// Scan all docs for external service mentions
|
|
68
|
+
scanForExternalDeps(content, result);
|
|
69
|
+
}
|
|
70
|
+
// Parse OpenAPI spec if available
|
|
71
|
+
const [openApiYaml, openApiJson] = await Promise.all([
|
|
72
|
+
tk.readYAML("openapi.yaml"),
|
|
73
|
+
tk.readJSON("openapi.json"),
|
|
74
|
+
]);
|
|
75
|
+
parseOpenAPI(openApiYaml ?? openApiJson, result);
|
|
76
|
+
// Also scan package.json/Cargo.toml deps for known services
|
|
77
|
+
await scanBuildDepsForServices(tk, result);
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Find all documentation files worth reading — READMEs, CLAUDE.md, docs/, architecture docs
|
|
82
|
+
*/
|
|
83
|
+
async function discoverDocFiles(tk) {
|
|
84
|
+
const files = [];
|
|
85
|
+
// Root-level docs
|
|
86
|
+
const rootEntries = await tk.listDir(".");
|
|
87
|
+
const rootDirs = rootEntries.filter((e) => e.type === "directory").map((e) => e.name);
|
|
88
|
+
// Root markdown files
|
|
89
|
+
for (const entry of rootEntries) {
|
|
90
|
+
if (entry.type === "file" && /\.(md|markdown)$/i.test(entry.name)) {
|
|
91
|
+
files.push(entry.name);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// docs/ directory (central documentation hub)
|
|
95
|
+
if (rootDirs.includes("docs")) {
|
|
96
|
+
const docEntries = await tk.listDir("docs");
|
|
97
|
+
for (const entry of docEntries) {
|
|
98
|
+
if (entry.type === "file" && /\.(md|markdown)$/i.test(entry.name)) {
|
|
99
|
+
files.push(`docs/${entry.name}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Scan each top-level directory for CLAUDE.md, README.md, and docs/ subdirs
|
|
104
|
+
for (const dir of rootDirs) {
|
|
105
|
+
if (dir.startsWith(".") || dir === "node_modules" || dir === "dist" || dir === "target")
|
|
106
|
+
continue;
|
|
107
|
+
const entries = await tk.listDir(dir);
|
|
108
|
+
const fileNames = entries.map((e) => e.name);
|
|
109
|
+
// CLAUDE.md — often has the best architecture descriptions
|
|
110
|
+
if (fileNames.includes("CLAUDE.md")) {
|
|
111
|
+
files.push(`${dir}/CLAUDE.md`);
|
|
112
|
+
}
|
|
113
|
+
// Sub-README
|
|
114
|
+
if (fileNames.includes("README.md")) {
|
|
115
|
+
files.push(`${dir}/README.md`);
|
|
116
|
+
}
|
|
117
|
+
// Component docs/ subdirectory (e.g., server/docs/architecture.md)
|
|
118
|
+
if (fileNames.includes("docs") && entries.find((e) => e.name === "docs")?.type === "directory") {
|
|
119
|
+
const subDocs = await tk.listDir(`${dir}/docs`);
|
|
120
|
+
for (const entry of subDocs) {
|
|
121
|
+
if (entry.type === "file" && /\.(md|markdown)$/i.test(entry.name)) {
|
|
122
|
+
files.push(`${dir}/docs/${entry.name}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Deduplicate
|
|
128
|
+
return [...new Set(files)];
|
|
129
|
+
}
|
|
130
|
+
function isArchitectureDoc(path) {
|
|
131
|
+
const lower = path.toLowerCase();
|
|
132
|
+
return (lower.includes("architect") ||
|
|
133
|
+
lower.includes("design") ||
|
|
134
|
+
lower.includes("overview") ||
|
|
135
|
+
lower.includes("claude.md") ||
|
|
136
|
+
lower.includes("system") ||
|
|
137
|
+
lower.includes("api.md") ||
|
|
138
|
+
lower.includes("readme.md"));
|
|
139
|
+
}
|
|
140
|
+
function extractDescription(readme) {
|
|
141
|
+
const lines = readme.split("\n").slice(0, 500);
|
|
142
|
+
let capturing = false;
|
|
143
|
+
const descLines = [];
|
|
144
|
+
for (const line of lines) {
|
|
145
|
+
if (!capturing && /^#\s+/.test(line)) {
|
|
146
|
+
capturing = true;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (capturing && /^##\s+/.test(line))
|
|
150
|
+
break;
|
|
151
|
+
if (capturing)
|
|
152
|
+
descLines.push(line);
|
|
153
|
+
}
|
|
154
|
+
return descLines
|
|
155
|
+
.join("\n")
|
|
156
|
+
.trim()
|
|
157
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
158
|
+
.slice(0, 500);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Extract architecture notes from a documentation file.
|
|
162
|
+
* Looks for: technology tables, ASCII diagrams, component descriptions,
|
|
163
|
+
* section headers mentioning architecture/components/services.
|
|
164
|
+
*/
|
|
165
|
+
function extractArchitectureNotes(path, content) {
|
|
166
|
+
const notes = [];
|
|
167
|
+
const lines = content.split("\n");
|
|
168
|
+
// Extract technology stack tables (markdown tables with | Component | Technology |)
|
|
169
|
+
const tableBlocks = extractMarkdownTables(content);
|
|
170
|
+
if (tableBlocks.length > 0) {
|
|
171
|
+
notes.push(`[${path}] Tech stack: ${tableBlocks[0].slice(0, 400)}`);
|
|
172
|
+
}
|
|
173
|
+
// Extract ASCII architecture diagrams (blocks between ``` that contain box-drawing chars or arrows)
|
|
174
|
+
const codeBlocks = extractCodeBlocks(content);
|
|
175
|
+
for (const block of codeBlocks) {
|
|
176
|
+
if (isAsciiDiagram(block)) {
|
|
177
|
+
notes.push(`[${path}] Architecture diagram: ${block.slice(0, 500)}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Extract key sections: Architecture, Components, Services, Overview, Stack
|
|
181
|
+
const sections = extractSections(content, [
|
|
182
|
+
"architecture", "components", "services", "overview",
|
|
183
|
+
"technology stack", "tech stack", "system design",
|
|
184
|
+
"quick overview", "core components", "key directories",
|
|
185
|
+
]);
|
|
186
|
+
for (const section of sections.slice(0, 3)) {
|
|
187
|
+
notes.push(`[${path}] ${section.title}: ${section.content.slice(0, 400)}`);
|
|
188
|
+
}
|
|
189
|
+
// Extract service connection descriptions (patterns like "X calls Y", "X → Y")
|
|
190
|
+
const connectionDescs = extractConnectionDescriptions(content);
|
|
191
|
+
if (connectionDescs.length > 0) {
|
|
192
|
+
notes.push(`[${path}] Connections: ${connectionDescs.join("; ").slice(0, 400)}`);
|
|
193
|
+
}
|
|
194
|
+
return notes;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Extract API endpoints documented in markdown.
|
|
198
|
+
* Looks for: GET /path, POST /path patterns, markdown tables with endpoints
|
|
199
|
+
*/
|
|
200
|
+
function extractAPIEndpoints(content) {
|
|
201
|
+
const endpoints = [];
|
|
202
|
+
const seen = new Set();
|
|
203
|
+
// Pattern 1: HTTP method + path (e.g., "GET /api/health", "POST /api/login")
|
|
204
|
+
const methodPathRegex = /\b(GET|POST|PUT|PATCH|DELETE)\s+(\/[a-zA-Z0-9/_\-{}:.*]+)/g;
|
|
205
|
+
let match;
|
|
206
|
+
while ((match = methodPathRegex.exec(content)) !== null) {
|
|
207
|
+
const key = `${match[1]}:${match[2]}`;
|
|
208
|
+
if (seen.has(key))
|
|
209
|
+
continue;
|
|
210
|
+
seen.add(key);
|
|
211
|
+
// Try to find description on the same or next line
|
|
212
|
+
const lineStart = content.lastIndexOf("\n", match.index) + 1;
|
|
213
|
+
const lineEnd = content.indexOf("\n", match.index + match[0].length);
|
|
214
|
+
const line = content.slice(lineStart, lineEnd > 0 ? lineEnd : undefined).trim();
|
|
215
|
+
// Strip the method+path to get remaining description
|
|
216
|
+
const desc = line
|
|
217
|
+
.replace(match[0], "")
|
|
218
|
+
.replace(/^[\s|*-]+/, "")
|
|
219
|
+
.replace(/[|`]+/g, "")
|
|
220
|
+
.trim()
|
|
221
|
+
.slice(0, 100);
|
|
222
|
+
endpoints.push({
|
|
223
|
+
method: match[1],
|
|
224
|
+
path: match[2],
|
|
225
|
+
description: desc,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return endpoints;
|
|
229
|
+
}
|
|
230
|
+
function extractMarkdownTables(content) {
|
|
231
|
+
const tables = [];
|
|
232
|
+
const lines = content.split("\n");
|
|
233
|
+
let inTable = false;
|
|
234
|
+
let tableLines = [];
|
|
235
|
+
for (const line of lines) {
|
|
236
|
+
if (line.includes("|") && line.trim().startsWith("|")) {
|
|
237
|
+
inTable = true;
|
|
238
|
+
tableLines.push(line);
|
|
239
|
+
}
|
|
240
|
+
else if (inTable) {
|
|
241
|
+
if (tableLines.length >= 2) {
|
|
242
|
+
tables.push(tableLines.join("\n"));
|
|
243
|
+
}
|
|
244
|
+
inTable = false;
|
|
245
|
+
tableLines = [];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (inTable && tableLines.length >= 2) {
|
|
249
|
+
tables.push(tableLines.join("\n"));
|
|
250
|
+
}
|
|
251
|
+
return tables;
|
|
252
|
+
}
|
|
253
|
+
function extractCodeBlocks(content) {
|
|
254
|
+
const blocks = [];
|
|
255
|
+
const regex = /```[^\n]*\n([\s\S]*?)```/g;
|
|
256
|
+
let match;
|
|
257
|
+
while ((match = regex.exec(content)) !== null) {
|
|
258
|
+
blocks.push(match[1]);
|
|
259
|
+
}
|
|
260
|
+
return blocks;
|
|
261
|
+
}
|
|
262
|
+
function isAsciiDiagram(block) {
|
|
263
|
+
// Contains box-drawing characters, arrows, or flow indicators
|
|
264
|
+
return (/[┌┐└┘│─├┤┬┴┼╔╗╚╝║═]/.test(block) ||
|
|
265
|
+
(/[→←↓↑▶◀]/.test(block) && block.includes("|")) ||
|
|
266
|
+
(block.includes("-->") && block.includes("[")) || // mermaid-like
|
|
267
|
+
(/\+-+\+/.test(block) && block.includes("|")) // ASCII box
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
function extractSections(content, keywords) {
|
|
271
|
+
const sections = [];
|
|
272
|
+
const lines = content.split("\n");
|
|
273
|
+
for (let i = 0; i < lines.length; i++) {
|
|
274
|
+
const line = lines[i];
|
|
275
|
+
const headingMatch = line.match(/^(#{1,3})\s+(.+)/);
|
|
276
|
+
if (!headingMatch)
|
|
277
|
+
continue;
|
|
278
|
+
const title = headingMatch[2].trim().toLowerCase();
|
|
279
|
+
if (!keywords.some((kw) => title.includes(kw)))
|
|
280
|
+
continue;
|
|
281
|
+
// Collect content until next heading of same or higher level
|
|
282
|
+
const level = headingMatch[1].length;
|
|
283
|
+
const contentLines = [];
|
|
284
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
285
|
+
const nextHeading = lines[j].match(/^(#{1,3})\s+/);
|
|
286
|
+
if (nextHeading && nextHeading[1].length <= level)
|
|
287
|
+
break;
|
|
288
|
+
contentLines.push(lines[j]);
|
|
289
|
+
}
|
|
290
|
+
sections.push({
|
|
291
|
+
title: headingMatch[2].trim(),
|
|
292
|
+
content: contentLines.join("\n").trim(),
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
return sections;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Extract service connection descriptions from text.
|
|
299
|
+
* Looks for patterns like "X calls Y", "X → Y", "X sends to Y"
|
|
300
|
+
*/
|
|
301
|
+
function extractConnectionDescriptions(content) {
|
|
302
|
+
const descs = [];
|
|
303
|
+
// "ServiceA calls ServiceB", "frontend talks to backend"
|
|
304
|
+
const callPatterns = content.match(/\b\w+\s+(?:calls?|connects?\s+to|talks?\s+to|sends?\s+to|communicates?\s+with|depends?\s+on|queries)\s+\w+/gi);
|
|
305
|
+
if (callPatterns) {
|
|
306
|
+
descs.push(...callPatterns.slice(0, 10));
|
|
307
|
+
}
|
|
308
|
+
// Arrow patterns in prose: "Backend → Database", "Client → Server"
|
|
309
|
+
const arrowPatterns = content.match(/\b\w+\s*[→→>-]+\s*\w+/g);
|
|
310
|
+
if (arrowPatterns) {
|
|
311
|
+
for (const p of arrowPatterns.slice(0, 10)) {
|
|
312
|
+
if (p.length < 50)
|
|
313
|
+
descs.push(p);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return descs;
|
|
317
|
+
}
|
|
318
|
+
function scanForExternalDeps(content, result) {
|
|
319
|
+
for (const svc of KNOWN_SERVICES) {
|
|
320
|
+
if (svc.pattern.test(content) && !result.externalDependencies.includes(svc.name)) {
|
|
321
|
+
result.externalDependencies.push(svc.name);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function parseOpenAPI(spec, result) {
|
|
326
|
+
if (!spec || typeof spec !== "object")
|
|
327
|
+
return;
|
|
328
|
+
const s = spec;
|
|
329
|
+
const paths = s.paths;
|
|
330
|
+
if (!paths)
|
|
331
|
+
return;
|
|
332
|
+
for (const [path, methods] of Object.entries(paths)) {
|
|
333
|
+
for (const [method, def] of Object.entries(methods)) {
|
|
334
|
+
if (["get", "post", "put", "patch", "delete"].includes(method)) {
|
|
335
|
+
const op = def;
|
|
336
|
+
result.apiEndpoints.push({
|
|
337
|
+
method: method.toUpperCase(),
|
|
338
|
+
path,
|
|
339
|
+
description: op.summary ?? op.description ?? "",
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
async function scanBuildDepsForServices(tk, result) {
|
|
346
|
+
const pkg = await tk.readJSON("package.json");
|
|
347
|
+
if (pkg) {
|
|
348
|
+
const depString = Object.keys({
|
|
349
|
+
...pkg.dependencies,
|
|
350
|
+
...pkg.devDependencies,
|
|
351
|
+
}).join(" ");
|
|
352
|
+
scanForExternalDeps(depString, result);
|
|
353
|
+
}
|
|
354
|
+
// Also check Cargo.toml at root
|
|
355
|
+
const cargo = await tk.readFileSafe("Cargo.toml");
|
|
356
|
+
if (cargo)
|
|
357
|
+
scanForExternalDeps(cargo, result);
|
|
358
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Static Analysis — Environment Detector
|
|
2
|
+
// Detects environments, config patterns, and secrets management from .env files
|
|
3
|
+
const SECRET_DEPS = [
|
|
4
|
+
"dotenv", "@aws-sdk/client-secrets-manager", "vault", "node-vault",
|
|
5
|
+
"@google-cloud/secret-manager", "@azure/keyvault-secrets", "infisical-sdk",
|
|
6
|
+
];
|
|
7
|
+
export async function detectEnvironments(tk) {
|
|
8
|
+
const result = {
|
|
9
|
+
environments: [],
|
|
10
|
+
configPattern: null,
|
|
11
|
+
hasSecrets: false,
|
|
12
|
+
};
|
|
13
|
+
// Find .env files
|
|
14
|
+
const envFiles = await tk.globFiles(".env*");
|
|
15
|
+
// Parse environment names from file names
|
|
16
|
+
const seenEnvs = new Set();
|
|
17
|
+
for (const file of envFiles) {
|
|
18
|
+
const name = file.replace(/^\.env\.?/, "").replace(/\.example$|\.sample$|\.template$/, "");
|
|
19
|
+
const envName = name || "default";
|
|
20
|
+
// Skip example/sample templates for environment detection, but still parse vars
|
|
21
|
+
const isTemplate = /\.example$|\.sample$|\.template$/.test(file);
|
|
22
|
+
if (!seenEnvs.has(envName) && !isTemplate) {
|
|
23
|
+
seenEnvs.add(envName);
|
|
24
|
+
}
|
|
25
|
+
// Read variable NAMES (not values)
|
|
26
|
+
const content = await tk.readFileSafe(file);
|
|
27
|
+
if (content) {
|
|
28
|
+
const varNames = content
|
|
29
|
+
.split("\n")
|
|
30
|
+
.filter((line) => /^[A-Z_][A-Z0-9_]*=/.test(line.trim()))
|
|
31
|
+
.map((line) => line.split("=")[0].trim());
|
|
32
|
+
// Look for the matching environment or create one
|
|
33
|
+
const existing = result.environments.find((e) => e.name === envName);
|
|
34
|
+
if (existing) {
|
|
35
|
+
for (const v of varNames) {
|
|
36
|
+
if (!existing.variables.includes(v))
|
|
37
|
+
existing.variables.push(v);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
result.environments.push({ name: envName, variables: varNames });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// If no .env files, check for config directories
|
|
46
|
+
if (result.environments.length === 0) {
|
|
47
|
+
const configFiles = await tk.globFiles("config/**/*");
|
|
48
|
+
if (configFiles.length > 0) {
|
|
49
|
+
result.configPattern = "config-directory";
|
|
50
|
+
// Try to detect env names from config file names
|
|
51
|
+
for (const f of configFiles) {
|
|
52
|
+
const match = f.match(/(?:config\/)?(development|production|staging|test|local)\./);
|
|
53
|
+
if (match && !seenEnvs.has(match[1])) {
|
|
54
|
+
seenEnvs.add(match[1]);
|
|
55
|
+
result.environments.push({ name: match[1], variables: [] });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Check for secrets management in deps
|
|
61
|
+
const pkg = await tk.readJSON("package.json");
|
|
62
|
+
if (pkg) {
|
|
63
|
+
const allDeps = Object.keys({
|
|
64
|
+
...pkg.dependencies,
|
|
65
|
+
...pkg.devDependencies,
|
|
66
|
+
});
|
|
67
|
+
result.hasSecrets = allDeps.some((d) => SECRET_DEPS.includes(d));
|
|
68
|
+
if (allDeps.includes("dotenv")) {
|
|
69
|
+
result.configPattern = result.configPattern ?? "dotenv";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Static Analysis — Event Detector
|
|
2
|
+
// Detects event-driven architecture patterns from deps and code grep
|
|
3
|
+
const EDA_DEPS = [
|
|
4
|
+
{ dep: "kafkajs", technology: "Kafka" },
|
|
5
|
+
{ dep: "@confluentinc/kafka-javascript", technology: "Kafka" },
|
|
6
|
+
{ dep: "amqplib", technology: "RabbitMQ" },
|
|
7
|
+
{ dep: "bullmq", technology: "Redis Streams (BullMQ)" },
|
|
8
|
+
{ dep: "bull", technology: "Redis Streams (Bull)" },
|
|
9
|
+
{ dep: "bee-queue", technology: "Redis Streams (Bee-Queue)" },
|
|
10
|
+
{ dep: "socket.io", technology: "WebSocket (Socket.IO)" },
|
|
11
|
+
{ dep: "ws", technology: "WebSocket" },
|
|
12
|
+
{ dep: "@google-cloud/pubsub", technology: "Google Pub/Sub" },
|
|
13
|
+
{ dep: "@aws-sdk/client-sqs", technology: "AWS SQS" },
|
|
14
|
+
{ dep: "@aws-sdk/client-sns", technology: "AWS SNS" },
|
|
15
|
+
{ dep: "@aws-sdk/client-eventbridge", technology: "AWS EventBridge" },
|
|
16
|
+
{ dep: "nats", technology: "NATS" },
|
|
17
|
+
{ dep: "mqtt", technology: "MQTT" },
|
|
18
|
+
{ dep: "@azure/service-bus", technology: "Azure Service Bus" },
|
|
19
|
+
{ dep: "@azure/event-hubs", technology: "Azure Event Hubs" },
|
|
20
|
+
{ dep: "sse-channel", technology: "Server-Sent Events" },
|
|
21
|
+
{ dep: "better-sse", technology: "Server-Sent Events" },
|
|
22
|
+
];
|
|
23
|
+
export async function detectEvents(tk) {
|
|
24
|
+
const result = {
|
|
25
|
+
hasEDA: false,
|
|
26
|
+
patterns: [],
|
|
27
|
+
events: [],
|
|
28
|
+
};
|
|
29
|
+
// Check package.json deps
|
|
30
|
+
const pkg = await tk.readJSON("package.json");
|
|
31
|
+
if (pkg) {
|
|
32
|
+
const allDeps = Object.keys({
|
|
33
|
+
...pkg.dependencies,
|
|
34
|
+
...pkg.devDependencies,
|
|
35
|
+
});
|
|
36
|
+
for (const edaDep of EDA_DEPS) {
|
|
37
|
+
if (allDeps.includes(edaDep.dep)) {
|
|
38
|
+
result.hasEDA = true;
|
|
39
|
+
result.patterns.push({
|
|
40
|
+
technology: edaDep.technology,
|
|
41
|
+
dependency: edaDep.dep,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Grep for event patterns (filtered to exclude vendored code)
|
|
47
|
+
const [publishResults, subscribeResults, emitResults] = await Promise.all([
|
|
48
|
+
tk.grepFiles("\\.publish\\(|\\.send\\(|\\.produce\\("),
|
|
49
|
+
tk.grepFiles("\\.subscribe\\(|\\.consume\\(|\\.on\\("),
|
|
50
|
+
tk.grepFiles("\\.emit\\(|\\.dispatch\\(|\\.trigger\\("),
|
|
51
|
+
]);
|
|
52
|
+
// Exclude vendored/generated dirs from all results
|
|
53
|
+
const excludeDirs = /\/(node_modules|venv|\.venv|__pycache__|dist|build|\.git|vendor|target)\//;
|
|
54
|
+
const filterVendored = (results) => results.filter((r) => !excludeDirs.test("/" + r.file));
|
|
55
|
+
// Filter subscribe results to only EDA-relevant ones (skip generic .on() calls)
|
|
56
|
+
const filteredSubscribe = filterVendored(subscribeResults).filter((r) => /\.(subscribe|consume)\(/.test(r.content));
|
|
57
|
+
for (const r of filterVendored(publishResults).slice(0, 20)) {
|
|
58
|
+
result.events.push({ type: "publish", file: r.file, pattern: r.content.slice(0, 100) });
|
|
59
|
+
}
|
|
60
|
+
for (const r of filteredSubscribe.slice(0, 20)) {
|
|
61
|
+
result.events.push({ type: "subscribe", file: r.file, pattern: r.content.slice(0, 100) });
|
|
62
|
+
}
|
|
63
|
+
for (const r of filterVendored(emitResults).slice(0, 20)) {
|
|
64
|
+
result.events.push({ type: "emit", file: r.file, pattern: r.content.slice(0, 100) });
|
|
65
|
+
}
|
|
66
|
+
if (result.events.length > 0) {
|
|
67
|
+
result.hasEDA = true;
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Static Analysis — File Tree Collector
|
|
2
|
+
// Collects depth-limited directory tree for LLM context
|
|
3
|
+
const SKIP_DIRS = new Set([
|
|
4
|
+
"node_modules", "dist", "build", ".git", "venv", "__pycache__",
|
|
5
|
+
"target", "coverage", ".next", ".nuxt", ".output", ".cache",
|
|
6
|
+
".turbo", ".nx", "vendor", ".venv", "env",
|
|
7
|
+
]);
|
|
8
|
+
const MAX_ENTRIES = 2000;
|
|
9
|
+
const MAX_DEPTH = 3;
|
|
10
|
+
export async function collectFileTree(tk) {
|
|
11
|
+
let totalFiles = 0;
|
|
12
|
+
let totalDirs = 0;
|
|
13
|
+
let entryCount = 0;
|
|
14
|
+
async function walk(dirPath, depth) {
|
|
15
|
+
if (depth > MAX_DEPTH || entryCount >= MAX_ENTRIES)
|
|
16
|
+
return [];
|
|
17
|
+
const entries = await tk.listDir(dirPath);
|
|
18
|
+
const result = [];
|
|
19
|
+
// Sort: directories first, then files
|
|
20
|
+
const sorted = entries.sort((a, b) => {
|
|
21
|
+
if (a.type !== b.type)
|
|
22
|
+
return a.type === "directory" ? -1 : 1;
|
|
23
|
+
return a.name.localeCompare(b.name);
|
|
24
|
+
});
|
|
25
|
+
for (const entry of sorted) {
|
|
26
|
+
if (entryCount >= MAX_ENTRIES)
|
|
27
|
+
break;
|
|
28
|
+
// Skip hidden dirs and vendored dirs
|
|
29
|
+
if (entry.type === "directory") {
|
|
30
|
+
if (entry.name.startsWith(".") && entry.name !== ".github")
|
|
31
|
+
continue;
|
|
32
|
+
if (SKIP_DIRS.has(entry.name))
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const entryPath = dirPath === "." ? entry.name : `${dirPath}/${entry.name}`;
|
|
36
|
+
entryCount++;
|
|
37
|
+
if (entry.type === "directory") {
|
|
38
|
+
totalDirs++;
|
|
39
|
+
const children = await walk(entryPath, depth + 1);
|
|
40
|
+
result.push({ path: entryPath, type: "directory", children });
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
totalFiles++;
|
|
44
|
+
result.push({ path: entryPath, type: "file" });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
const tree = await walk(".", 0);
|
|
50
|
+
return { tree, totalFiles, totalDirs };
|
|
51
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { StaticAnalysisResult, StaticContext } from "./types.js";
|
|
2
|
+
export type { StaticAnalysisResult, StaticContext } from "./types.js";
|
|
3
|
+
export { validateAnalysis } from "./validator.js";
|
|
4
|
+
/**
|
|
5
|
+
* Run all static analysis scanners.
|
|
6
|
+
*
|
|
7
|
+
* Pipeline:
|
|
8
|
+
* 1. Parallel: structure, docs, infra, events, envs
|
|
9
|
+
* 2. Sequential: components (needs structure), connections (needs components+infra+events)
|
|
10
|
+
* 3. Validation + auto-repair
|
|
11
|
+
* 4. Gap detection — identify what the LLM should resolve
|
|
12
|
+
*/
|
|
13
|
+
export declare function runStaticAnalysis(projectRoot: string, onProgress?: (msg: string) => void): Promise<StaticAnalysisResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Collect raw static context from all 7 scanners.
|
|
16
|
+
* This runs ONLY fact-collectors (no component-detector, connection-mapper, or validator).
|
|
17
|
+
* Output is consumed by the pipeline LLM agents.
|
|
18
|
+
*/
|
|
19
|
+
export declare function runStaticContextCollection(projectRoot: string, onProgress?: (msg: string) => void): Promise<StaticContext>;
|