aztrx-cli 0.4.5 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +326 -29
  2. package/dist/cli/help.d.ts +22 -0
  3. package/dist/cli/repo.d.ts +20 -0
  4. package/dist/cli/repo.js +50 -0
  5. package/dist/cli.d.ts +2 -0
  6. package/dist/cli.js +284 -86
  7. package/dist/core/auth.d.ts +33 -0
  8. package/dist/core/badge.d.ts +9 -0
  9. package/dist/core/browser.d.ts +3 -0
  10. package/dist/core/classifier.d.ts +41 -0
  11. package/dist/core/cloud/index.d.ts +62 -0
  12. package/dist/core/cloud/index.js +24 -4
  13. package/dist/core/devServer.d.ts +90 -0
  14. package/dist/core/devServer.js +253 -0
  15. package/dist/core/diagnose.d.ts +18 -0
  16. package/dist/core/diff.d.ts +31 -0
  17. package/dist/core/domWalker.d.ts +24 -0
  18. package/dist/core/domWalker.js +18 -2
  19. package/dist/core/eventBus.d.ts +59 -0
  20. package/dist/core/events.d.ts +42 -0
  21. package/dist/core/fixPr.d.ts +16 -0
  22. package/dist/core/fixPr.js +11 -1
  23. package/dist/core/fuzzer.d.ts +24 -0
  24. package/dist/core/fuzzer.js +1 -1
  25. package/dist/core/heal/apply.d.ts +28 -0
  26. package/dist/core/heal/boot.d.ts +74 -0
  27. package/dist/core/heal/boot.js +126 -23
  28. package/dist/core/heal/childEnv.d.ts +15 -0
  29. package/dist/core/heal/gates.d.ts +11 -0
  30. package/dist/core/heal/index.d.ts +19 -0
  31. package/dist/core/heal/index.js +53 -23
  32. package/dist/core/heal/llm.d.ts +43 -0
  33. package/dist/core/heal/llm.js +22 -2
  34. package/dist/core/heal/redact.d.ts +22 -0
  35. package/dist/core/heal/sandbox.d.ts +54 -0
  36. package/dist/core/heal/sandbox.js +63 -3
  37. package/dist/core/heal/types.d.ts +102 -0
  38. package/dist/core/heal/verify.d.ts +37 -0
  39. package/dist/core/heal/verify.js +28 -14
  40. package/dist/core/httpFuzzer.d.ts +33 -0
  41. package/dist/core/init.d.ts +21 -0
  42. package/dist/core/init.js +5 -6
  43. package/dist/core/interceptor.d.ts +21 -0
  44. package/dist/core/llm.d.ts +36 -0
  45. package/dist/core/llm.js +40 -13
  46. package/dist/core/minimizer.d.ts +13 -0
  47. package/dist/core/modernize.d.ts +28 -0
  48. package/dist/core/modernize.js +2 -2
  49. package/dist/core/networkGuard.d.ts +13 -0
  50. package/dist/core/orchestrator.d.ts +86 -0
  51. package/dist/core/orchestrator.js +5 -2
  52. package/dist/core/patrol/loop.d.ts +46 -0
  53. package/dist/core/patrol/pr.d.ts +42 -0
  54. package/dist/core/patrol/pr.js +9 -6
  55. package/dist/core/patrol/record.d.ts +51 -0
  56. package/dist/core/patrol/state.d.ts +39 -0
  57. package/dist/core/pr.d.ts +3 -0
  58. package/dist/core/pr.js +15 -11
  59. package/dist/core/prompt.d.ts +14 -0
  60. package/dist/core/recorder.d.ts +23 -0
  61. package/dist/core/recorder.js +1 -1
  62. package/dist/core/renderMarkdown.d.ts +7 -0
  63. package/dist/core/replay.d.ts +33 -0
  64. package/dist/core/replay.js +52 -7
  65. package/dist/core/report.d.ts +8 -0
  66. package/dist/core/resolver.d.ts +64 -0
  67. package/dist/core/resolver.js +135 -17
  68. package/dist/core/rng.d.ts +2 -0
  69. package/dist/core/specCompiler.d.ts +17 -0
  70. package/dist/core/studio.d.ts +6 -0
  71. package/dist/core/studio.js +0 -3
  72. package/dist/core/summarize.d.ts +26 -0
  73. package/dist/core/summarize.js +17 -6
  74. package/dist/core/swarm.d.ts +97 -0
  75. package/dist/core/swarm.js +25 -2
  76. package/dist/core/telemetry/index.d.ts +33 -0
  77. package/dist/core/telemetry/index.js +22 -4
  78. package/dist/core/telemetry/sanitize.d.ts +19 -0
  79. package/dist/core/telemetry/types.d.ts +26 -0
  80. package/dist/core/types.d.ts +88 -0
  81. package/dist/core/ui.d.ts +23 -0
  82. package/dist/core/ui.js +0 -5
  83. package/dist/core/validator.d.ts +14 -0
  84. package/dist/core/version.d.ts +1 -0
  85. package/dist/hooks/index.d.ts +106 -0
  86. package/dist/hooks/index.js +349 -0
  87. package/dist/mcp/index.d.ts +58 -0
  88. package/dist/mcp/index.js +295 -0
  89. package/dist/mcp/install.d.ts +52 -0
  90. package/dist/mcp/install.js +204 -0
  91. package/dist/mcp/protocol.d.ts +106 -0
  92. package/dist/mcp/protocol.js +156 -0
  93. package/dist/mcp/tools.d.ts +107 -0
  94. package/dist/mcp/tools.js +621 -0
  95. package/dist/next/index.d.ts +70 -0
  96. package/dist/next/index.js +103 -0
  97. package/dist/plugins/scan.d.ts +85 -0
  98. package/dist/plugins/scan.js +188 -0
  99. package/dist/ui/app.d.ts +11 -0
  100. package/dist/vite/index.d.ts +60 -0
  101. package/dist/vite/index.js +56 -0
  102. package/package.json +44 -6
