archbyte 0.5.1 → 0.5.2
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/bin/archbyte.js +7 -2
- package/dist/agents/pipeline/agents/service-describer.js +4 -4
- package/dist/agents/pipeline/merger.js +26 -34
- package/dist/cli/analyze.js +15 -5
- package/dist/server/src/index.js +25 -4
- package/package.json +1 -1
- package/ui/dist/assets/index-CWGPRsWP.js +72 -0
- package/ui/dist/index.html +1 -1
- package/ui/dist/assets/index-QllGSFhe.js +0 -72
package/bin/archbyte.js
CHANGED
|
@@ -22,6 +22,11 @@ import { handleVersion, handleUpdate } from '../dist/cli/version.js';
|
|
|
22
22
|
import { requireLicense } from '../dist/cli/license-gate.js';
|
|
23
23
|
import { DEFAULT_PORT } from '../dist/cli/constants.js';
|
|
24
24
|
|
|
25
|
+
// When spawned by `archbyte serve` (internal), skip interactive license checks.
|
|
26
|
+
// The user already authenticated when they started the server.
|
|
27
|
+
const isInternal = process.env.ARCHBYTE_INTERNAL === '1';
|
|
28
|
+
const gate = isInternal ? async () => {} : requireLicense;
|
|
29
|
+
|
|
25
30
|
const require = createRequire(import.meta.url);
|
|
26
31
|
const { version: PKG_VERSION } = require('../package.json');
|
|
27
32
|
|
|
@@ -111,7 +116,7 @@ program
|
|
|
111
116
|
.option('--force', 'Force full re-scan (skip incremental detection)')
|
|
112
117
|
.option('--dry-run', 'Preview without running')
|
|
113
118
|
.action(async (options) => {
|
|
114
|
-
await
|
|
119
|
+
await gate('analyze');
|
|
115
120
|
await handleAnalyze(options);
|
|
116
121
|
});
|
|
117
122
|
|
|
@@ -122,7 +127,7 @@ program
|
|
|
122
127
|
.option('-o, --output <path>', 'Output diagram (default: .archbyte/architecture.json)')
|
|
123
128
|
.option('-v, --verbose', 'Show detailed output')
|
|
124
129
|
.action(async (options) => {
|
|
125
|
-
await
|
|
130
|
+
await gate('generate');
|
|
126
131
|
await handleGenerate(options);
|
|
127
132
|
});
|
|
128
133
|
|
|
@@ -33,13 +33,13 @@ export const serviceDescriber = {
|
|
|
33
33
|
parts.push(`Detected language: ${ctx.structure.language}`);
|
|
34
34
|
parts.push(`Languages: ${ctx.structure.languages.join(", ") || "none"}`);
|
|
35
35
|
parts.push(`Framework: ${ctx.structure.framework ?? "none"}`);
|
|
36
|
-
// Docs
|
|
36
|
+
// Docs — only project description, NOT externalDependencies.
|
|
37
|
+
// Doc-extracted dependency mentions prime the LLM to hallucinate phantom services
|
|
38
|
+
// (e.g., docs mention "MCP" → LLM creates "MCP Server" component).
|
|
39
|
+
// The LLM should discover services from actual code evidence only.
|
|
37
40
|
if (ctx.docs.projectDescription) {
|
|
38
41
|
parts.push(`\nFrom docs: ${ctx.docs.projectDescription}`);
|
|
39
42
|
}
|
|
40
|
-
if (ctx.docs.externalDependencies.length > 0) {
|
|
41
|
-
parts.push(`\nExternal dependencies mentioned: ${ctx.docs.externalDependencies.join(", ")}`);
|
|
42
|
-
}
|
|
43
43
|
// Docker services — only include if infra/config files changed (or full scan)
|
|
44
44
|
if (ctx.infra.docker.composeFile && (hasInfraChanges || hasConfigChanges)) {
|
|
45
45
|
const svcInfo = ctx.infra.docker.services.map((s) => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Pipeline — Merger
|
|
2
2
|
// Assembles all agent outputs into a StaticAnalysisResult
|
|
3
|
+
import { categorizeDep } from "../static/taxonomy.js";
|
|
3
4
|
function sanitize(s) {
|
|
4
5
|
if (!s)
|
|
5
6
|
return s;
|
|
@@ -9,21 +10,24 @@ function sanitize(s) {
|
|
|
9
10
|
* Build a set of "evidence tokens" from the static context — things that concretely
|
|
10
11
|
* exist in the codebase (dependencies, env vars, docker images/services).
|
|
11
12
|
* Used to gate LLM-generated databases/external services against hallucination.
|
|
13
|
+
*
|
|
14
|
+
* Uses the package taxonomy to resolve package names to their display names
|
|
15
|
+
* (e.g., "pg" → also adds "postgresql", "stripe" → also adds "stripe").
|
|
16
|
+
* This lets the LLM use human-readable names while still requiring code evidence.
|
|
12
17
|
*/
|
|
13
18
|
function buildEvidenceTokens(ctx) {
|
|
14
19
|
const tokens = new Set();
|
|
20
|
+
/** Add a dependency name + its taxonomy display name as tokens. */
|
|
21
|
+
function addDep(dep) {
|
|
22
|
+
tokens.add(dep.toLowerCase());
|
|
23
|
+
const cat = categorizeDep(dep);
|
|
24
|
+
if (cat)
|
|
25
|
+
tokens.add(cat.displayName.toLowerCase());
|
|
26
|
+
}
|
|
15
27
|
// Package dependencies from import map (codeSamples.importMap: file → imported modules)
|
|
16
28
|
for (const imports of Object.values(ctx.codeSamples.importMap)) {
|
|
17
|
-
for (const imp of imports)
|
|
18
|
-
|
|
19
|
-
// Also add short name for scoped packages: @aws-sdk/client-s3 → client-s3, aws-sdk
|
|
20
|
-
if (imp.startsWith("@")) {
|
|
21
|
-
const parts = imp.split("/");
|
|
22
|
-
if (parts[1])
|
|
23
|
-
tokens.add(parts[1].toLowerCase());
|
|
24
|
-
tokens.add(parts[0].slice(1).toLowerCase());
|
|
25
|
-
}
|
|
26
|
-
}
|
|
29
|
+
for (const imp of imports)
|
|
30
|
+
addDep(imp);
|
|
27
31
|
}
|
|
28
32
|
// Config files may contain dependency info (package.json deps etc.)
|
|
29
33
|
for (const cfg of ctx.codeSamples.configFiles) {
|
|
@@ -31,13 +35,7 @@ function buildEvidenceTokens(ctx) {
|
|
|
31
35
|
try {
|
|
32
36
|
const pkg = JSON.parse(cfg.content);
|
|
33
37
|
for (const dep of Object.keys({ ...pkg.dependencies, ...pkg.devDependencies })) {
|
|
34
|
-
|
|
35
|
-
if (dep.startsWith("@")) {
|
|
36
|
-
const parts = dep.split("/");
|
|
37
|
-
if (parts[1])
|
|
38
|
-
tokens.add(parts[1].toLowerCase());
|
|
39
|
-
tokens.add(parts[0].slice(1).toLowerCase());
|
|
40
|
-
}
|
|
38
|
+
addDep(dep);
|
|
41
39
|
}
|
|
42
40
|
}
|
|
43
41
|
catch { /* ignore parse errors */ }
|
|
@@ -59,35 +57,29 @@ function buildEvidenceTokens(ctx) {
|
|
|
59
57
|
for (const s of ctx.infra.cloud.services) {
|
|
60
58
|
tokens.add(s.toLowerCase());
|
|
61
59
|
}
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
// NOTE: ctx.docs.externalDependencies intentionally excluded.
|
|
61
|
+
// Doc mentions (from markdown/README) are not concrete code evidence and cause
|
|
62
|
+
// hallucination — the LLM sees "MCP" in docs and creates phantom components.
|
|
63
|
+
// Only code-level signals (imports, deps, env vars, Docker, cloud) count.
|
|
66
64
|
return tokens;
|
|
67
65
|
}
|
|
68
66
|
/**
|
|
69
|
-
* Check if a service/database
|
|
70
|
-
*
|
|
67
|
+
* Check if a service/database has concrete evidence in the static context.
|
|
68
|
+
* Strict exact-match only — no substring/regex fuzzy matching.
|
|
69
|
+
* The taxonomy enriches evidence tokens with display names (pg → PostgreSQL)
|
|
70
|
+
* so the LLM can use human-readable names and still match.
|
|
71
71
|
*/
|
|
72
72
|
function hasEvidence(id, name, type, evidenceTokens) {
|
|
73
|
-
// Build candidate keywords from the service
|
|
74
73
|
const candidates = [
|
|
75
74
|
id.toLowerCase(),
|
|
76
75
|
name.toLowerCase(),
|
|
77
76
|
type.toLowerCase(),
|
|
78
|
-
// Split hyphenated IDs: "aws-sqs" →
|
|
77
|
+
// Split hyphenated IDs: "aws-sqs" → also check "aws", "sqs"
|
|
79
78
|
...id.toLowerCase().split("-"),
|
|
80
79
|
].filter(Boolean);
|
|
81
80
|
for (const candidate of candidates) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (token === candidate)
|
|
85
|
-
return true;
|
|
86
|
-
if (token.includes(candidate) && candidate.length >= 3)
|
|
87
|
-
return true;
|
|
88
|
-
if (candidate.includes(token) && token.length >= 3)
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
81
|
+
if (evidenceTokens.has(candidate))
|
|
82
|
+
return true;
|
|
91
83
|
}
|
|
92
84
|
return false;
|
|
93
85
|
}
|
package/dist/cli/analyze.js
CHANGED
|
@@ -50,10 +50,11 @@ export async function handleAnalyze(options) {
|
|
|
50
50
|
progress.update(1, "Building analysis...");
|
|
51
51
|
const freshAnalysis = buildAnalysisFromStatic(result, rootDir);
|
|
52
52
|
const duration = Date.now() - startTime;
|
|
53
|
-
// Merge into existing
|
|
53
|
+
// Merge into existing data if it was produced by an agentic run,
|
|
54
54
|
// preserving LLM-generated components/connections while refreshing
|
|
55
55
|
// static data (environments, metadata, project info).
|
|
56
56
|
const existingAnalysis = loadExistingAnalysis(rootDir);
|
|
57
|
+
const existingSpec = loadSpec(rootDir);
|
|
57
58
|
const wasAgentic = existingAnalysis && existingAnalysis.metadata?.mode !== "static";
|
|
58
59
|
const analysis = wasAgentic ? mergeStaticIntoExisting(existingAnalysis, freshAnalysis) : freshAnalysis;
|
|
59
60
|
// Stamp scan metadata on analysis.json (backward compat)
|
|
@@ -62,10 +63,19 @@ export async function handleAnalyze(options) {
|
|
|
62
63
|
ameta.mode = wasAgentic ? "static-refresh" : "static";
|
|
63
64
|
writeAnalysis(rootDir, analysis);
|
|
64
65
|
// Dual-write: archbyte.yaml + metadata.json
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
// When prior data came from an agentic run, only refresh static fields
|
|
67
|
+
// (project info, environments) — never overwrite LLM components/connections.
|
|
68
|
+
if (wasAgentic && existingSpec) {
|
|
69
|
+
const freshSpec = staticResultToSpec(result, rootDir, existingSpec.rules);
|
|
70
|
+
existingSpec.project = freshSpec.project;
|
|
71
|
+
existingSpec.environments = freshSpec.environments;
|
|
72
|
+
writeSpec(rootDir, existingSpec);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
const spec = staticResultToSpec(result, rootDir, existingSpec?.rules);
|
|
76
|
+
writeSpec(rootDir, spec);
|
|
77
|
+
}
|
|
78
|
+
writeScanMetadata(rootDir, duration, wasAgentic ? "static-refresh" : "static");
|
|
69
79
|
progress.update(2, "Generating diagram...");
|
|
70
80
|
await autoGenerate(rootDir, options);
|
|
71
81
|
progress.done("Analysis complete");
|
package/dist/server/src/index.js
CHANGED
|
@@ -138,6 +138,11 @@ function createHttpServer() {
|
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
140
|
const url = req.url || "/";
|
|
141
|
+
// Log API actions to terminal (skip static files, SSE, health checks)
|
|
142
|
+
if (url.startsWith("/api/") && url !== "/api/health") {
|
|
143
|
+
const label = url.replace("/api/", "").split("?")[0];
|
|
144
|
+
console.error(`[archbyte] ${req.method} ${label}`);
|
|
145
|
+
}
|
|
141
146
|
// SSE endpoint
|
|
142
147
|
if (url === "/events") {
|
|
143
148
|
res.writeHead(200, {
|
|
@@ -1129,7 +1134,7 @@ function createHttpServer() {
|
|
|
1129
1134
|
const child = spawn(process.execPath, [bin, "generate"], {
|
|
1130
1135
|
cwd: config.workspaceRoot,
|
|
1131
1136
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1132
|
-
env: { ...process.env, FORCE_COLOR: "0" },
|
|
1137
|
+
env: { ...process.env, FORCE_COLOR: "0", ARCHBYTE_INTERNAL: "1" },
|
|
1133
1138
|
});
|
|
1134
1139
|
runningWorkflows.set("__generate__", child);
|
|
1135
1140
|
broadcastOpsEvent({ type: "generate:started" });
|
|
@@ -1221,7 +1226,7 @@ function createHttpServer() {
|
|
|
1221
1226
|
const child = spawn(process.execPath, [bin, "workflow", "--run", id], {
|
|
1222
1227
|
cwd: config.workspaceRoot,
|
|
1223
1228
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1224
|
-
env: { ...process.env, FORCE_COLOR: "0" },
|
|
1229
|
+
env: { ...process.env, FORCE_COLOR: "0", ARCHBYTE_INTERNAL: "1" },
|
|
1225
1230
|
});
|
|
1226
1231
|
runningWorkflows.set(id, child);
|
|
1227
1232
|
broadcastOpsEvent({ type: "workflow:started", id });
|
|
@@ -1452,6 +1457,22 @@ function createHttpServer() {
|
|
|
1452
1457
|
}
|
|
1453
1458
|
return;
|
|
1454
1459
|
}
|
|
1460
|
+
// API: UI event logging — fire-and-forget from UI to server console
|
|
1461
|
+
if (url === "/api/ui-event" && req.method === "POST") {
|
|
1462
|
+
let body = "";
|
|
1463
|
+
req.on("data", (chunk) => { body += chunk.toString(); });
|
|
1464
|
+
req.on("end", () => {
|
|
1465
|
+
try {
|
|
1466
|
+
const { event, detail } = JSON.parse(body);
|
|
1467
|
+
const msg = detail ? `${event} — ${detail}` : event;
|
|
1468
|
+
console.error(`[archbyte] ui: ${msg}`);
|
|
1469
|
+
}
|
|
1470
|
+
catch { }
|
|
1471
|
+
res.writeHead(204);
|
|
1472
|
+
res.end();
|
|
1473
|
+
});
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1455
1476
|
// API: Telemetry — record agent timing data
|
|
1456
1477
|
if (url === "/api/telemetry" && req.method === "POST") {
|
|
1457
1478
|
let body = "";
|
|
@@ -1717,7 +1738,7 @@ function runAnalyzePipeline(mode = "static", fileChanges) {
|
|
|
1717
1738
|
const analyzeChild = spawn(process.execPath, analyzeArgs, {
|
|
1718
1739
|
cwd: config.workspaceRoot,
|
|
1719
1740
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1720
|
-
env: { ...process.env, FORCE_COLOR: "0" },
|
|
1741
|
+
env: { ...process.env, FORCE_COLOR: "0", ARCHBYTE_INTERNAL: "1" },
|
|
1721
1742
|
});
|
|
1722
1743
|
let analyzeStderr = "";
|
|
1723
1744
|
analyzeChild.stdout?.on("data", (d) => process.stderr.write(`[analyze] ${d}`));
|
|
@@ -1733,7 +1754,7 @@ function runAnalyzePipeline(mode = "static", fileChanges) {
|
|
|
1733
1754
|
const genChild = spawn(process.execPath, [bin, "generate"], {
|
|
1734
1755
|
cwd: config.workspaceRoot,
|
|
1735
1756
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1736
|
-
env: { ...process.env, FORCE_COLOR: "0" },
|
|
1757
|
+
env: { ...process.env, FORCE_COLOR: "0", ARCHBYTE_INTERNAL: "1" },
|
|
1737
1758
|
});
|
|
1738
1759
|
genChild.stdout?.on("data", (d) => process.stderr.write(`[generate] ${d}`));
|
|
1739
1760
|
genChild.stderr?.on("data", (d) => process.stderr.write(`[generate] ${d}`));
|