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,195 @@
|
|
|
1
|
+
// Static Analysis — Structure Scanner
|
|
2
|
+
// Detects project language, framework, monorepo, package manager, entry points
|
|
3
|
+
export async function scanStructure(tk) {
|
|
4
|
+
const result = {
|
|
5
|
+
projectName: "",
|
|
6
|
+
language: "unknown",
|
|
7
|
+
languages: [],
|
|
8
|
+
framework: null,
|
|
9
|
+
packageManager: null,
|
|
10
|
+
isMonorepo: false,
|
|
11
|
+
monorepoTool: null,
|
|
12
|
+
entryPoints: [],
|
|
13
|
+
buildSystem: null,
|
|
14
|
+
testFramework: null,
|
|
15
|
+
directories: {},
|
|
16
|
+
};
|
|
17
|
+
// Read config files in parallel
|
|
18
|
+
const [pkg, cargoToml, goMod, pyProject, tsconfig] = await Promise.all([
|
|
19
|
+
tk.readJSON("package.json"),
|
|
20
|
+
tk.readFileSafe("Cargo.toml"),
|
|
21
|
+
tk.readFileSafe("go.mod"),
|
|
22
|
+
tk.readFileSafe("pyproject.toml"),
|
|
23
|
+
tk.readJSON("tsconfig.json"),
|
|
24
|
+
]);
|
|
25
|
+
// Project name
|
|
26
|
+
if (pkg?.name) {
|
|
27
|
+
result.projectName = pkg.name;
|
|
28
|
+
}
|
|
29
|
+
else if (goMod) {
|
|
30
|
+
const match = goMod.match(/^module\s+(.+)/m);
|
|
31
|
+
if (match)
|
|
32
|
+
result.projectName = match[1].split("/").pop() ?? match[1];
|
|
33
|
+
}
|
|
34
|
+
// Language detection
|
|
35
|
+
if (tsconfig) {
|
|
36
|
+
result.language = "TypeScript";
|
|
37
|
+
result.languages.push("TypeScript");
|
|
38
|
+
}
|
|
39
|
+
if (pkg && !tsconfig) {
|
|
40
|
+
result.languages.push("JavaScript");
|
|
41
|
+
if (result.language === "unknown")
|
|
42
|
+
result.language = "JavaScript";
|
|
43
|
+
}
|
|
44
|
+
if (cargoToml) {
|
|
45
|
+
result.languages.push("Rust");
|
|
46
|
+
if (result.language === "unknown")
|
|
47
|
+
result.language = "Rust";
|
|
48
|
+
}
|
|
49
|
+
if (goMod) {
|
|
50
|
+
result.languages.push("Go");
|
|
51
|
+
if (result.language === "unknown")
|
|
52
|
+
result.language = "Go";
|
|
53
|
+
}
|
|
54
|
+
if (pyProject) {
|
|
55
|
+
result.languages.push("Python");
|
|
56
|
+
if (result.language === "unknown")
|
|
57
|
+
result.language = "Python";
|
|
58
|
+
}
|
|
59
|
+
// Framework detection from deps
|
|
60
|
+
if (pkg) {
|
|
61
|
+
const allDeps = {
|
|
62
|
+
...pkg.dependencies,
|
|
63
|
+
...pkg.devDependencies,
|
|
64
|
+
};
|
|
65
|
+
const depNames = Object.keys(allDeps);
|
|
66
|
+
// JS/TS frameworks
|
|
67
|
+
if (depNames.includes("next"))
|
|
68
|
+
result.framework = "Next.js";
|
|
69
|
+
else if (depNames.includes("nuxt"))
|
|
70
|
+
result.framework = "Nuxt";
|
|
71
|
+
else if (depNames.includes("@nestjs/core"))
|
|
72
|
+
result.framework = "NestJS";
|
|
73
|
+
else if (depNames.includes("fastify"))
|
|
74
|
+
result.framework = "Fastify";
|
|
75
|
+
else if (depNames.includes("express"))
|
|
76
|
+
result.framework = "Express";
|
|
77
|
+
else if (depNames.includes("hono"))
|
|
78
|
+
result.framework = "Hono";
|
|
79
|
+
else if (depNames.includes("react"))
|
|
80
|
+
result.framework = "React";
|
|
81
|
+
else if (depNames.includes("vue"))
|
|
82
|
+
result.framework = "Vue";
|
|
83
|
+
else if (depNames.includes("svelte"))
|
|
84
|
+
result.framework = "Svelte";
|
|
85
|
+
else if (depNames.includes("angular"))
|
|
86
|
+
result.framework = "Angular";
|
|
87
|
+
// Test framework
|
|
88
|
+
if (depNames.includes("vitest"))
|
|
89
|
+
result.testFramework = "Vitest";
|
|
90
|
+
else if (depNames.includes("jest"))
|
|
91
|
+
result.testFramework = "Jest";
|
|
92
|
+
else if (depNames.includes("mocha"))
|
|
93
|
+
result.testFramework = "Mocha";
|
|
94
|
+
// Build system
|
|
95
|
+
if (depNames.includes("vite"))
|
|
96
|
+
result.buildSystem = "Vite";
|
|
97
|
+
else if (depNames.includes("webpack"))
|
|
98
|
+
result.buildSystem = "Webpack";
|
|
99
|
+
else if (depNames.includes("esbuild"))
|
|
100
|
+
result.buildSystem = "esbuild";
|
|
101
|
+
else if (depNames.includes("rollup"))
|
|
102
|
+
result.buildSystem = "Rollup";
|
|
103
|
+
// Monorepo detection
|
|
104
|
+
if (pkg.workspaces) {
|
|
105
|
+
result.isMonorepo = true;
|
|
106
|
+
result.monorepoTool = "npm-workspaces";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Python frameworks
|
|
110
|
+
if (pyProject) {
|
|
111
|
+
if (pyProject.includes("django"))
|
|
112
|
+
result.framework = "Django";
|
|
113
|
+
else if (pyProject.includes("fastapi"))
|
|
114
|
+
result.framework = "FastAPI";
|
|
115
|
+
else if (pyProject.includes("flask"))
|
|
116
|
+
result.framework = "Flask";
|
|
117
|
+
}
|
|
118
|
+
// Rust frameworks
|
|
119
|
+
if (cargoToml) {
|
|
120
|
+
if (cargoToml.includes("axum"))
|
|
121
|
+
result.framework = "Axum";
|
|
122
|
+
else if (cargoToml.includes("actix"))
|
|
123
|
+
result.framework = "Actix";
|
|
124
|
+
else if (cargoToml.includes("rocket"))
|
|
125
|
+
result.framework = "Rocket";
|
|
126
|
+
result.buildSystem = "Cargo";
|
|
127
|
+
}
|
|
128
|
+
// Go frameworks
|
|
129
|
+
if (goMod) {
|
|
130
|
+
if (goMod.includes("gin-gonic"))
|
|
131
|
+
result.framework = "Gin";
|
|
132
|
+
else if (goMod.includes("labstack/echo"))
|
|
133
|
+
result.framework = "Echo";
|
|
134
|
+
else if (goMod.includes("go-fiber"))
|
|
135
|
+
result.framework = "Fiber";
|
|
136
|
+
result.buildSystem = "Go";
|
|
137
|
+
}
|
|
138
|
+
// Monorepo tools (check files in parallel)
|
|
139
|
+
const [nxJson, turboJson, lernaJson, pnpmWorkspace] = await Promise.all([
|
|
140
|
+
tk.readJSON("nx.json"),
|
|
141
|
+
tk.readJSON("turbo.json"),
|
|
142
|
+
tk.readJSON("lerna.json"),
|
|
143
|
+
tk.readFileSafe("pnpm-workspace.yaml"),
|
|
144
|
+
]);
|
|
145
|
+
if (nxJson) {
|
|
146
|
+
result.isMonorepo = true;
|
|
147
|
+
result.monorepoTool = "nx";
|
|
148
|
+
}
|
|
149
|
+
if (turboJson) {
|
|
150
|
+
result.isMonorepo = true;
|
|
151
|
+
result.monorepoTool = "turborepo";
|
|
152
|
+
}
|
|
153
|
+
if (lernaJson) {
|
|
154
|
+
result.isMonorepo = true;
|
|
155
|
+
result.monorepoTool = "lerna";
|
|
156
|
+
}
|
|
157
|
+
if (pnpmWorkspace) {
|
|
158
|
+
result.isMonorepo = true;
|
|
159
|
+
result.monorepoTool = "pnpm";
|
|
160
|
+
}
|
|
161
|
+
// Package manager detection
|
|
162
|
+
const [hasPackageLock, hasYarnLock, hasPnpmLock, hasBunLock] = await Promise.all([
|
|
163
|
+
tk.readFileSafe("package-lock.json").then((c) => !!c),
|
|
164
|
+
tk.readFileSafe("yarn.lock").then((c) => !!c),
|
|
165
|
+
tk.readFileSafe("pnpm-lock.yaml").then((c) => !!c),
|
|
166
|
+
tk.readFileSafe("bun.lockb").then((c) => !!c),
|
|
167
|
+
]);
|
|
168
|
+
if (hasPnpmLock)
|
|
169
|
+
result.packageManager = "pnpm";
|
|
170
|
+
else if (hasYarnLock)
|
|
171
|
+
result.packageManager = "yarn";
|
|
172
|
+
else if (hasBunLock)
|
|
173
|
+
result.packageManager = "bun";
|
|
174
|
+
else if (hasPackageLock)
|
|
175
|
+
result.packageManager = "npm";
|
|
176
|
+
// Entry points
|
|
177
|
+
if (pkg?.main)
|
|
178
|
+
result.entryPoints.push(pkg.main);
|
|
179
|
+
if (pkg?.bin) {
|
|
180
|
+
const bin = pkg.bin;
|
|
181
|
+
if (typeof bin === "string") {
|
|
182
|
+
result.entryPoints.push(bin);
|
|
183
|
+
}
|
|
184
|
+
else if (typeof bin === "object") {
|
|
185
|
+
result.entryPoints.push(...Object.values(bin));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Directory structure
|
|
189
|
+
const rootEntries = await tk.listDir(".");
|
|
190
|
+
const dirs = rootEntries.filter((e) => e.type === "directory").map((e) => e.name);
|
|
191
|
+
for (const d of dirs) {
|
|
192
|
+
result.directories[d] = true;
|
|
193
|
+
}
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
export interface StructureResult {
|
|
2
|
+
projectName: string;
|
|
3
|
+
language: string;
|
|
4
|
+
languages: string[];
|
|
5
|
+
framework: string | null;
|
|
6
|
+
packageManager: string | null;
|
|
7
|
+
isMonorepo: boolean;
|
|
8
|
+
monorepoTool: string | null;
|
|
9
|
+
entryPoints: string[];
|
|
10
|
+
buildSystem: string | null;
|
|
11
|
+
testFramework: string | null;
|
|
12
|
+
directories: Record<string, boolean>;
|
|
13
|
+
}
|
|
14
|
+
export interface DocResult {
|
|
15
|
+
projectDescription: string;
|
|
16
|
+
architectureNotes: string[];
|
|
17
|
+
apiEndpoints: Array<{
|
|
18
|
+
method: string;
|
|
19
|
+
path: string;
|
|
20
|
+
description: string;
|
|
21
|
+
}>;
|
|
22
|
+
externalDependencies: string[];
|
|
23
|
+
}
|
|
24
|
+
export interface ComponentResult {
|
|
25
|
+
components: StaticComponent[];
|
|
26
|
+
}
|
|
27
|
+
export interface StaticComponent {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
type: string;
|
|
31
|
+
layer: string;
|
|
32
|
+
path: string;
|
|
33
|
+
description: string;
|
|
34
|
+
technologies: string[];
|
|
35
|
+
}
|
|
36
|
+
export interface InfraResult {
|
|
37
|
+
docker: {
|
|
38
|
+
services: Array<{
|
|
39
|
+
name: string;
|
|
40
|
+
image?: string;
|
|
41
|
+
buildContext?: string;
|
|
42
|
+
ports?: string[];
|
|
43
|
+
dependsOn?: string[];
|
|
44
|
+
environment?: Record<string, string>;
|
|
45
|
+
}>;
|
|
46
|
+
composeFile: boolean;
|
|
47
|
+
composeFilePath?: string;
|
|
48
|
+
};
|
|
49
|
+
kubernetes: {
|
|
50
|
+
resources: Array<{
|
|
51
|
+
kind: string;
|
|
52
|
+
name: string;
|
|
53
|
+
namespace?: string;
|
|
54
|
+
}>;
|
|
55
|
+
};
|
|
56
|
+
cloud: {
|
|
57
|
+
provider: string | null;
|
|
58
|
+
services: string[];
|
|
59
|
+
iac: string | null;
|
|
60
|
+
};
|
|
61
|
+
ci: {
|
|
62
|
+
platform: string | null;
|
|
63
|
+
pipelines: string[];
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export interface EventResult {
|
|
67
|
+
hasEDA: boolean;
|
|
68
|
+
patterns: Array<{
|
|
69
|
+
technology: string;
|
|
70
|
+
dependency: string;
|
|
71
|
+
}>;
|
|
72
|
+
events: Array<{
|
|
73
|
+
type: string;
|
|
74
|
+
file: string;
|
|
75
|
+
pattern: string;
|
|
76
|
+
}>;
|
|
77
|
+
}
|
|
78
|
+
export interface EnvResult {
|
|
79
|
+
environments: Array<{
|
|
80
|
+
name: string;
|
|
81
|
+
variables: string[];
|
|
82
|
+
}>;
|
|
83
|
+
configPattern: string | null;
|
|
84
|
+
hasSecrets: boolean;
|
|
85
|
+
}
|
|
86
|
+
export interface StaticConnection {
|
|
87
|
+
from: string;
|
|
88
|
+
to: string;
|
|
89
|
+
type: string;
|
|
90
|
+
description: string;
|
|
91
|
+
confidence: number;
|
|
92
|
+
async: boolean;
|
|
93
|
+
}
|
|
94
|
+
export interface ConnectionResult {
|
|
95
|
+
connections: StaticConnection[];
|
|
96
|
+
flows: Array<{
|
|
97
|
+
name: string;
|
|
98
|
+
description: string;
|
|
99
|
+
steps: string[];
|
|
100
|
+
}>;
|
|
101
|
+
}
|
|
102
|
+
export interface ValidationResult {
|
|
103
|
+
valid: boolean;
|
|
104
|
+
repairs: string[];
|
|
105
|
+
errors: string[];
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* A gap identified by static analysis that the LLM should try to resolve.
|
|
109
|
+
* Each gap is a generic question/uncertainty — not project-specific.
|
|
110
|
+
*/
|
|
111
|
+
export interface AnalysisGap {
|
|
112
|
+
/** Category of the gap */
|
|
113
|
+
category: "unresolved_docker_service" | "isolated_component" | "weak_description" | "missing_database" | "unmodeled_external_service" | "unmapped_api_endpoints" | "event_pattern_no_connection" | "ambiguous_component_type" | "unknown_primary_language";
|
|
114
|
+
/** Human-readable description of the gap */
|
|
115
|
+
description: string;
|
|
116
|
+
/** IDs of related components (if any) */
|
|
117
|
+
relatedComponentIds?: string[];
|
|
118
|
+
/** Raw context data the LLM can use to resolve this gap */
|
|
119
|
+
context?: Record<string, unknown>;
|
|
120
|
+
}
|
|
121
|
+
export interface StaticAnalysisResult {
|
|
122
|
+
structure: StructureResult;
|
|
123
|
+
docs: DocResult;
|
|
124
|
+
components: ComponentResult;
|
|
125
|
+
infra: InfraResult;
|
|
126
|
+
events: EventResult;
|
|
127
|
+
envs: EnvResult;
|
|
128
|
+
connections: ConnectionResult;
|
|
129
|
+
validation: ValidationResult;
|
|
130
|
+
/** Gaps the static analysis couldn't resolve — passed to LLM for resolution */
|
|
131
|
+
gaps: AnalysisGap[];
|
|
132
|
+
}
|
|
133
|
+
export interface TreeEntry {
|
|
134
|
+
path: string;
|
|
135
|
+
type: "file" | "directory";
|
|
136
|
+
children?: TreeEntry[];
|
|
137
|
+
}
|
|
138
|
+
export interface FileTreeResult {
|
|
139
|
+
tree: TreeEntry[];
|
|
140
|
+
totalFiles: number;
|
|
141
|
+
totalDirs: number;
|
|
142
|
+
}
|
|
143
|
+
export interface FileSample {
|
|
144
|
+
path: string;
|
|
145
|
+
excerpt: string;
|
|
146
|
+
category: "entry-point" | "config" | "route-file" | "model-file";
|
|
147
|
+
}
|
|
148
|
+
export interface ConfigSample {
|
|
149
|
+
path: string;
|
|
150
|
+
content: string;
|
|
151
|
+
}
|
|
152
|
+
export interface CodeSampleResult {
|
|
153
|
+
samples: FileSample[];
|
|
154
|
+
importMap: Record<string, string[]>;
|
|
155
|
+
configFiles: ConfigSample[];
|
|
156
|
+
}
|
|
157
|
+
export interface StaticContext {
|
|
158
|
+
structure: StructureResult;
|
|
159
|
+
docs: DocResult;
|
|
160
|
+
infra: InfraResult;
|
|
161
|
+
events: EventResult;
|
|
162
|
+
envs: EnvResult;
|
|
163
|
+
fileTree: FileTreeResult;
|
|
164
|
+
codeSamples: CodeSampleResult;
|
|
165
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { GrepResult, DirEntry } from "../runtime/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Wraps LocalFSBackend with safe-read helpers for static scanners.
|
|
4
|
+
*/
|
|
5
|
+
export declare class StaticToolkit {
|
|
6
|
+
private fs;
|
|
7
|
+
constructor(projectRoot: string);
|
|
8
|
+
readFileSafe(path: string): Promise<string | null>;
|
|
9
|
+
globFiles(pattern: string, cwd?: string): Promise<string[]>;
|
|
10
|
+
grepFiles(pattern: string, searchPath?: string): Promise<GrepResult[]>;
|
|
11
|
+
listDir(dirPath: string): Promise<DirEntry[]>;
|
|
12
|
+
readJSON(path: string): Promise<Record<string, unknown> | null>;
|
|
13
|
+
readYAML(path: string): Promise<unknown>;
|
|
14
|
+
readYAMLAll(path: string): Promise<unknown[]>;
|
|
15
|
+
}
|
|
16
|
+
export declare function slugify(name: string | undefined): string | undefined;
|
|
17
|
+
export declare function mapComponentType(type: string | undefined): string;
|
|
18
|
+
/**
|
|
19
|
+
* Assign a layer based on component type.
|
|
20
|
+
*/
|
|
21
|
+
export declare function assignLayer(type: string): string;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// Static Analysis — Shared Utilities
|
|
2
|
+
import yaml from "js-yaml";
|
|
3
|
+
import { LocalFSBackend } from "../tools/local-fs.js";
|
|
4
|
+
/**
|
|
5
|
+
* Wraps LocalFSBackend with safe-read helpers for static scanners.
|
|
6
|
+
*/
|
|
7
|
+
export class StaticToolkit {
|
|
8
|
+
fs;
|
|
9
|
+
constructor(projectRoot) {
|
|
10
|
+
this.fs = new LocalFSBackend(projectRoot);
|
|
11
|
+
}
|
|
12
|
+
async readFileSafe(path) {
|
|
13
|
+
try {
|
|
14
|
+
return await this.fs.readFile(path);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async globFiles(pattern, cwd) {
|
|
21
|
+
try {
|
|
22
|
+
// Expand brace patterns like *.{yml,yaml} → [*.yml, *.yaml]
|
|
23
|
+
const patterns = expandBraces(pattern);
|
|
24
|
+
if (patterns.length === 1) {
|
|
25
|
+
return await this.fs.glob(patterns[0], cwd);
|
|
26
|
+
}
|
|
27
|
+
const resultSets = await Promise.all(patterns.map((p) => this.fs.glob(p, cwd).catch(() => [])));
|
|
28
|
+
// Deduplicate and sort
|
|
29
|
+
return [...new Set(resultSets.flat())].sort();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async grepFiles(pattern, searchPath) {
|
|
36
|
+
try {
|
|
37
|
+
// Work around LocalFSBackend.grep glob bug with cwd:
|
|
38
|
+
// grep from root and filter by path prefix
|
|
39
|
+
const results = await this.fs.grep(pattern);
|
|
40
|
+
if (!searchPath)
|
|
41
|
+
return results;
|
|
42
|
+
const prefix = searchPath.endsWith("/") ? searchPath : `${searchPath}/`;
|
|
43
|
+
return results.filter((r) => r.file.startsWith(prefix));
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async listDir(dirPath) {
|
|
50
|
+
try {
|
|
51
|
+
return await this.fs.listDir(dirPath);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async readJSON(path) {
|
|
58
|
+
const content = await this.readFileSafe(path);
|
|
59
|
+
if (!content)
|
|
60
|
+
return null;
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(content);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async readYAML(path) {
|
|
69
|
+
const content = await this.readFileSafe(path);
|
|
70
|
+
if (!content)
|
|
71
|
+
return null;
|
|
72
|
+
try {
|
|
73
|
+
return yaml.load(content);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async readYAMLAll(path) {
|
|
80
|
+
const content = await this.readFileSafe(path);
|
|
81
|
+
if (!content)
|
|
82
|
+
return [];
|
|
83
|
+
try {
|
|
84
|
+
return yaml.loadAll(content);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export function slugify(name) {
|
|
92
|
+
if (!name)
|
|
93
|
+
return undefined;
|
|
94
|
+
return name
|
|
95
|
+
.toLowerCase()
|
|
96
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
97
|
+
.replace(/^-|-$/g, "");
|
|
98
|
+
}
|
|
99
|
+
export function mapComponentType(type) {
|
|
100
|
+
if (!type)
|
|
101
|
+
return "service";
|
|
102
|
+
const t = type.toLowerCase();
|
|
103
|
+
if (t.includes("frontend") || t.includes("ui") || t.includes("web"))
|
|
104
|
+
return "frontend";
|
|
105
|
+
if (t.includes("api") || t.includes("gateway"))
|
|
106
|
+
return "api";
|
|
107
|
+
if (t.includes("database") || t.includes("db"))
|
|
108
|
+
return "database";
|
|
109
|
+
if (t.includes("worker") || t.includes("job") || t.includes("queue"))
|
|
110
|
+
return "worker";
|
|
111
|
+
if (t.includes("library") || t.includes("lib") || t.includes("package"))
|
|
112
|
+
return "library";
|
|
113
|
+
if (t.includes("cli") || t.includes("command"))
|
|
114
|
+
return "service";
|
|
115
|
+
if (t.includes("cache"))
|
|
116
|
+
return "cache";
|
|
117
|
+
return "service";
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Assign a layer based on component type.
|
|
121
|
+
*/
|
|
122
|
+
export function assignLayer(type) {
|
|
123
|
+
switch (type) {
|
|
124
|
+
case "frontend":
|
|
125
|
+
case "cli":
|
|
126
|
+
case "gateway":
|
|
127
|
+
return "presentation";
|
|
128
|
+
case "database":
|
|
129
|
+
return "data";
|
|
130
|
+
case "external":
|
|
131
|
+
return "external";
|
|
132
|
+
default:
|
|
133
|
+
return "application";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Expand simple brace patterns: "*.{a,b}" → ["*.a", "*.b"]
|
|
138
|
+
* Handles one level of braces only (sufficient for our glob patterns).
|
|
139
|
+
*/
|
|
140
|
+
function expandBraces(pattern) {
|
|
141
|
+
const match = pattern.match(/^(.*)\{([^}]+)\}(.*)$/);
|
|
142
|
+
if (!match)
|
|
143
|
+
return [pattern];
|
|
144
|
+
const [, prefix, alternatives, suffix] = match;
|
|
145
|
+
return alternatives.split(",").map((alt) => `${prefix}${alt}${suffix}`);
|
|
146
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Static Analysis — Validator
|
|
2
|
+
// Referential integrity checks, dedup, auto-repair
|
|
3
|
+
export function validateAnalysis(analysis) {
|
|
4
|
+
const result = {
|
|
5
|
+
valid: true,
|
|
6
|
+
repairs: [],
|
|
7
|
+
errors: [],
|
|
8
|
+
};
|
|
9
|
+
const componentIds = new Set(analysis.components.components.map((c) => c.id));
|
|
10
|
+
// Check: at least 1 component
|
|
11
|
+
if (componentIds.size === 0) {
|
|
12
|
+
result.errors.push("No components detected");
|
|
13
|
+
result.valid = false;
|
|
14
|
+
}
|
|
15
|
+
// Check: no duplicate component IDs
|
|
16
|
+
const idCounts = new Map();
|
|
17
|
+
for (const c of analysis.components.components) {
|
|
18
|
+
idCounts.set(c.id, (idCounts.get(c.id) ?? 0) + 1);
|
|
19
|
+
}
|
|
20
|
+
for (const [id, count] of idCounts) {
|
|
21
|
+
if (count > 1) {
|
|
22
|
+
result.repairs.push(`Deduplicated component ID: ${id} (appeared ${count}x)`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// Dedup components
|
|
26
|
+
const seenIds = new Set();
|
|
27
|
+
analysis.components.components = analysis.components.components.filter((c) => {
|
|
28
|
+
if (seenIds.has(c.id))
|
|
29
|
+
return false;
|
|
30
|
+
seenIds.add(c.id);
|
|
31
|
+
return true;
|
|
32
|
+
});
|
|
33
|
+
// Check: every connection from/to references existing component ID
|
|
34
|
+
const validConnections = [];
|
|
35
|
+
for (const conn of analysis.connections.connections) {
|
|
36
|
+
if (!componentIds.has(conn.from)) {
|
|
37
|
+
result.repairs.push(`Removed connection: unknown source "${conn.from}"`);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (!componentIds.has(conn.to)) {
|
|
41
|
+
result.repairs.push(`Removed connection: unknown target "${conn.to}"`);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (conn.from === conn.to) {
|
|
45
|
+
result.repairs.push(`Removed self-connection: ${conn.from}`);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
validConnections.push(conn);
|
|
49
|
+
}
|
|
50
|
+
// Dedup connections
|
|
51
|
+
const connSeen = new Set();
|
|
52
|
+
analysis.connections.connections = validConnections.filter((c) => {
|
|
53
|
+
const key = `${c.from}::${c.to}::${c.type}`;
|
|
54
|
+
if (connSeen.has(key)) {
|
|
55
|
+
result.repairs.push(`Deduplicated connection: ${key}`);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
connSeen.add(key);
|
|
59
|
+
return true;
|
|
60
|
+
});
|
|
61
|
+
// Assign stable slugified IDs to any components missing them
|
|
62
|
+
for (const comp of analysis.components.components) {
|
|
63
|
+
if (!comp.id) {
|
|
64
|
+
comp.id = comp.name
|
|
65
|
+
.toLowerCase()
|
|
66
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
67
|
+
.replace(/^-|-$/g, "");
|
|
68
|
+
result.repairs.push(`Assigned ID to component: ${comp.name} → ${comp.id}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (result.repairs.length > 0 || result.errors.length > 0) {
|
|
72
|
+
result.valid = result.errors.length === 0;
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Tool Backend
|
|
3
|
+
*
|
|
4
|
+
* When archbyte runs inside Claude Code (via /archbyte-analyze skill),
|
|
5
|
+
* this backend bridges to Claude Code's native tools (Glob, Grep, Read)
|
|
6
|
+
* instead of using the local filesystem directly.
|
|
7
|
+
*
|
|
8
|
+
* This gives the best analysis quality — Claude's tools are optimized
|
|
9
|
+
* for code understanding and handle large repos efficiently.
|
|
10
|
+
*/
|
|
11
|
+
import type { ToolBackend, GrepResult, DirEntry } from "../runtime/types.js";
|
|
12
|
+
/**
|
|
13
|
+
* ClaudeCodeBackend — uses the same local FS operations as LocalFSBackend
|
|
14
|
+
* but is designed to be instantiated when running inside Claude Code.
|
|
15
|
+
*
|
|
16
|
+
* In practice, when archbyte is invoked via a Claude Code skill,
|
|
17
|
+
* Claude Code's Task agents run the archbyte agent pipeline.
|
|
18
|
+
* The agents use tool_use calls which are executed by the orchestrator
|
|
19
|
+
* against this backend — which reads the local filesystem that
|
|
20
|
+
* Claude Code also has access to.
|
|
21
|
+
*
|
|
22
|
+
* The key difference from LocalFSBackend:
|
|
23
|
+
* - Optimized for the files Claude Code has already discovered
|
|
24
|
+
* - Can accept a pre-scanned file list to avoid redundant discovery
|
|
25
|
+
* - Respects .gitignore and Claude Code's file filtering
|
|
26
|
+
*/
|
|
27
|
+
export declare class ClaudeCodeBackend implements ToolBackend {
|
|
28
|
+
private root;
|
|
29
|
+
private preScannedFiles?;
|
|
30
|
+
constructor(projectRoot: string, preScannedFiles?: string[]);
|
|
31
|
+
readFile(filePath: string): Promise<string>;
|
|
32
|
+
glob(pattern: string): Promise<string[]>;
|
|
33
|
+
grep(pattern: string, searchPath?: string): Promise<GrepResult[]>;
|
|
34
|
+
listDir(dirPath: string): Promise<DirEntry[]>;
|
|
35
|
+
private resolvePath;
|
|
36
|
+
private walk;
|
|
37
|
+
private globToRegex;
|
|
38
|
+
}
|