aztrx-cli 0.2.3 → 0.3.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 +24 -13
- package/dist/cli.js +5 -5
- package/dist/core/heal/index.js +5 -4
- package/dist/core/heal/llm.js +15 -38
- package/dist/core/llm.js +121 -0
- package/dist/core/modernize.js +9 -29
- package/dist/core/networkGuard.js +2 -2
- package/dist/core/summarize.js +8 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ aztrx-cli run http://localhost:3000
|
|
|
47
47
|
## Fix it, not just find it
|
|
48
48
|
|
|
49
49
|
The only feature that needs a key. `--fix` hands the crash to an LLM and applies a
|
|
50
|
-
verified patch to your working tree.
|
|
50
|
+
verified patch to your working tree. Anthropic by default — set `ANTHROPIC_API_KEY`:
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
53
|
export ANTHROPIC_API_KEY="your-api-key"
|
|
@@ -55,6 +55,16 @@ npx aztrx-cli run http://localhost:3000 --fix # find → explain → he
|
|
|
55
55
|
npx aztrx-cli run http://localhost:3000 --fix --yes # non-interactive (CI)
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
Or use **any model** — Grok, DeepSeek, Gemini, GPT, Kimi, Mistral, OpenRouter, or a
|
|
59
|
+
local Ollama/vLLM. Point it at any OpenAI-compatible `/v1` endpoint:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
export AZTRX_API_BASE="https://openrouter.ai/api/v1" # or api.openai.com/v1, api.x.ai/v1, ...
|
|
63
|
+
export AZTRX_API_KEY="your-key"
|
|
64
|
+
export AZTRX_MODEL="anthropic/claude-sonnet-5" # any model the provider serves
|
|
65
|
+
npx aztrx-cli run http://localhost:3000 --fix
|
|
66
|
+
```
|
|
67
|
+
|
|
58
68
|
`--fix` chains find → explain → heal, then asks *"Apply the fix?"* (`y/N`). On yes, the
|
|
59
69
|
verified patch lands in your working tree — `git diff` shows the result. Aztrx AI never
|
|
60
70
|
commits.
|
|
@@ -138,8 +148,8 @@ and niche tuning knobs).
|
|
|
138
148
|
| `--workers <n>` | Number of parallel detection workers | `1` |
|
|
139
149
|
| `--swarm` | Hidden alias for `--workers auto` | — |
|
|
140
150
|
| `--repro-runs <n>` | Flake-rate replay iterations | `3` |
|
|
141
|
-
| `--heal-model <model>` | Fallback LLM tier | `claude-sonnet-5` |
|
|
142
|
-
| `--heal-fast-model <model>` | Fast/cheap first tier | `claude-haiku-4-5` |
|
|
151
|
+
| `--heal-model <model>` | Fallback LLM tier | `claude-sonnet-5` / `$AZTRX_MODEL` |
|
|
152
|
+
| `--heal-fast-model <model>` | Fast/cheap first tier | `claude-haiku-4-5` / `$AZTRX_FAST_MODEL` |
|
|
143
153
|
| `--test-command <cmd>` | Test command run against a healed patch | `npm test` (auto-detected) |
|
|
144
154
|
| `--test-timeout <ms>` | Timeout for the heal test gate | `300000` |
|
|
145
155
|
| `--no-test` | Skip the test gate during healing | — |
|
|
@@ -196,7 +206,7 @@ jobs:
|
|
|
196
206
|
permissions: { contents: read, pull-requests: write }
|
|
197
207
|
steps:
|
|
198
208
|
- uses: actions/checkout@v4
|
|
199
|
-
- uses: DanisChaparov/aztrx@
|
|
209
|
+
- uses: DanisChaparov/aztrx@ee586a4410240efb7ed211ab0e1bf9d188361efa
|
|
200
210
|
with:
|
|
201
211
|
url: http://localhost:3000
|
|
202
212
|
start-command: npm run dev # optional — boot the app in the background
|
|
@@ -210,7 +220,7 @@ Or as a reusable workflow:
|
|
|
210
220
|
on: pull_request
|
|
211
221
|
jobs:
|
|
212
222
|
aztrx:
|
|
213
|
-
uses: DanisChaparov/aztrx/.github/workflows/aztrx-pr.yml@
|
|
223
|
+
uses: DanisChaparov/aztrx/.github/workflows/aztrx-pr.yml@ee586a4410240efb7ed211ab0e1bf9d188361efa
|
|
214
224
|
with:
|
|
215
225
|
url: http://localhost:3000
|
|
216
226
|
start-command: npm run dev
|
|
@@ -261,7 +271,7 @@ jobs:
|
|
|
261
271
|
sleep 2
|
|
262
272
|
done
|
|
263
273
|
- name: Generate badge
|
|
264
|
-
run: npx --yes aztrx-cli@0.
|
|
274
|
+
run: npx --yes aztrx-cli@0.3.0 run http://localhost:3000 --badge badge.svg
|
|
265
275
|
- name: Commit badge
|
|
266
276
|
run: |
|
|
267
277
|
git config user.name "github-actions[bot]"
|
|
@@ -279,12 +289,13 @@ are, and the run still exits 0 so the commit step always runs. Swap `push` for a
|
|
|
279
289
|
|
|
280
290
|
## Smart Cloud Router
|
|
281
291
|
|
|
282
|
-
`--heal` is backed by a two-tier router
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
`AZTRX_MODEL` or `--heal-fast-model` / `--heal-model
|
|
292
|
+
`--heal` is backed by a two-tier router: a fast/cheap model generates first, its patch
|
|
293
|
+
is gated, compiled, and replayed against the repro, and if the bug still reproduces (or
|
|
294
|
+
the patch fails a gate) aztrx falls back to the stronger model and tries again. Most
|
|
295
|
+
one-line fixes never pay for the big model. With Anthropic the defaults are
|
|
296
|
+
`claude-haiku-4-5` → `claude-sonnet-5`; with any other provider you pick both via
|
|
297
|
+
`AZTRX_FAST_MODEL` / `AZTRX_MODEL` (or `--heal-fast-model` / `--heal-model`). When only
|
|
298
|
+
one model is set, the router collapses to a single tier.
|
|
288
299
|
|
|
289
300
|
## Security & data flow
|
|
290
301
|
|
|
@@ -320,7 +331,7 @@ LLM call.
|
|
|
320
331
|
CORS.
|
|
321
332
|
- **`.aztrx/` is gitignored** on `init` — repro specs, reports, and patches stay
|
|
322
333
|
out of history.
|
|
323
|
-
- **Pinned supply chain.** The GitHub Action pins `aztrx-cli@0.
|
|
334
|
+
- **Pinned supply chain.** The GitHub Action pins `aztrx-cli@0.3.0` (never
|
|
324
335
|
`@latest`).
|
|
325
336
|
|
|
326
337
|
## Telemetry & privacy
|
package/dist/cli.js
CHANGED
|
@@ -122,8 +122,8 @@ program
|
|
|
122
122
|
.addOption(opt("--fix", "find → explain → heal → apply: one-command fix", "fix"))
|
|
123
123
|
.addOption(opt("--heal", "closed-loop healing for crash/error findings (implies --repro)", "fix"))
|
|
124
124
|
.addOption(opt("--magic-fix", "alias for --fix (deprecated)").hideHelp())
|
|
125
|
-
.addOption(opt("--heal-model <model>", "LLM model for healing
|
|
126
|
-
.addOption(opt("--heal-fast-model <model>", "fast/cheap first tier
|
|
125
|
+
.addOption(opt("--heal-model <model>", "LLM model for healing (default: claude-sonnet-5 or $AZTRX_MODEL)", "advanced"))
|
|
126
|
+
.addOption(opt("--heal-fast-model <model>", "fast/cheap first tier (default: claude-haiku-4-5 or $AZTRX_FAST_MODEL)", "advanced"))
|
|
127
127
|
.addOption(opt("--test-command <cmd>", "test command run against a healed patch (default: npm test, auto-detected)", "advanced"))
|
|
128
128
|
.addOption(opt("--test-timeout <ms>", "timeout for the heal test gate, ms", "advanced").default("300000"))
|
|
129
129
|
.addOption(opt("--no-test", "skip the test gate during healing", "advanced"))
|
|
@@ -258,9 +258,9 @@ program
|
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
261
|
+
// Suggest the next flag (e.g. --fix) after the run, in both the live TUI
|
|
262
|
+
// and plain paths. In the TUI this prints after the panel has finished.
|
|
263
|
+
suggestNext(findings, opts);
|
|
264
264
|
// Drain any in-flight telemetry uploads (each bounded) before exit, so a
|
|
265
265
|
// pending `--share-data` dispatch isn't killed mid-flight. Never affects
|
|
266
266
|
// the exit code.
|
package/dist/core/heal/index.js
CHANGED
|
@@ -20,6 +20,7 @@ import * as path from "path";
|
|
|
20
20
|
import { redact, unredact } from "./redact.js";
|
|
21
21
|
import { auditPatch } from "./gates.js";
|
|
22
22
|
import { generatePatch, modelTiers } from "./llm.js";
|
|
23
|
+
import { hasLlmKey } from "../llm.js";
|
|
23
24
|
import { applyHunks, createWorktree, diffWorktree, runTests, typecheckWorktree, writeWorktreeFile } from "./sandbox.js";
|
|
24
25
|
import { bootServer, detectStartCommand } from "./boot.js";
|
|
25
26
|
import { verifyFix } from "./verify.js";
|
|
@@ -121,18 +122,18 @@ export async function heal(finding, opts) {
|
|
|
121
122
|
const ctx = { finding, filePath, fileContent: original, redactedContent: red.text };
|
|
122
123
|
// No transport configured (and no injected generator) → nothing to try. A
|
|
123
124
|
// higher model tier can't fix a missing key, so bail before paying anything.
|
|
124
|
-
if (!opts.patchFn && !
|
|
125
|
+
if (!opts.patchFn && !hasLlmKey()) {
|
|
125
126
|
return {
|
|
126
127
|
...base,
|
|
127
128
|
status: "no-llm",
|
|
128
|
-
error: "heal:
|
|
129
|
+
error: "heal: no LLM API key is set (set ANTHROPIC_API_KEY, AZTRX_API_KEY, or AZTRX_API_BASE)",
|
|
129
130
|
};
|
|
130
131
|
}
|
|
131
132
|
// The Smart Cloud Router tier plan: fast/cheap first, Sonnet as the fallback.
|
|
132
133
|
// An injected patchFn collapses to a single tier (there is no model to route).
|
|
133
134
|
const tiers = opts.patchFn
|
|
134
|
-
? [{ model: opts.model ?? "
|
|
135
|
-
: modelTiers(opts.model);
|
|
135
|
+
? [{ model: opts.model ?? "default", label: "sonnet" }]
|
|
136
|
+
: modelTiers(opts.model, opts.fastModel);
|
|
136
137
|
const wt = await createWorktree(opts.repoRoot, finding.id);
|
|
137
138
|
// The winning (or last) patch + verification, held back for the final save.
|
|
138
139
|
let savedPatch = null;
|
package/dist/core/heal/llm.js
CHANGED
|
@@ -7,25 +7,23 @@
|
|
|
7
7
|
* module's callers.
|
|
8
8
|
*/
|
|
9
9
|
import { redact } from "./redact.js";
|
|
10
|
-
|
|
11
|
-
/** Cheap/fast first tier for the Smart Cloud Router. The idea: most one-line
|
|
12
|
-
* fixes are trivial, so try the small model before paying for the big one. */
|
|
13
|
-
const FAST_MODEL = process.env.AZTRX_FAST_MODEL || "claude-haiku-4-5-20251001";
|
|
14
|
-
const API_URL = "https://api.anthropic.com/v1/messages";
|
|
10
|
+
import { complete, primaryModel, fastModel } from "../llm.js";
|
|
15
11
|
/**
|
|
16
12
|
* The Smart Cloud Router's tier plan: fast/cheap first, then the capable model
|
|
17
13
|
* as the fallback. Collapses to a single tier when the two resolve to the same
|
|
18
14
|
* model (e.g. `AZTRX_FAST_MODEL=claude-sonnet-5`). Consumers loop over this in
|
|
19
15
|
* order and stop at the first `healed` result.
|
|
20
16
|
*/
|
|
21
|
-
export function modelTiers(fallbackModel) {
|
|
22
|
-
const fast =
|
|
23
|
-
const
|
|
24
|
-
if (
|
|
25
|
-
return [
|
|
17
|
+
export function modelTiers(fallbackModel, fastFallback) {
|
|
18
|
+
const fast = fastFallback || fastModel();
|
|
19
|
+
const primary = fallbackModel || primaryModel();
|
|
20
|
+
if (!primary)
|
|
21
|
+
return []; // no model configured — the caller reports no-llm
|
|
22
|
+
if (!fast || fast === primary)
|
|
23
|
+
return [{ model: primary, label: "sonnet" }];
|
|
26
24
|
return [
|
|
27
25
|
{ model: fast, label: "fast" },
|
|
28
|
-
{ model:
|
|
26
|
+
{ model: primary, label: "sonnet" },
|
|
29
27
|
];
|
|
30
28
|
}
|
|
31
29
|
const SYSTEM = `You are a meticulous bug-fixing engineer. You are given a single source file and a runtime error that occurs in it. Produce a MINIMAL fix as a Search & Replace diff.
|
|
@@ -76,33 +74,12 @@ export function parsePatch(raw) {
|
|
|
76
74
|
export async function generatePatch(ctx, opts = {}) {
|
|
77
75
|
if (opts.patchFn)
|
|
78
76
|
return opts.patchFn(ctx);
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
headers: {
|
|
86
|
-
"content-type": "application/json",
|
|
87
|
-
"x-api-key": key,
|
|
88
|
-
"anthropic-version": "2023-06-01",
|
|
89
|
-
},
|
|
90
|
-
body: JSON.stringify({
|
|
91
|
-
model: opts.model ?? DEFAULT_MODEL,
|
|
92
|
-
max_tokens: 2048,
|
|
93
|
-
temperature: 0,
|
|
94
|
-
system: SYSTEM,
|
|
95
|
-
messages: [{ role: "user", content: buildPrompt(ctx) }],
|
|
96
|
-
}),
|
|
77
|
+
const text = await complete({
|
|
78
|
+
system: SYSTEM,
|
|
79
|
+
prompt: buildPrompt(ctx),
|
|
80
|
+
model: opts.model,
|
|
81
|
+
maxTokens: 2048,
|
|
82
|
+
temperature: 0,
|
|
97
83
|
});
|
|
98
|
-
if (!res.ok) {
|
|
99
|
-
const body = await res.text().catch(() => "");
|
|
100
|
-
throw new Error(`heal: LLM request failed (${res.status}): ${body.slice(0, 300)}`);
|
|
101
|
-
}
|
|
102
|
-
const data = (await res.json());
|
|
103
|
-
const text = (data.content ?? [])
|
|
104
|
-
.filter((c) => c.type === "text")
|
|
105
|
-
.map((c) => c.text ?? "")
|
|
106
|
-
.join("\n");
|
|
107
84
|
return parsePatch(text);
|
|
108
85
|
}
|
package/dist/core/llm.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-agnostic LLM client. Two transports behind one `complete()`:
|
|
3
|
+
*
|
|
4
|
+
* - Anthropic Messages API (default) — key from `ANTHROPIC_API_KEY` (or
|
|
5
|
+
* `AZTRX_API_KEY`), primary model defaults to `claude-sonnet-5`.
|
|
6
|
+
* - Any OpenAI-compatible `/chat/completions` endpoint — selected by setting
|
|
7
|
+
* `AZTRX_API_BASE`. That single flag unlocks OpenAI, Grok, DeepSeek, Gemini,
|
|
8
|
+
* Kimi, Mistral, OpenRouter, and local models (Ollama / vLLM / LM Studio)
|
|
9
|
+
* with their existing keys.
|
|
10
|
+
*
|
|
11
|
+
* `AZTRX_MODEL` / `AZTRX_FAST_MODEL` pick the model(s); they default sensibly
|
|
12
|
+
* for Anthropic and must be set explicitly when a custom base URL is used.
|
|
13
|
+
*/
|
|
14
|
+
/** Resolve the active provider from the environment. */
|
|
15
|
+
export function resolveSettings() {
|
|
16
|
+
const base = process.env.AZTRX_API_BASE?.trim();
|
|
17
|
+
if (base) {
|
|
18
|
+
return {
|
|
19
|
+
provider: "openai",
|
|
20
|
+
apiKey: process.env.AZTRX_API_KEY || process.env.OPENAI_API_KEY,
|
|
21
|
+
baseUrl: base.replace(/\/+$/, ""),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
provider: "anthropic",
|
|
26
|
+
apiKey: process.env.ANTHROPIC_API_KEY || process.env.AZTRX_API_KEY,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** Is any provider configured? Used to decide whether to attempt an LLM call. */
|
|
30
|
+
export function hasLlmKey() {
|
|
31
|
+
return Boolean(resolveSettings().apiKey);
|
|
32
|
+
}
|
|
33
|
+
/** The primary model for the active provider. */
|
|
34
|
+
export function primaryModel() {
|
|
35
|
+
const s = resolveSettings();
|
|
36
|
+
if (s.provider === "anthropic")
|
|
37
|
+
return process.env.AZTRX_MODEL || "claude-sonnet-5";
|
|
38
|
+
return process.env.AZTRX_MODEL || "";
|
|
39
|
+
}
|
|
40
|
+
/** The cheap/fast first tier, or undefined when the provider has none. */
|
|
41
|
+
export function fastModel() {
|
|
42
|
+
const s = resolveSettings();
|
|
43
|
+
if (s.provider === "anthropic")
|
|
44
|
+
return process.env.AZTRX_FAST_MODEL || "claude-haiku-4-5-20251001";
|
|
45
|
+
return process.env.AZTRX_FAST_MODEL || undefined;
|
|
46
|
+
}
|
|
47
|
+
/** Run one completion against the active provider and return the text. */
|
|
48
|
+
export async function complete(opts) {
|
|
49
|
+
const s = resolveSettings();
|
|
50
|
+
const model = opts.model || primaryModel();
|
|
51
|
+
if (!model) {
|
|
52
|
+
throw new Error("no model configured — set AZTRX_MODEL (e.g. AZTRX_MODEL=gpt-4o)");
|
|
53
|
+
}
|
|
54
|
+
if (!s.apiKey) {
|
|
55
|
+
throw new Error(s.provider === "anthropic"
|
|
56
|
+
? "ANTHROPIC_API_KEY is not set"
|
|
57
|
+
: "AZTRX_API_KEY (or OPENAI_API_KEY) is not set");
|
|
58
|
+
}
|
|
59
|
+
return s.provider === "anthropic"
|
|
60
|
+
? anthropicComplete(s, model, opts)
|
|
61
|
+
: openaiComplete(s, model, opts);
|
|
62
|
+
}
|
|
63
|
+
async function anthropicComplete(s, model, opts) {
|
|
64
|
+
const res = await fetch("https://api.anthropic.com/v1/messages", {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: {
|
|
67
|
+
"content-type": "application/json",
|
|
68
|
+
"x-api-key": s.apiKey,
|
|
69
|
+
"anthropic-version": "2023-06-01",
|
|
70
|
+
},
|
|
71
|
+
body: JSON.stringify({
|
|
72
|
+
model,
|
|
73
|
+
max_tokens: opts.maxTokens ?? 2048,
|
|
74
|
+
temperature: opts.temperature ?? 0,
|
|
75
|
+
system: opts.system,
|
|
76
|
+
messages: [{ role: "user", content: opts.prompt }],
|
|
77
|
+
}),
|
|
78
|
+
});
|
|
79
|
+
if (!res.ok) {
|
|
80
|
+
const body = await res.text().catch(() => "");
|
|
81
|
+
throw new Error(`LLM request failed (${res.status}): ${body.slice(0, 300)}`);
|
|
82
|
+
}
|
|
83
|
+
const data = (await res.json());
|
|
84
|
+
return (data.content ?? [])
|
|
85
|
+
.filter((c) => c.type === "text")
|
|
86
|
+
.map((c) => c.text ?? "")
|
|
87
|
+
.join("\n");
|
|
88
|
+
}
|
|
89
|
+
async function openaiComplete(s, model, opts) {
|
|
90
|
+
const res = await fetch(`${s.baseUrl}/chat/completions`, {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: {
|
|
93
|
+
"content-type": "application/json",
|
|
94
|
+
authorization: `Bearer ${s.apiKey}`,
|
|
95
|
+
},
|
|
96
|
+
body: JSON.stringify({
|
|
97
|
+
model,
|
|
98
|
+
max_tokens: opts.maxTokens ?? 2048,
|
|
99
|
+
temperature: opts.temperature ?? 0,
|
|
100
|
+
messages: [
|
|
101
|
+
{ role: "system", content: opts.system },
|
|
102
|
+
{ role: "user", content: opts.prompt },
|
|
103
|
+
],
|
|
104
|
+
}),
|
|
105
|
+
});
|
|
106
|
+
if (!res.ok) {
|
|
107
|
+
const body = await res.text().catch(() => "");
|
|
108
|
+
throw new Error(`LLM request failed (${res.status}): ${body.slice(0, 300)}`);
|
|
109
|
+
}
|
|
110
|
+
const data = (await res.json());
|
|
111
|
+
const content = data.choices?.[0]?.message?.content;
|
|
112
|
+
if (typeof content === "string")
|
|
113
|
+
return content;
|
|
114
|
+
if (Array.isArray(content)) {
|
|
115
|
+
return content
|
|
116
|
+
.filter((c) => typeof c === "object" && c !== null && c.type === "text")
|
|
117
|
+
.map((c) => c.text ?? "")
|
|
118
|
+
.join("\n");
|
|
119
|
+
}
|
|
120
|
+
return "";
|
|
121
|
+
}
|
package/dist/core/modernize.js
CHANGED
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
import * as fs from "fs";
|
|
13
13
|
import * as path from "path";
|
|
14
14
|
import * as ts from "typescript";
|
|
15
|
-
|
|
16
|
-
const API_URL = "https://api.anthropic.com/v1/messages";
|
|
15
|
+
import { complete, hasLlmKey } from "./llm.js";
|
|
17
16
|
export function detectLang(filePath) {
|
|
18
17
|
const ext = path.extname(filePath).toLowerCase();
|
|
19
18
|
if ([".ts", ".tsx", ".mts", ".cts"].includes(ext))
|
|
@@ -86,39 +85,20 @@ export async function modernizeFile(repoRoot, filePath) {
|
|
|
86
85
|
catch (e) {
|
|
87
86
|
return { ok: false, original: "", changes: [], error: `cannot read ${filePath}: ${e.message}` };
|
|
88
87
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return { ok: false, original, changes: [], lang, error: "ANTHROPIC_API_KEY is not set" };
|
|
88
|
+
if (!hasLlmKey()) {
|
|
89
|
+
return { ok: false, original, changes: [], lang, error: "no LLM API key is set (set ANTHROPIC_API_KEY, AZTRX_API_KEY, or AZTRX_API_BASE)" };
|
|
92
90
|
}
|
|
93
91
|
let reply;
|
|
94
92
|
try {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
"anthropic-version": "2023-06-01",
|
|
101
|
-
},
|
|
102
|
-
body: JSON.stringify({
|
|
103
|
-
model: MODEL,
|
|
104
|
-
max_tokens: 8192,
|
|
105
|
-
temperature: 0,
|
|
106
|
-
system: SYSTEM,
|
|
107
|
-
messages: [{ role: "user", content: buildPrompt(original, lang) }],
|
|
108
|
-
}),
|
|
93
|
+
reply = await complete({
|
|
94
|
+
system: SYSTEM,
|
|
95
|
+
prompt: buildPrompt(original, lang),
|
|
96
|
+
maxTokens: 8192,
|
|
97
|
+
temperature: 0,
|
|
109
98
|
});
|
|
110
|
-
if (!res.ok) {
|
|
111
|
-
const body = await res.text().catch(() => "");
|
|
112
|
-
return { ok: false, original, changes: [], lang, error: `LLM request failed (${res.status}): ${body.slice(0, 300)}` };
|
|
113
|
-
}
|
|
114
|
-
const data = (await res.json());
|
|
115
|
-
reply = (data.content ?? [])
|
|
116
|
-
.filter((c) => c.type === "text")
|
|
117
|
-
.map((c) => c.text ?? "")
|
|
118
|
-
.join("\n");
|
|
119
99
|
}
|
|
120
100
|
catch (e) {
|
|
121
|
-
return { ok: false, original, changes: [], lang, error:
|
|
101
|
+
return { ok: false, original, changes: [], lang, error: e.message };
|
|
122
102
|
}
|
|
123
103
|
let parsed;
|
|
124
104
|
try {
|
|
@@ -11,7 +11,7 @@ export async function attachNetworkGuard(page, opts) {
|
|
|
11
11
|
host = new URL(url).hostname.toLowerCase();
|
|
12
12
|
}
|
|
13
13
|
catch {
|
|
14
|
-
await route.abort();
|
|
14
|
+
await route.abort("blockedbyclient");
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
17
|
if (host === "localhost" || host === "127.0.0.1" || host === "::1") {
|
|
@@ -25,7 +25,7 @@ export async function attachNetworkGuard(page, opts) {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
opts.onBlock?.(url);
|
|
28
|
-
await route.abort();
|
|
28
|
+
await route.abort("blockedbyclient");
|
|
29
29
|
});
|
|
30
30
|
}
|
|
31
31
|
/** Builds the allow-list: target origin + each `--allow-host`. */
|
package/dist/core/summarize.js
CHANGED
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
* The offer-to-apply line is emitted only when verified fixes are actually ready,
|
|
12
12
|
* so `--explain` (no healing) never promises a fix it doesn't have.
|
|
13
13
|
*/
|
|
14
|
-
|
|
15
|
-
const API_URL = "https://api.anthropic.com/v1/messages";
|
|
14
|
+
import { complete, hasLlmKey } from "./llm.js";
|
|
16
15
|
function normalizeLang(lang) {
|
|
17
16
|
return lang === "ru" ? "ru" : "en";
|
|
18
17
|
}
|
|
@@ -126,31 +125,12 @@ function buildLlmPrompt(findings, lang, hasHealed) {
|
|
|
126
125
|
const SYSTEM = "You are the plain-spoken explainer for a QA tool called Aztrx AI. You turn raw runtime-finding data into a concise, human-language summary for a developer. Never invent details absent from the data. Respond in the requested language only.";
|
|
127
126
|
async function summarizeFindingsLlm(findings, lang) {
|
|
128
127
|
const hasHealed = findings.some((f) => f.heal?.status === "healed");
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
},
|
|
136
|
-
body: JSON.stringify({
|
|
137
|
-
model: MODEL,
|
|
138
|
-
max_tokens: 1024,
|
|
139
|
-
temperature: 0.2,
|
|
140
|
-
system: SYSTEM,
|
|
141
|
-
messages: [{ role: "user", content: buildLlmPrompt(findings, lang, hasHealed) }],
|
|
142
|
-
}),
|
|
143
|
-
});
|
|
144
|
-
if (!res.ok) {
|
|
145
|
-
const body = await res.text().catch(() => "");
|
|
146
|
-
throw new Error(`summarize: LLM request failed (${res.status}): ${body.slice(0, 300)}`);
|
|
147
|
-
}
|
|
148
|
-
const data = (await res.json());
|
|
149
|
-
const text = (data.content ?? [])
|
|
150
|
-
.filter((c) => c.type === "text")
|
|
151
|
-
.map((c) => c.text ?? "")
|
|
152
|
-
.join("\n")
|
|
153
|
-
.trim();
|
|
128
|
+
const text = (await complete({
|
|
129
|
+
system: SYSTEM,
|
|
130
|
+
prompt: buildLlmPrompt(findings, lang, hasHealed),
|
|
131
|
+
maxTokens: 1024,
|
|
132
|
+
temperature: 0.2,
|
|
133
|
+
})).trim();
|
|
154
134
|
return text || summarizeFindingsTemplate(findings, lang);
|
|
155
135
|
}
|
|
156
136
|
/**
|
|
@@ -162,7 +142,7 @@ export async function summarizeFindings(findings, opts = {}) {
|
|
|
162
142
|
const lang = normalizeLang(opts.lang);
|
|
163
143
|
if (findings.length === 0)
|
|
164
144
|
return summarizeFindingsTemplate(findings, lang);
|
|
165
|
-
if (!
|
|
145
|
+
if (!hasLlmKey())
|
|
166
146
|
return summarizeFindingsTemplate(findings, lang);
|
|
167
147
|
try {
|
|
168
148
|
return await summarizeFindingsLlm(findings, lang);
|