@@ -0,0 +1,156 @@
1
+ /**
2
+ * MCP wire format — framing, version negotiation, result envelopes.
3
+ *
4
+ * Hand-rolled, deliberately. Revisions `2025-11-25` and earlier open with an
5
+ * `initialize` handshake; `2026-07-28` removed it in favour of per-request
6
+ * metadata. Editors are split across the two (Cursor still opens with
7
+ * `initialize`), and neither official SDK speaks both: `@modelcontextprotocol/sdk`
8
+ * is legacy-only and pulls 17 runtime dependencies — express, hono, cors, jose,
9
+ * ajv — for a stdio server that needs none of them, and
10
+ * `@modelcontextprotocol/server` is the new revision only. A server that speaks
11
+ * one era is a server that is missing from somebody's editor. The spec has a name
12
+ * for the thing that speaks both — **dual-era** — and rules for it, which this
13
+ * follows rather than reinvents.
14
+ *
15
+ * This file imports nothing at all. It is loaded before the first request from
16
+ * an editor, and editors time out servers that boot slowly.
17
+ */
18
+ /** Every revision this server can serve, oldest first. `2026-07-28` is the one
19
+ * that made `server/discover` mandatory and `resultType` required; the earlier
20
+ * four are the handshake era, and `2025-06-18` / `2025-11-25` are what the
21
+ * editors in the wild actually send today. */
22
+ export const SUPPORTED_VERSIONS = [
23
+ "2024-11-05",
24
+ "2025-03-26",
25
+ "2025-06-18",
26
+ "2025-11-25",
27
+ "2026-07-28",
28
+ ];
29
+ /** Newest revision implemented. Used only when we cannot serve what was asked
30
+ * for — never to "upgrade" a client that asked for something older. */
31
+ export const LATEST_VERSION = SUPPORTED_VERSIONS[SUPPORTED_VERSIONS.length - 1];
32
+ /** `_meta` keys, per the `_meta` naming rules in the 2026-07-28 base spec. */
33
+ export const META_PROTOCOL_VERSION = "io.modelcontextprotocol/protocolVersion";
34
+ export const META_SERVER_INFO = "io.modelcontextprotocol/serverInfo";
35
+ export const PARSE_ERROR = -32700;
36
+ export const INVALID_REQUEST = -32600;
37
+ export const METHOD_NOT_FOUND = -32601;
38
+ export const INVALID_PARAMS = -32602;
39
+ export const INTERNAL_ERROR = -32603;
40
+ /** `UnsupportedProtocolVersionError`, per the 2026-07-28 error-code table. The
41
+ * number is not decorative: a modern client matches on it to tell "this server
42
+ * speaks my language but not this dialect — retry with one off `supported`"
43
+ * from "this server is old", and the two lead to opposite actions. */
44
+ export const UNSUPPORTED_PROTOCOL_VERSION = -32022;
45
+ /** A failure of the *request* — bad method, bad shape. Distinct from a tool that
46
+ * ran and reported a problem, which is a normal result with `isError: true`. */
47
+ export class ProtocolError extends Error {
48
+ code;
49
+ data;
50
+ constructor(code, message, data) {
51
+ super(message);
52
+ this.code = code;
53
+ this.data = data;
54
+ this.name = "ProtocolError";
55
+ }
56
+ }
57
+ // ---------------------------------------------------------------------------
58
+ // Framing
59
+ // ---------------------------------------------------------------------------
60
+ /** One JSON-RPC message, one line. That is the stdio binding, and `JSON.stringify`
61
+ * already guarantees it — a newline inside a string is escaped, never emitted raw. */
62
+ export function encode(message) {
63
+ return JSON.stringify(message) + "\n";
64
+ }
65
+ /** Parse one line. A line that is not JSON has an unknowable id, so the error
66
+ * goes out with a null one — the JSON-RPC rule for exactly this case. */
67
+ export function decode(line) {
68
+ let value;
69
+ try {
70
+ value = JSON.parse(line);
71
+ }
72
+ catch {
73
+ return { ok: false, response: error(null, PARSE_ERROR, "Parse error: line is not JSON") };
74
+ }
75
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
76
+ return { ok: false, response: error(null, INVALID_REQUEST, "Invalid request: not a JSON object") };
77
+ }
78
+ const msg = value;
79
+ if (typeof msg.method !== "string" && msg.id === undefined) {
80
+ // A response we have no use for, or a garbage object. Either way there is
81
+ // nothing to answer: an object with no method is not a request.
82
+ return { ok: false, response: error(msg.id ?? null, INVALID_REQUEST, "Invalid request: no method") };
83
+ }
84
+ return { ok: true, msg };
85
+ }
86
+ /** A request expects exactly one response; a notification expects none, and
87
+ * answering one is a protocol violation. `id` is what separates them — a
88
+ * notification simply has no `id` member. */
89
+ export function isNotification(msg) {
90
+ return typeof msg.method === "string" && (msg.id === undefined || msg.id === null);
91
+ }
92
+ // ---------------------------------------------------------------------------
93
+ // Version negotiation
94
+ // ---------------------------------------------------------------------------
95
+ export function supports(version) {
96
+ return typeof version === "string" && SUPPORTED_VERSIONS.includes(version);
97
+ }
98
+ /** The version a *modern* request declares in `_meta`, or undefined for a legacy
99
+ * request (which declares nothing — its version was fixed by `initialize`). */
100
+ export function declaredVersion(msg) {
101
+ const meta = msg.params?.["_meta"];
102
+ if (typeof meta !== "object" || meta === null)
103
+ return undefined;
104
+ const v = meta[META_PROTOCOL_VERSION];
105
+ return typeof v === "string" ? v : undefined;
106
+ }
107
+ /** Which version to answer a legacy `initialize` with.
108
+ *
109
+ * The legacy spec makes this a **MUST**, not a courtesy: *"If the server supports
110
+ * the requested protocol version, it MUST respond with the same version.
111
+ * Otherwise, the server MUST respond with another protocol version it supports."*
112
+ * So we echo what was asked for whenever we can serve it. Answering `initialize`
113
+ * with our newest version when the client asked for an older one we support is a
114
+ * spec violation, and it is the documented way to make a modern client refuse the
115
+ * server outright ("Server's protocol version is not supported") — the client has
116
+ * no fall-forward mechanism, so a needless mismatch is fatal, not merely noisy. */
117
+ export function negotiate(requested) {
118
+ return supports(requested) ? requested : LATEST_VERSION;
119
+ }
120
+ /** Reject a request that declares a version we do not implement. Returns a
121
+ * ready-to-send error, or null when the request may proceed.
122
+ *
123
+ * Only modern requests declare a version; a legacy request carries none and is
124
+ * served under legacy semantics for the life of the process. The `data` shape is
125
+ * exact because it is load-bearing: the client picks a version off `supported`
126
+ * and retries, so an inexact copy costs it the retry it needs. */
127
+ export function checkVersion(msg) {
128
+ const declared = declaredVersion(msg);
129
+ if (declared === undefined || supports(declared))
130
+ return null;
131
+ return error(msg.id ?? null, UNSUPPORTED_PROTOCOL_VERSION, "Unsupported protocol version", {
132
+ supported: [...SUPPORTED_VERSIONS],
133
+ requested: declared,
134
+ });
135
+ }
136
+ // ---------------------------------------------------------------------------
137
+ // Envelopes
138
+ // ---------------------------------------------------------------------------
139
+ /** A successful result.
140
+ *
141
+ * `resultType: "complete"` is required on every result from `2026-07-28` on, and
142
+ * older clients ignore fields they do not recognise — so it is written
143
+ * unconditionally rather than behind an era check. One response shape for every
144
+ * era is the whole reason this server can serve both. */
145
+ export function result(id, payload = {}) {
146
+ return { jsonrpc: "2.0", id, result: { resultType: "complete", ...payload } };
147
+ }
148
+ export function error(id, code, message, data) {
149
+ const err = { code, message };
150
+ if (data !== undefined)
151
+ err.data = data;
152
+ return { jsonrpc: "2.0", id: id ?? null, error: err };
153
+ }
154
+ export function serverInfo(version) {
155
+ return { name: "aztrx-cli", version };
156
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * The three tools, their schemas, and the handlers behind them.
3
+ *
4
+ * `aztrx_scan` finds and proves; `aztrx_repro` shows one finding's steps;
5
+ * `aztrx_fix` patches and verifies. The split is about the agent's context
6
+ * budget, not about the code: a `Finding` carries a whole minimized action
7
+ * sequence and a compiled spec, and a fix carries a diff — inlining all of that
8
+ * into every scan response is how a useful call becomes an expensive one. The
9
+ * scan returns a compact projection and a handle; detail is fetched by handle.
10
+ *
11
+ * Sessions are gone from the protocol in `2026-07-28` and never existed in this
12
+ * design. Cross-call state is an explicit, opaque, server-minted `scanId` held in
13
+ * a bounded in-memory map — which is easier to reason about than hidden state, and
14
+ * is exactly what the spec recommends for stateful tools.
15
+ *
16
+ * Nothing here imports the orchestrator, Playwright, or the heal stack. Those are
17
+ * `await import()`ed on first use, because an editor kills a server that is slow
18
+ * to answer `initialize` and `core/orchestrator.js` costs ~1.7s to load.
19
+ */
20
+ import type { HealOptions, HealResult } from "../core/heal/types.js";
21
+ import type { RunOptions } from "../core/orchestrator.js";
22
+ import type { Finding } from "../core/types.js";
23
+ export interface TextBlock {
24
+ type: "text";
25
+ text: string;
26
+ }
27
+ /** A tool result. `isError` marks a tool that *ran* and could not do the job —
28
+ * distinct from a protocol error, which is about the request itself. */
29
+ export interface ToolCallResult {
30
+ content: TextBlock[];
31
+ structuredContent?: unknown;
32
+ isError?: boolean;
33
+ }
34
+ export interface ToolDefinition {
35
+ name: string;
36
+ title: string;
37
+ description: string;
38
+ inputSchema: Record<string, unknown>;
39
+ }
40
+ export declare const TOOLS: ToolDefinition[];
41
+ export interface ScanRecord {
42
+ scanId: string;
43
+ repoRoot: string;
44
+ url: string;
45
+ findings: Finding[];
46
+ /** Heal results already paid for, so `apply: true` after a review call does not
47
+ * buy the same patch twice. */
48
+ heals: Map<string, HealResult>;
49
+ createdAt: number;
50
+ }
51
+ export type RunFn = (opts: RunOptions) => Promise<Finding[]>;
52
+ export type HealFn = (finding: Finding, opts: HealOptions) => Promise<HealResult>;
53
+ export interface RuntimeDeps {
54
+ /** Loaded on first use, never at module load. Injectable so the dispatch and
55
+ * purity tests can run without a browser. */
56
+ loadRun?: () => Promise<RunFn>;
57
+ loadHeal?: () => Promise<HealFn>;
58
+ /** Project used when a tool call does not name one. */
59
+ defaultRepoRoot?: string;
60
+ }
61
+ export declare class McpRuntime {
62
+ private deps;
63
+ private records;
64
+ /** Dev servers this process booted and has not yet stopped. The server tears
65
+ * these down on shutdown — a scan interrupted by a closing editor must not
66
+ * leave a dev server holding its port. */
67
+ private openTargets;
68
+ /** Scans and fixes run one at a time; see `serialize`. */
69
+ private queue;
70
+ constructor(deps?: RuntimeDeps);
71
+ call(name: string, args: Record<string, unknown>): Promise<ToolCallResult>;
72
+ /** Stop every dev server this process started. Idempotent. */
73
+ closeAll(): Promise<void>;
74
+ /** One scanning run at a time.
75
+ *
76
+ * `run()` truncates `.aztrx/events.jsonl` and rewrites `.aztrx/report.html` and
77
+ * `.aztrx/repro/<id>.spec.ts`, and a zero-config scan boots a dev server on a
78
+ * port it picked. Two overlapping runs in one repo therefore corrupt each
79
+ * other's artifacts and race for the port — and an MCP client can pipeline
80
+ * requests, so this is a real case rather than a theoretical one. `heal`
81
+ * creates git worktrees and boots servers of its own, so it shares the queue. */
82
+ private serialize;
83
+ private defaultRepo;
84
+ private loadRun;
85
+ private loadHeal;
86
+ private remember;
87
+ private scan;
88
+ /** Find a running app, or start the project's own.
89
+ *
90
+ * `core/devServer.ts`'s `resolveTarget` does this too, but it also arms
91
+ * `process.once("SIGINT"/"SIGTERM")` handlers that are never removed and that
92
+ * call `process.exit` themselves. That is right for a CLI about to exit, and
93
+ * wrong for a process that will serve an editor for hours: the sixth scan trips
94
+ * MaxListenersExceededWarning, and the first Ctrl-C kills the server before it
95
+ * can tear the dev server down. So the same three steps are run here, and the
96
+ * close hook is handed back instead of wired to a signal. */
97
+ private bootOrAttach;
98
+ private release;
99
+ /** Resolve a `{scanId, findingId}` pair to the finding, or to the message the
100
+ * model needs. A stale handle is an ordinary, recoverable answer. */
101
+ private resolve;
102
+ private repro;
103
+ private fix;
104
+ private healOptions;
105
+ private finishFix;
106
+ private reportFix;
107
+ }