archbyte 0.3.5 → 0.4.1
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 +42 -0
- package/bin/archbyte.js +26 -25
- package/dist/agents/pipeline/merger.d.ts +2 -2
- package/dist/agents/pipeline/merger.js +165 -35
- package/dist/agents/pipeline/types.d.ts +29 -1
- package/dist/agents/pipeline/types.js +0 -1
- package/dist/agents/providers/claude-sdk.d.ts +7 -0
- package/dist/agents/providers/claude-sdk.js +83 -0
- package/dist/agents/providers/router.d.ts +5 -0
- package/dist/agents/providers/router.js +23 -1
- package/dist/agents/runtime/types.d.ts +6 -2
- package/dist/agents/runtime/types.js +6 -1
- package/dist/agents/static/component-detector.js +35 -3
- package/dist/agents/static/connection-mapper.d.ts +1 -1
- package/dist/agents/static/connection-mapper.js +74 -1
- package/dist/agents/static/index.js +5 -2
- package/dist/agents/static/types.d.ts +26 -0
- package/dist/cli/analyze.js +65 -18
- package/dist/cli/arch-diff.d.ts +38 -0
- package/dist/cli/arch-diff.js +61 -0
- package/dist/cli/auth.d.ts +8 -2
- package/dist/cli/auth.js +241 -31
- package/dist/cli/config.js +31 -5
- package/dist/cli/export.js +64 -2
- package/dist/cli/patrol.d.ts +5 -3
- package/dist/cli/patrol.js +417 -65
- package/dist/cli/setup.js +76 -8
- package/dist/cli/shared.d.ts +11 -0
- package/dist/cli/shared.js +61 -0
- package/dist/cli/ui.d.ts +9 -0
- package/dist/cli/ui.js +59 -5
- package/dist/cli/validate.d.ts +0 -1
- package/dist/cli/validate.js +0 -16
- package/dist/server/src/index.js +593 -19
- package/package.json +4 -1
- package/templates/archbyte.yaml +8 -0
- package/ui/dist/assets/index-DDCNauh7.css +1 -0
- package/ui/dist/assets/index-DO4t5Xu1.js +72 -0
- package/ui/dist/index.html +2 -2
- package/dist/cli/mcp-server.d.ts +0 -1
- package/dist/cli/mcp-server.js +0 -443
- package/dist/cli/mcp.d.ts +0 -1
- package/dist/cli/mcp.js +0 -98
- package/ui/dist/assets/index-0_XpUUZQ.css +0 -1
- package/ui/dist/assets/index-BTo0zV5E.js +0 -70
package/README.md
CHANGED
|
@@ -76,6 +76,48 @@ Run `/archbyte-help` in Claude Code to see all commands.
|
|
|
76
76
|
|
|
77
77
|
## CLI Commands
|
|
78
78
|
|
|
79
|
+
### `archbyte login`
|
|
80
|
+
|
|
81
|
+
Sign in or create a free account. Shows an interactive provider picker (GitHub, Google, Email & Password).
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
archbyte login # interactive provider picker
|
|
85
|
+
archbyte login --github # sign in with GitHub
|
|
86
|
+
archbyte login --google # sign in with Google
|
|
87
|
+
archbyte login --email # sign in with email and password
|
|
88
|
+
archbyte login --token JWT # login with a pre-existing JWT token
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Multiple accounts are supported. If already logged in, you'll be prompted to add a different account.
|
|
92
|
+
|
|
93
|
+
### `archbyte logout`
|
|
94
|
+
|
|
95
|
+
Sign out of ArchByte.
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
archbyte logout # logout active account
|
|
99
|
+
archbyte logout user@co.com # logout specific account
|
|
100
|
+
archbyte logout --all # logout all accounts
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### `archbyte accounts`
|
|
104
|
+
|
|
105
|
+
List and manage logged-in accounts.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
archbyte accounts # list all accounts
|
|
109
|
+
archbyte accounts switch # interactive account switcher
|
|
110
|
+
archbyte accounts switch user@co.com # switch to specific account
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### `archbyte status`
|
|
114
|
+
|
|
115
|
+
Show account status, tier, and usage.
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
archbyte status
|
|
119
|
+
```
|
|
120
|
+
|
|
79
121
|
### `archbyte init`
|
|
80
122
|
|
|
81
123
|
Scaffold an `archbyte.yaml` config and `.archbyte/` directory.
|
package/bin/archbyte.js
CHANGED
|
@@ -18,7 +18,7 @@ import { handlePatrol } from '../dist/cli/patrol.js';
|
|
|
18
18
|
import { handleWorkflow } from '../dist/cli/workflow.js';
|
|
19
19
|
import { handleAnalyze } from '../dist/cli/analyze.js';
|
|
20
20
|
import { handleConfig } from '../dist/cli/config.js';
|
|
21
|
-
import { handleLogin, handleLoginWithToken, handleLogout, handleStatus } from '../dist/cli/auth.js';
|
|
21
|
+
import { handleLogin, handleLoginWithToken, handleLogout, handleStatus, handleAccounts, handleAccountSwitch } from '../dist/cli/auth.js';
|
|
22
22
|
import { handleRun } from '../dist/cli/run.js';
|
|
23
23
|
import { handleSetup } from '../dist/cli/setup.js';
|
|
24
24
|
import { handleVersion, handleUpdate } from '../dist/cli/version.js';
|
|
@@ -51,11 +51,13 @@ program
|
|
|
51
51
|
.option('--token <jwt>', 'Login with a pre-existing JWT token')
|
|
52
52
|
.option('--github', 'Sign in with GitHub')
|
|
53
53
|
.option('--google', 'Sign in with Google')
|
|
54
|
+
.option('--email', 'Sign in with email and password')
|
|
54
55
|
.action(async (options) => {
|
|
55
56
|
if (options.token) {
|
|
56
57
|
await handleLoginWithToken(options.token);
|
|
57
58
|
} else {
|
|
58
|
-
|
|
59
|
+
// Explicit provider flag, or null for interactive picker
|
|
60
|
+
const provider = options.github ? 'github' : options.google ? 'google' : options.email ? 'email' : undefined;
|
|
59
61
|
await handleLogin(provider);
|
|
60
62
|
}
|
|
61
63
|
});
|
|
@@ -63,8 +65,10 @@ program
|
|
|
63
65
|
program
|
|
64
66
|
.command('logout')
|
|
65
67
|
.description('Sign out of ArchByte')
|
|
66
|
-
.
|
|
67
|
-
|
|
68
|
+
.argument('[email]', 'Logout a specific account by email')
|
|
69
|
+
.option('--all', 'Logout all accounts')
|
|
70
|
+
.action(async (email, options) => {
|
|
71
|
+
await handleLogout({ email, all: options.all });
|
|
68
72
|
});
|
|
69
73
|
|
|
70
74
|
program
|
|
@@ -147,7 +151,6 @@ program
|
|
|
147
151
|
.option('-d, --diagram <path>', 'Path to architecture JSON (default: .archbyte/architecture.json)')
|
|
148
152
|
.option('-c, --config <path>', 'Path to archbyte.yaml config')
|
|
149
153
|
.option('--ci', 'Machine-readable JSON output for CI pipelines')
|
|
150
|
-
.option('-w, --watch', 'Watch for changes and re-validate')
|
|
151
154
|
.action(async (options) => {
|
|
152
155
|
await requireLicense('analyze');
|
|
153
156
|
await handleValidate(options);
|
|
@@ -167,7 +170,7 @@ program
|
|
|
167
170
|
.command('export')
|
|
168
171
|
.description('Export architecture to various formats')
|
|
169
172
|
.option('-d, --diagram <path>', 'Path to architecture JSON (default: .archbyte/architecture.json)')
|
|
170
|
-
.option('-f, --format <format>', 'Output format: mermaid, markdown, json, plantuml, dot (default: mermaid)')
|
|
173
|
+
.option('-f, --format <format>', 'Output format: mermaid, markdown, json, plantuml, dot, html [Pro] (default: mermaid)')
|
|
171
174
|
.option('-o, --output <path>', 'Write to file instead of stdout')
|
|
172
175
|
.action(async (options) => {
|
|
173
176
|
await handleExport(options);
|
|
@@ -182,7 +185,8 @@ program
|
|
|
182
185
|
.option('-c, --config <path>', 'Path to archbyte.yaml config')
|
|
183
186
|
.option('-i, --interval <duration>', 'Patrol interval: 30s, 5m, 1h (default: 5m)')
|
|
184
187
|
.option('--on-violation <action>', 'Action on new violations: log, json (default: log)')
|
|
185
|
-
.option('--
|
|
188
|
+
.option('--once', 'Run a single patrol cycle then exit')
|
|
189
|
+
.option('-w, --watch', 'Watch source files for changes instead of polling on interval')
|
|
186
190
|
.option('--history', 'Show patrol history dashboard')
|
|
187
191
|
.action(async (options) => {
|
|
188
192
|
await requireLicense('analyze');
|
|
@@ -212,6 +216,21 @@ program
|
|
|
212
216
|
await handleStatus();
|
|
213
217
|
});
|
|
214
218
|
|
|
219
|
+
const accountsCmd = program
|
|
220
|
+
.command('accounts')
|
|
221
|
+
.description('List and manage logged-in accounts')
|
|
222
|
+
.action(async () => {
|
|
223
|
+
await handleAccounts();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
accountsCmd
|
|
227
|
+
.command('switch')
|
|
228
|
+
.description('Switch the active account')
|
|
229
|
+
.argument('[email]', 'Switch to a specific account by email')
|
|
230
|
+
.action(async (email) => {
|
|
231
|
+
await handleAccountSwitch(email);
|
|
232
|
+
});
|
|
233
|
+
|
|
215
234
|
program
|
|
216
235
|
.command('config')
|
|
217
236
|
.description('Manage ArchByte configuration (provider, API key)')
|
|
@@ -237,24 +256,6 @@ program
|
|
|
237
256
|
await handleUpdate();
|
|
238
257
|
});
|
|
239
258
|
|
|
240
|
-
// — MCP server —
|
|
241
|
-
|
|
242
|
-
const mcpCmd = program
|
|
243
|
-
.command('mcp')
|
|
244
|
-
.description('Start MCP server for AI coding tools (Claude Code, Codex)')
|
|
245
|
-
.action(async () => {
|
|
246
|
-
const { startMcpServer } = await import('../dist/cli/mcp-server.js');
|
|
247
|
-
await startMcpServer();
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
mcpCmd
|
|
251
|
-
.command('install')
|
|
252
|
-
.description('Auto-configure Claude Code and/or Codex CLI')
|
|
253
|
-
.action(async () => {
|
|
254
|
-
const { handleMcpInstall } = await import('../dist/cli/mcp.js');
|
|
255
|
-
await handleMcpInstall();
|
|
256
|
-
});
|
|
257
|
-
|
|
258
259
|
// Default: show help
|
|
259
260
|
program
|
|
260
261
|
.action(() => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { StaticAnalysisResult, StaticContext } from "../static/types.js";
|
|
2
|
-
import type { ComponentIdentifierOutput, ServiceDescriberOutput, FlowDetectorOutput, ConnectionMapperOutput, ValidatorOutput, IncrementalContext } from "./types.js";
|
|
2
|
+
import type { ComponentIdentifierOutput, ServiceDescriberOutput, FlowDetectorOutput, ConnectionMapperOutput, ValidatorOutput, IncrementalContext, ArchitectureEnricherOutput } from "./types.js";
|
|
3
3
|
/**
|
|
4
4
|
* Merge all pipeline agent outputs into a StaticAnalysisResult
|
|
5
5
|
* compatible with the existing buildAnalysisFromStatic() in cli/analyze.ts.
|
|
6
6
|
*/
|
|
7
|
-
export declare function mergeAgentOutputs(ctx: StaticContext, componentId: ComponentIdentifierOutput | null, serviceDesc: ServiceDescriberOutput | null, flowDet: FlowDetectorOutput | null, connMap: ConnectionMapperOutput | null, validatorOut: ValidatorOutput | null, incrementalContext?: IncrementalContext): StaticAnalysisResult;
|
|
7
|
+
export declare function mergeAgentOutputs(ctx: StaticContext, componentId: ComponentIdentifierOutput | null, serviceDesc: ServiceDescriberOutput | null, flowDet: FlowDetectorOutput | null, connMap: ConnectionMapperOutput | null, validatorOut: ValidatorOutput | null, incrementalContext?: IncrementalContext, enricherOut?: ArchitectureEnricherOutput | null): StaticAnalysisResult;
|
|
@@ -1,21 +1,112 @@
|
|
|
1
1
|
// Pipeline — Merger
|
|
2
2
|
// Assembles all agent outputs into a StaticAnalysisResult
|
|
3
|
+
function sanitize(s) {
|
|
4
|
+
if (!s)
|
|
5
|
+
return s;
|
|
6
|
+
return s.replace(/\u2014/g, "-").replace(/\u2013/g, "-").replace(/\u2018|\u2019/g, "'").replace(/\u201C|\u201D/g, '"');
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Build a set of "evidence tokens" from the static context — things that concretely
|
|
10
|
+
* exist in the codebase (dependencies, env vars, docker images/services).
|
|
11
|
+
* Used to gate LLM-generated databases/external services against hallucination.
|
|
12
|
+
*/
|
|
13
|
+
function buildEvidenceTokens(ctx) {
|
|
14
|
+
const tokens = new Set();
|
|
15
|
+
// Package dependencies from import map (codeSamples.importMap: file → imported modules)
|
|
16
|
+
for (const imports of Object.values(ctx.codeSamples.importMap)) {
|
|
17
|
+
for (const imp of imports) {
|
|
18
|
+
tokens.add(imp.toLowerCase());
|
|
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
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Config files may contain dependency info (package.json deps etc.)
|
|
29
|
+
for (const cfg of ctx.codeSamples.configFiles) {
|
|
30
|
+
if (cfg.path.endsWith("package.json")) {
|
|
31
|
+
try {
|
|
32
|
+
const pkg = JSON.parse(cfg.content);
|
|
33
|
+
for (const dep of Object.keys({ ...pkg.dependencies, ...pkg.devDependencies })) {
|
|
34
|
+
tokens.add(dep.toLowerCase());
|
|
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
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch { /* ignore parse errors */ }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Environment variable names
|
|
47
|
+
for (const env of ctx.envs.environments) {
|
|
48
|
+
for (const v of env.variables) {
|
|
49
|
+
tokens.add(v.toLowerCase());
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Docker compose service names and images
|
|
53
|
+
for (const svc of ctx.infra.docker.services) {
|
|
54
|
+
tokens.add(svc.name.toLowerCase());
|
|
55
|
+
if (svc.image)
|
|
56
|
+
tokens.add(svc.image.toLowerCase().split(":")[0]);
|
|
57
|
+
}
|
|
58
|
+
// Cloud services detected by infra scanner
|
|
59
|
+
for (const s of ctx.infra.cloud.services) {
|
|
60
|
+
tokens.add(s.toLowerCase());
|
|
61
|
+
}
|
|
62
|
+
// External dependencies mentioned in docs
|
|
63
|
+
for (const dep of ctx.docs.externalDependencies) {
|
|
64
|
+
tokens.add(dep.toLowerCase());
|
|
65
|
+
}
|
|
66
|
+
return tokens;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Check if a service/database ID and type have concrete evidence in the static context.
|
|
70
|
+
* Uses fuzzy matching: checks if any evidence token contains or is contained by the service keywords.
|
|
71
|
+
*/
|
|
72
|
+
function hasEvidence(id, name, type, evidenceTokens) {
|
|
73
|
+
// Build candidate keywords from the service
|
|
74
|
+
const candidates = [
|
|
75
|
+
id.toLowerCase(),
|
|
76
|
+
name.toLowerCase(),
|
|
77
|
+
type.toLowerCase(),
|
|
78
|
+
// Split hyphenated IDs: "aws-sqs" → ["aws", "sqs"]
|
|
79
|
+
...id.toLowerCase().split("-"),
|
|
80
|
+
].filter(Boolean);
|
|
81
|
+
for (const candidate of candidates) {
|
|
82
|
+
for (const token of evidenceTokens) {
|
|
83
|
+
// Direct match or substring match (in both directions)
|
|
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
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
3
94
|
/**
|
|
4
95
|
* Merge all pipeline agent outputs into a StaticAnalysisResult
|
|
5
96
|
* compatible with the existing buildAnalysisFromStatic() in cli/analyze.ts.
|
|
6
97
|
*/
|
|
7
|
-
export function mergeAgentOutputs(ctx, componentId, serviceDesc, flowDet, connMap, validatorOut, incrementalContext) {
|
|
98
|
+
export function mergeAgentOutputs(ctx, componentId, serviceDesc, flowDet, connMap, validatorOut, incrementalContext, enricherOut) {
|
|
8
99
|
// Start with components from component-identifier
|
|
9
100
|
const components = [];
|
|
10
101
|
if (componentId?.components) {
|
|
11
102
|
for (const c of componentId.components) {
|
|
12
103
|
components.push({
|
|
13
104
|
id: c.id,
|
|
14
|
-
name: c.name,
|
|
105
|
+
name: sanitize(c.name) ?? c.name,
|
|
15
106
|
type: c.type,
|
|
16
107
|
layer: c.layer,
|
|
17
108
|
path: c.path,
|
|
18
|
-
description: c.description,
|
|
109
|
+
description: sanitize(c.description),
|
|
19
110
|
technologies: c.technologies,
|
|
20
111
|
});
|
|
21
112
|
}
|
|
@@ -36,38 +127,44 @@ export function mergeAgentOutputs(ctx, componentId, serviceDesc, flowDet, connMa
|
|
|
36
127
|
}
|
|
37
128
|
}
|
|
38
129
|
const componentIds = new Set(components.map((c) => c.id));
|
|
39
|
-
//
|
|
130
|
+
// Build evidence tokens for hallucination gating
|
|
131
|
+
const evidenceTokens = buildEvidenceTokens(ctx);
|
|
132
|
+
// Add databases from service-describer as components (evidence-gated)
|
|
40
133
|
if (serviceDesc?.databases) {
|
|
41
134
|
for (const db of serviceDesc.databases) {
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
135
|
+
if (componentIds.has(db.id))
|
|
136
|
+
continue;
|
|
137
|
+
if (!hasEvidence(db.id, db.name, db.type, evidenceTokens))
|
|
138
|
+
continue;
|
|
139
|
+
components.push({
|
|
140
|
+
id: db.id,
|
|
141
|
+
name: db.name,
|
|
142
|
+
type: "database",
|
|
143
|
+
layer: "data",
|
|
144
|
+
path: "",
|
|
145
|
+
description: sanitize(db.description),
|
|
146
|
+
technologies: [db.type],
|
|
147
|
+
});
|
|
148
|
+
componentIds.add(db.id);
|
|
54
149
|
}
|
|
55
150
|
}
|
|
56
|
-
// Add external services from service-describer as components
|
|
151
|
+
// Add external services from service-describer as components (evidence-gated)
|
|
57
152
|
if (serviceDesc?.externalServices) {
|
|
58
153
|
for (const svc of serviceDesc.externalServices) {
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
154
|
+
if (componentIds.has(svc.id))
|
|
155
|
+
continue;
|
|
156
|
+
if (!hasEvidence(svc.id, svc.name, svc.type, evidenceTokens))
|
|
157
|
+
continue;
|
|
158
|
+
components.push({
|
|
159
|
+
id: svc.id,
|
|
160
|
+
name: sanitize(svc.name) ?? svc.name,
|
|
161
|
+
type: "service",
|
|
162
|
+
layer: "external",
|
|
163
|
+
path: "",
|
|
164
|
+
description: sanitize(svc.description),
|
|
165
|
+
technologies: [svc.type],
|
|
166
|
+
});
|
|
167
|
+
componentIds.add(svc.id);
|
|
71
168
|
}
|
|
72
169
|
}
|
|
73
170
|
// Incremental fallback: if service-describer returned nothing, restore from spec
|
|
@@ -96,7 +193,7 @@ export function mergeAgentOutputs(ctx, componentId, serviceDesc, flowDet, connMa
|
|
|
96
193
|
// Apply validator description improvements
|
|
97
194
|
if (validatorOut?.componentDescriptions) {
|
|
98
195
|
for (const comp of components) {
|
|
99
|
-
const better = validatorOut.componentDescriptions[comp.id];
|
|
196
|
+
const better = sanitize(validatorOut.componentDescriptions[comp.id]);
|
|
100
197
|
if (better && better.length > (comp.description?.length ?? 0)) {
|
|
101
198
|
comp.description = better;
|
|
102
199
|
}
|
|
@@ -118,7 +215,7 @@ export function mergeAgentOutputs(ctx, componentId, serviceDesc, flowDet, connMa
|
|
|
118
215
|
from: c.from,
|
|
119
216
|
to: c.to,
|
|
120
217
|
type: c.type,
|
|
121
|
-
description: c.description,
|
|
218
|
+
description: sanitize(c.description),
|
|
122
219
|
confidence: 80,
|
|
123
220
|
async: c.async,
|
|
124
221
|
});
|
|
@@ -198,7 +295,7 @@ export function mergeAgentOutputs(ctx, componentId, serviceDesc, flowDet, connMa
|
|
|
198
295
|
from: c.from,
|
|
199
296
|
to: c.to,
|
|
200
297
|
type: c.type,
|
|
201
|
-
description: c.description,
|
|
298
|
+
description: sanitize(c.description),
|
|
202
299
|
confidence: 65,
|
|
203
300
|
async: c.async,
|
|
204
301
|
});
|
|
@@ -247,7 +344,38 @@ export function mergeAgentOutputs(ctx, componentId, serviceDesc, flowDet, connMa
|
|
|
247
344
|
// Override docs from service-describer
|
|
248
345
|
const docs = { ...ctx.docs };
|
|
249
346
|
if (serviceDesc?.projectDescription && serviceDesc.projectDescription.length > (docs.projectDescription?.length ?? 0)) {
|
|
250
|
-
docs.projectDescription = serviceDesc.projectDescription;
|
|
347
|
+
docs.projectDescription = sanitize(serviceDesc.projectDescription);
|
|
348
|
+
}
|
|
349
|
+
// Apply enricher overlays (non-destructive)
|
|
350
|
+
if (enricherOut) {
|
|
351
|
+
// Overlay coupling weight on connections
|
|
352
|
+
if (enricherOut.connectionEnrichments?.length) {
|
|
353
|
+
for (const ce of enricherOut.connectionEnrichments) {
|
|
354
|
+
const conn = filteredConnections.find((c) => c.from === ce.from && c.to === ce.to && c.type === ce.type);
|
|
355
|
+
if (conn) {
|
|
356
|
+
if (ce.weight != null)
|
|
357
|
+
conn.weight = ce.weight;
|
|
358
|
+
if (ce.verifiedByTool)
|
|
359
|
+
conn.confidence = Math.min(100, (conn.confidence ?? 80) + 10);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Overlay component enrichments
|
|
364
|
+
if (enricherOut.componentEnrichments?.length) {
|
|
365
|
+
for (const ce of enricherOut.componentEnrichments) {
|
|
366
|
+
const comp = components.find((c) => c.id === ce.id);
|
|
367
|
+
if (comp) {
|
|
368
|
+
if (ce.interfaceSurface != null)
|
|
369
|
+
comp.interfaceSurface = ce.interfaceSurface;
|
|
370
|
+
if (ce.hasBoundary != null)
|
|
371
|
+
comp.hasBoundary = ce.hasBoundary;
|
|
372
|
+
if (ce.boundaryType)
|
|
373
|
+
comp.boundaryType = ce.boundaryType;
|
|
374
|
+
if (ce.publicExports?.length)
|
|
375
|
+
comp.publicExports = ce.publicExports;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
251
379
|
}
|
|
252
380
|
return {
|
|
253
381
|
structure,
|
|
@@ -259,13 +387,15 @@ export function mergeAgentOutputs(ctx, componentId, serviceDesc, flowDet, connMa
|
|
|
259
387
|
connections: {
|
|
260
388
|
connections: filteredConnections,
|
|
261
389
|
flows: flows.map((f) => ({
|
|
262
|
-
name: f.name,
|
|
263
|
-
description: f.description,
|
|
390
|
+
name: sanitize(f.name) ?? f.name,
|
|
391
|
+
description: sanitize(f.description),
|
|
264
392
|
category: f.category,
|
|
265
393
|
steps: f.steps,
|
|
266
394
|
})),
|
|
267
395
|
},
|
|
268
396
|
validation: { valid: true, repairs: [], errors: [] },
|
|
269
397
|
gaps: [],
|
|
398
|
+
enrichmentInsights: enricherOut?.insights,
|
|
399
|
+
flowVerifications: enricherOut?.flowVerifications,
|
|
270
400
|
};
|
|
271
401
|
}
|
|
@@ -5,7 +5,11 @@ export interface PipelineAgent {
|
|
|
5
5
|
name: string;
|
|
6
6
|
modelTier: ModelTier;
|
|
7
7
|
phase: "parallel" | "sequential";
|
|
8
|
-
|
|
8
|
+
/** Tool names this agent can use: ["read_file", "grep", "glob"] */
|
|
9
|
+
tools?: string[];
|
|
10
|
+
/** Max tool loop iterations (default 1 = no tools) */
|
|
11
|
+
maxTurns?: number;
|
|
12
|
+
buildPrompt(ctx: StaticContext, priorResults?: Record<string, unknown>, toolsAvailable?: boolean): {
|
|
9
13
|
system: string;
|
|
10
14
|
user: string;
|
|
11
15
|
};
|
|
@@ -135,6 +139,30 @@ export interface IncrementalContext {
|
|
|
135
139
|
neighborComponents: string[];
|
|
136
140
|
hasUnmappedFiles: boolean;
|
|
137
141
|
}
|
|
142
|
+
export interface ArchitectureEnricherOutput {
|
|
143
|
+
connectionEnrichments: Array<{
|
|
144
|
+
from: string;
|
|
145
|
+
to: string;
|
|
146
|
+
type: string;
|
|
147
|
+
weight?: number;
|
|
148
|
+
verifiedByTool?: boolean;
|
|
149
|
+
}>;
|
|
150
|
+
componentEnrichments: Array<{
|
|
151
|
+
id: string;
|
|
152
|
+
interfaceSurface?: number;
|
|
153
|
+
hasBoundary?: boolean;
|
|
154
|
+
boundaryType?: string;
|
|
155
|
+
publicExports?: string[];
|
|
156
|
+
}>;
|
|
157
|
+
flowVerifications: Array<{
|
|
158
|
+
flowName: string;
|
|
159
|
+
verified: boolean;
|
|
160
|
+
stepsVerified: number;
|
|
161
|
+
stepsTotal: number;
|
|
162
|
+
issues?: string[];
|
|
163
|
+
}>;
|
|
164
|
+
insights: string[];
|
|
165
|
+
}
|
|
138
166
|
export interface PipelineAgentResult {
|
|
139
167
|
agentId: string;
|
|
140
168
|
data: unknown;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { LLMProvider, ChatParams, LLMResponse, LLMChunk } from "../runtime/types.js";
|
|
2
|
+
export declare class ClaudeSdkProvider implements LLMProvider {
|
|
3
|
+
name: "claude-sdk";
|
|
4
|
+
chat(params: ChatParams): Promise<LLMResponse>;
|
|
5
|
+
stream(params: ChatParams): AsyncIterable<LLMChunk>;
|
|
6
|
+
private extractPrompt;
|
|
7
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export class ClaudeSdkProvider {
|
|
2
|
+
name = "claude-sdk";
|
|
3
|
+
async chat(params) {
|
|
4
|
+
const { query } = await import("@anthropic-ai/claude-agent-sdk");
|
|
5
|
+
const prompt = this.extractPrompt(params.messages);
|
|
6
|
+
// Dynamic tool config: empty = text-only, populated = tool-enabled
|
|
7
|
+
const sdkTools = params.sdkTools?.length ? params.sdkTools : [];
|
|
8
|
+
const maxTurns = params.sdkMaxTurns ?? 1;
|
|
9
|
+
const result = query({
|
|
10
|
+
prompt,
|
|
11
|
+
options: {
|
|
12
|
+
systemPrompt: params.system,
|
|
13
|
+
tools: sdkTools,
|
|
14
|
+
maxTurns,
|
|
15
|
+
...(params.model ? { model: params.model } : {}),
|
|
16
|
+
permissionMode: "bypassPermissions",
|
|
17
|
+
allowDangerouslySkipPermissions: true,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
let resultText = "";
|
|
21
|
+
let assistantText = "";
|
|
22
|
+
let usage = { inputTokens: 0, outputTokens: 0 };
|
|
23
|
+
for await (const message of result) {
|
|
24
|
+
if (message.type === "assistant") {
|
|
25
|
+
// Capture text from assistant content blocks as fallback
|
|
26
|
+
const msg = message.message;
|
|
27
|
+
if (msg && "content" in msg && Array.isArray(msg.content)) {
|
|
28
|
+
for (const block of msg.content) {
|
|
29
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
30
|
+
assistantText += block.text;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (message.type === "result") {
|
|
36
|
+
if (message.subtype === "success") {
|
|
37
|
+
resultText = message.result;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
// Error result — log the errors for debugging
|
|
41
|
+
const errors = "errors" in message ? message.errors : [];
|
|
42
|
+
console.error(`[claude-sdk] Result subtype="${message.subtype}" errors=${JSON.stringify(errors)}`);
|
|
43
|
+
}
|
|
44
|
+
if (message.usage) {
|
|
45
|
+
usage = {
|
|
46
|
+
inputTokens: message.usage.input_tokens ?? 0,
|
|
47
|
+
outputTokens: message.usage.output_tokens ?? 0,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Use result text if available, otherwise fall back to assistant message text
|
|
53
|
+
const text = resultText || assistantText;
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: "text", text }],
|
|
56
|
+
stopReason: "end_turn",
|
|
57
|
+
usage,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async *stream(params) {
|
|
61
|
+
// Pipeline agents don't use streaming — delegate to chat()
|
|
62
|
+
const response = await this.chat(params);
|
|
63
|
+
const text = response.content.find((b) => b.type === "text")?.text ?? "";
|
|
64
|
+
if (text) {
|
|
65
|
+
yield { type: "text", text };
|
|
66
|
+
}
|
|
67
|
+
yield { type: "done" };
|
|
68
|
+
}
|
|
69
|
+
extractPrompt(messages) {
|
|
70
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
71
|
+
if (messages[i].role === "user") {
|
|
72
|
+
const content = messages[i].content;
|
|
73
|
+
if (typeof content === "string")
|
|
74
|
+
return content;
|
|
75
|
+
return content
|
|
76
|
+
.filter((b) => b.type === "text")
|
|
77
|
+
.map((b) => b.text ?? "")
|
|
78
|
+
.join("\n");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return "";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { LLMProvider, ArchByteConfig } from "../runtime/types.js";
|
|
2
2
|
export declare function createProvider(config: ArchByteConfig): LLMProvider;
|
|
3
|
+
/**
|
|
4
|
+
* Check if Claude Code CLI is available on PATH.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isClaudeCodeAvailable(): boolean;
|
|
3
7
|
/**
|
|
4
8
|
* Auto-detect provider from environment variables.
|
|
5
9
|
* Checks in order: ARCHBYTE_PROVIDER, then falls back to whichever API key is set.
|
|
10
|
+
* Last resort: Claude Code on PATH → claude-sdk (zero config).
|
|
6
11
|
*/
|
|
7
12
|
export declare function detectConfig(): ArchByteConfig | null;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
1
2
|
import { AnthropicProvider } from "./anthropic.js";
|
|
2
3
|
import { OpenAIProvider } from "./openai.js";
|
|
3
4
|
import { GoogleProvider } from "./google.js";
|
|
5
|
+
import { ClaudeSdkProvider } from "./claude-sdk.js";
|
|
4
6
|
export function createProvider(config) {
|
|
5
7
|
switch (config.provider) {
|
|
6
8
|
case "anthropic":
|
|
@@ -9,13 +11,29 @@ export function createProvider(config) {
|
|
|
9
11
|
return new OpenAIProvider(config.apiKey);
|
|
10
12
|
case "google":
|
|
11
13
|
return new GoogleProvider(config.apiKey);
|
|
14
|
+
case "claude-sdk":
|
|
15
|
+
return new ClaudeSdkProvider();
|
|
12
16
|
default:
|
|
13
17
|
throw new Error(`Unknown provider: ${config.provider}`);
|
|
14
18
|
}
|
|
15
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Check if Claude Code CLI is available on PATH.
|
|
22
|
+
*/
|
|
23
|
+
export function isClaudeCodeAvailable() {
|
|
24
|
+
try {
|
|
25
|
+
const cmd = process.platform === "win32" ? "where claude" : "which claude";
|
|
26
|
+
execSync(cmd, { stdio: "pipe" });
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
16
33
|
/**
|
|
17
34
|
* Auto-detect provider from environment variables.
|
|
18
35
|
* Checks in order: ARCHBYTE_PROVIDER, then falls back to whichever API key is set.
|
|
36
|
+
* Last resort: Claude Code on PATH → claude-sdk (zero config).
|
|
19
37
|
*/
|
|
20
38
|
export function detectConfig() {
|
|
21
39
|
const explicit = process.env.ARCHBYTE_PROVIDER;
|
|
@@ -23,7 +41,11 @@ export function detectConfig() {
|
|
|
23
41
|
if (explicit && apiKey) {
|
|
24
42
|
return { provider: explicit, apiKey };
|
|
25
43
|
}
|
|
26
|
-
//
|
|
44
|
+
// Claude Code on PATH → zero-config, preferred
|
|
45
|
+
if (isClaudeCodeAvailable()) {
|
|
46
|
+
return { provider: "claude-sdk" };
|
|
47
|
+
}
|
|
48
|
+
// Fall back to API key env vars
|
|
27
49
|
if (process.env.ANTHROPIC_API_KEY) {
|
|
28
50
|
return { provider: "anthropic", apiKey: process.env.ANTHROPIC_API_KEY };
|
|
29
51
|
}
|
|
@@ -23,6 +23,10 @@ export interface ChatParams {
|
|
|
23
23
|
messages: Message[];
|
|
24
24
|
tools?: ToolDefinition[];
|
|
25
25
|
maxTokens?: number;
|
|
26
|
+
/** Claude SDK tool names: ["Read", "Grep", "Glob"] */
|
|
27
|
+
sdkTools?: string[];
|
|
28
|
+
/** Claude SDK max agentic turns (default 1) */
|
|
29
|
+
sdkMaxTurns?: number;
|
|
26
30
|
}
|
|
27
31
|
export interface LLMResponse {
|
|
28
32
|
content: ContentBlock[];
|
|
@@ -97,14 +101,14 @@ export interface License {
|
|
|
97
101
|
expiresAt: string;
|
|
98
102
|
isValid: boolean;
|
|
99
103
|
}
|
|
100
|
-
export type ProviderName = "anthropic" | "openai" | "google";
|
|
104
|
+
export type ProviderName = "anthropic" | "openai" | "google" | "claude-sdk";
|
|
101
105
|
export interface ProviderProfile {
|
|
102
106
|
apiKey?: string;
|
|
103
107
|
model?: string;
|
|
104
108
|
}
|
|
105
109
|
export interface ArchByteConfig {
|
|
106
110
|
provider: ProviderName;
|
|
107
|
-
apiKey
|
|
111
|
+
apiKey?: string;
|
|
108
112
|
model?: string;
|
|
109
113
|
modelOverrides?: Partial<Record<ModelTier, string>>;
|
|
110
114
|
profiles?: Record<string, ProviderProfile>;
|