aztrx-cli 0.4.4 → 0.4.5
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/dist/core/orchestrator.js +19 -7
- package/dist/ui/app.js +14 -1
- package/package.json +1 -1
|
@@ -72,6 +72,14 @@ export async function run(options) {
|
|
|
72
72
|
if (!ui)
|
|
73
73
|
console.log(parts.join(" "));
|
|
74
74
|
};
|
|
75
|
+
// Important hints/warnings (destructive mode, login-wall, wrong --repo) must be
|
|
76
|
+
// visible in the TUI too — `say` is a no-op there. Emit on the bus always, and
|
|
77
|
+
// print to stdout only in the linear (non-UI) path so it isn't duplicated.
|
|
78
|
+
const notice = (message, level = "hint") => {
|
|
79
|
+
bus.emit("notice", { message, level, ts: Date.now() });
|
|
80
|
+
if (!ui)
|
|
81
|
+
console.log(level === "danger" ? pc.red(message) : pc.yellow(message));
|
|
82
|
+
};
|
|
75
83
|
const emitPhase = (phase, detail) => bus.emit("phase", { phase, detail, ts: Date.now() });
|
|
76
84
|
say(pc.cyan(`\nAztrx AI v${VERSION} — Runtime Detector`));
|
|
77
85
|
say(pc.dim(`Target: ${url}`));
|
|
@@ -85,8 +93,7 @@ export async function run(options) {
|
|
|
85
93
|
if (options.storageState)
|
|
86
94
|
say(pc.dim(`Auth: ${options.storageState}`));
|
|
87
95
|
if (options.allowDestructive) {
|
|
88
|
-
|
|
89
|
-
say(pc.yellow(" This can mutate real data. Run only against a disposable/dev instance you own."));
|
|
96
|
+
notice("⚠ DESTRUCTIVE MODE — delete/pay/logout/checkout controls are ENABLED. This can mutate real data. Run only against a disposable/dev instance you own.", "danger");
|
|
90
97
|
}
|
|
91
98
|
say("");
|
|
92
99
|
emitPhase("launch", url);
|
|
@@ -149,7 +156,7 @@ export async function run(options) {
|
|
|
149
156
|
say(pc.dim(`\nWalked ${totalActions} action(s).\n`));
|
|
150
157
|
}
|
|
151
158
|
if (sawLoginForm && !options.login) {
|
|
152
|
-
|
|
159
|
+
notice("Hint: this app has a login form — re-run with --login to test the authenticated app.", "hint");
|
|
153
160
|
}
|
|
154
161
|
// F7 → F8 → F9: minimize each finding, compile an executable spec, validate
|
|
155
162
|
// the flake rate. Only crash/error findings with a recorded action history.
|
|
@@ -317,10 +324,15 @@ export async function run(options) {
|
|
|
317
324
|
}
|
|
318
325
|
}
|
|
319
326
|
else if (candidates.length > 0) {
|
|
320
|
-
// Reproducible crashes, but none mapped to source (wrong
|
|
321
|
-
// user rather than silently doing nothing. Already-handled
|
|
322
|
-
// excluded above, so this only fires for genuinely new
|
|
323
|
-
|
|
327
|
+
// Reproducible crashes, but none mapped to a readable source file (wrong
|
|
328
|
+
// --repo?) — tell the user rather than silently doing nothing. Already-handled
|
|
329
|
+
// fingerprints are excluded above, so this only fires for genuinely new
|
|
330
|
+
// findings. Name the files it looked for so the hint is actionable.
|
|
331
|
+
const missing = candidates
|
|
332
|
+
.map((f) => f.mappedLocation?.filePath || f.rawMessage.split("\n")[0].slice(0, 40))
|
|
333
|
+
.filter(Boolean)
|
|
334
|
+
.slice(0, 3);
|
|
335
|
+
notice(`Found ${candidates.length} reproducible crash(es) but couldn't read their source (${missing.join(", ")}). Run from your project root (or pass --repo <dir>) so --fix can read the code.`, "warning");
|
|
324
336
|
}
|
|
325
337
|
}
|
|
326
338
|
const reportPath = writeReport(repoRoot, url, findings, options.lang);
|
package/dist/ui/app.js
CHANGED
|
@@ -54,6 +54,8 @@ function reducer(state, msg) {
|
|
|
54
54
|
...state,
|
|
55
55
|
findings: state.findings.map((f) => f.fingerprint === msg.heal.finding.fingerprint ? msg.heal.finding : f),
|
|
56
56
|
};
|
|
57
|
+
case "notice":
|
|
58
|
+
return { ...state, notices: [...state.notices, msg.notice] };
|
|
57
59
|
default:
|
|
58
60
|
return state;
|
|
59
61
|
}
|
|
@@ -68,6 +70,7 @@ const initialState = {
|
|
|
68
70
|
routes: [],
|
|
69
71
|
navigations: 0,
|
|
70
72
|
done: false,
|
|
73
|
+
notices: [],
|
|
71
74
|
};
|
|
72
75
|
function useAztrx(bus) {
|
|
73
76
|
const [state, dispatch] = useReducer(reducer, initialState);
|
|
@@ -86,6 +89,7 @@ function useAztrx(bus) {
|
|
|
86
89
|
bus.on("route", (r) => dispatch({ type: "route", url: r.url })),
|
|
87
90
|
bus.on("repro", (r) => dispatch({ type: "repro", repro: r })),
|
|
88
91
|
bus.on("heal", (h) => dispatch({ type: "heal", heal: h })),
|
|
92
|
+
bus.on("notice", (n) => dispatch({ type: "notice", notice: n })),
|
|
89
93
|
];
|
|
90
94
|
return () => offs.forEach((off) => off());
|
|
91
95
|
}, [bus]);
|
|
@@ -119,6 +123,15 @@ function FindingRow({ finding, repro }) {
|
|
|
119
123
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(SeverityMark, { severity: finding.severity }), _jsx(Text, { color: C.dim, children: " " }), _jsx(Text, { color: C.fg, children: firstLine(finding.rawMessage) })] }), finding.mappedLocation ? (_jsxs(Text, { color: C.dim, children: [" ", finding.mappedLocation.filePath, ":", finding.mappedLocation.line, ":", finding.mappedLocation.column] })) : null, dx ? (_jsxs(Text, { color: C.azure, children: [" ↳ ", dx] })) : null, repro ? (_jsxs(Box, { children: [_jsx(Text, { color: C.dim, children: " " }), _jsx(ReproBadge, { repro: repro }), _jsxs(Text, { color: C.dim, children: [" ", "spec ", repro.specPath, " \u00B7 ", repro.steps, "/", repro.totalSteps, " steps"] })] })) : null, finding.heal ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: finding.heal.status === "healed" ? C.green : C.amber, children: [" ", finding.heal.status === "healed" ? "✓" : "◐", " ", finding.heal.status, finding.heal.model ? ` · ${finding.heal.model}` : ""] }), finding.heal.status === "healed" && finding.heal.hunks.length > 0 ? (_jsx(DiffView, { hunks: finding.heal.hunks, filePath: finding.heal.filePath })) : null] })) : null] }));
|
|
120
124
|
}
|
|
121
125
|
const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
126
|
+
const NOTICE_STYLE = {
|
|
127
|
+
hint: { color: C.azure, glyph: "💡" },
|
|
128
|
+
warning: { color: C.amber, glyph: "⚠" },
|
|
129
|
+
danger: { color: C.red, glyph: "⚠" },
|
|
130
|
+
};
|
|
131
|
+
function NoticeRow({ notice }) {
|
|
132
|
+
const style = NOTICE_STYLE[notice.level];
|
|
133
|
+
return (_jsxs(Text, { color: style.color, children: [style.glyph, " ", notice.message] }));
|
|
134
|
+
}
|
|
122
135
|
function DiffView({ hunks, filePath }) {
|
|
123
136
|
const groups = diffHunks(hunks);
|
|
124
137
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: C.dim, children: [" ", filePath] }), groups.map((group, gi) => (_jsx(Box, { flexDirection: "column", children: group.map((l, i) => (_jsxs(Box, { children: [_jsxs(Text, { color: l.type === "add" ? C.green : C.red, children: [" ", l.type === "add" ? "+" : "-", " "] }), _jsx(Text, { children: l.tokens.map((t, j) => {
|
|
@@ -150,7 +163,7 @@ function AztrxApp({ bus, done, targetUrl, repoRoot, mode }) {
|
|
|
150
163
|
}, [done, exit]);
|
|
151
164
|
const phase = PHASE_LABEL[state.phase];
|
|
152
165
|
const currentRoute = state.routes[state.routes.length - 1] ?? targetUrl;
|
|
153
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: C.azure, bold: true, children: "Aztrx AI" }), _jsx(Text, { color: C.dim, children: " \u2014 Runtime Detector" }), _jsxs(Text, { color: C.dim, children: [" v", VERSION] })] }), _jsxs(Text, { color: C.dim, children: [" target ", targetUrl, " repo ", repoRoot] }), _jsxs(Text, { color: C.dim, children: [" mode ", mode] }), _jsx(Text, { color: C.dim, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: phase.color, children: [state.phase === "done" ? "✓" : SPINNER[spin], " ", phase.text.replace(/^[◉✓]\s*/, "")] }), _jsx(Text, { color: C.dim, children: " " }), _jsx(Text, { color: C.azureBright, bold: true, children: rate.toFixed(1) }), _jsx(Text, { color: C.dim, children: " ops/s \u00B7 " }), _jsx(Text, { color: C.fg, children: state.actions }), _jsx(Text, { color: C.dim, children: " actions \u00B7 " }), _jsx(Text, { color: C.fg, children: state.clicks }), _jsx(Text, { color: C.dim, children: " clicks" })] }), _jsxs(Box, { children: [_jsx(Text, { color: C.dim, children: " route " }), _jsx(Text, { color: C.muted, children: currentRoute }), _jsxs(Text, { color: C.dim, children: [" \u00B7 ", state.routes.length, " route(s)"] })] }), state.findings.length > 0 ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: C.muted, bold: true, children: ["findings (", state.findings.length, ")"] }), state.findings.map((f) => (_jsx(FindingRow, { finding: f, repro: state.repros[f.fingerprint] }, f.fingerprint)))] })) : null, state.noise > 0 ? (_jsxs(Text, { color: C.dim, children: [" \u25B8 ", state.noise, " noise event(s) suppressed"] })) : null] }));
|
|
166
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: C.azure, bold: true, children: "Aztrx AI" }), _jsx(Text, { color: C.dim, children: " \u2014 Runtime Detector" }), _jsxs(Text, { color: C.dim, children: [" v", VERSION] })] }), _jsxs(Text, { color: C.dim, children: [" target ", targetUrl, " repo ", repoRoot] }), _jsxs(Text, { color: C.dim, children: [" mode ", mode] }), _jsx(Text, { color: C.dim, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: phase.color, children: [state.phase === "done" ? "✓" : SPINNER[spin], " ", phase.text.replace(/^[◉✓]\s*/, "")] }), _jsx(Text, { color: C.dim, children: " " }), _jsx(Text, { color: C.azureBright, bold: true, children: rate.toFixed(1) }), _jsx(Text, { color: C.dim, children: " ops/s \u00B7 " }), _jsx(Text, { color: C.fg, children: state.actions }), _jsx(Text, { color: C.dim, children: " actions \u00B7 " }), _jsx(Text, { color: C.fg, children: state.clicks }), _jsx(Text, { color: C.dim, children: " clicks" })] }), _jsxs(Box, { children: [_jsx(Text, { color: C.dim, children: " route " }), _jsx(Text, { color: C.muted, children: currentRoute }), _jsxs(Text, { color: C.dim, children: [" \u00B7 ", state.routes.length, " route(s)"] })] }), state.notices.length > 0 ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: state.notices.map((n, i) => (_jsx(NoticeRow, { notice: n }, i))) })) : null, state.findings.length > 0 ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: C.muted, bold: true, children: ["findings (", state.findings.length, ")"] }), state.findings.map((f) => (_jsx(FindingRow, { finding: f, repro: state.repros[f.fingerprint] }, f.fingerprint)))] })) : null, state.noise > 0 ? (_jsxs(Text, { color: C.dim, children: [" \u25B8 ", state.noise, " noise event(s) suppressed"] })) : null] }));
|
|
154
167
|
}
|
|
155
168
|
/** Mount the live terminal panel and resolve once the run (or a failure) ends. */
|
|
156
169
|
export function renderTui(props) {
|