@henols/vice-mcp 0.2.1 → 0.2.3

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 (108) hide show
  1. package/README.md +4 -3
  2. package/THIRD-PARTY-NOTICES.md +423 -25
  3. package/{r2000-acme-ident.ts → anno-acme-ident.ts} +13 -13
  4. package/anno-bank.ts +171 -0
  5. package/anno-cli.ts +3040 -0
  6. package/{r2000-confidence.ts → anno-confidence.ts} +22 -22
  7. package/anno-coverage.ts +2465 -0
  8. package/anno-derive.ts +590 -0
  9. package/anno-details.ts +169 -0
  10. package/anno-enum-gen.ts +919 -0
  11. package/anno-export-asm.ts +2396 -0
  12. package/anno-graphics.ts +338 -0
  13. package/anno-hazard-report.ts +1367 -0
  14. package/anno-import.ts +495 -0
  15. package/anno-index.ts +150 -0
  16. package/anno-join.ts +480 -0
  17. package/{r2000-memmap-render.ts → anno-memmap-render.ts} +236 -95
  18. package/anno-provenance-ledger.ts +472 -0
  19. package/{r2000-regbits-gen.ts → anno-regbits-gen.ts} +20 -15
  20. package/{r2000-regbits.json → anno-regbits.json} +2 -2
  21. package/anno-register.ts +399 -0
  22. package/anno-store-export.ts +661 -0
  23. package/anno-store.ts +4002 -0
  24. package/anno-symbols.ts +266 -0
  25. package/anno-tools.ts +3264 -0
  26. package/anno-types.ts +1909 -0
  27. package/backend-detect.mts +124 -312
  28. package/block-class.ts +201 -0
  29. package/build.ts +4 -2
  30. package/capture-predicate.ts +597 -0
  31. package/channel-lock.ts +349 -0
  32. package/disasm-decoder.ts +14 -14
  33. package/disasm-opcodes.ts +4 -4
  34. package/disasm-renderer.ts +2 -2
  35. package/evid-ingest.ts +217 -0
  36. package/evid-reconcile.ts +316 -0
  37. package/host-tool-client.ts +430 -0
  38. package/hostpath.ts +1 -1
  39. package/incident-record.ts +23 -12
  40. package/install-resources.ts +30 -14
  41. package/memmap-lookup.ts +285 -0
  42. package/package.json +48 -23
  43. package/prg-image.ts +118 -0
  44. package/repo-root.ts +107 -8
  45. package/resources/backend-detect.mjs +98 -236
  46. package/resources/broker-control.mjs +189 -16
  47. package/resources/broker-epoch.mjs +1 -1
  48. package/resources/broker-kill.mjs +8 -2
  49. package/resources/broker-launch.mjs +373 -214
  50. package/resources/broker-state.mjs +64 -18
  51. package/resources/container-guard.mjs +1 -1
  52. package/resources/ghidra-project.mjs +790 -0
  53. package/resources/host-tool.mjs +2561 -0
  54. package/resources/vice-broker.mjs +330 -184
  55. package/resources/vice-launcher.sh +130 -12
  56. package/stock-address.ts +6 -6
  57. package/stock-cia.ts +2 -2
  58. package/stock-condition.ts +8 -8
  59. package/stock-connect.ts +10 -6
  60. package/stock-derived.ts +29 -37
  61. package/stock-diagnose.ts +200 -36
  62. package/stock-dispatch.ts +200 -68
  63. package/stock-execution.ts +5 -3
  64. package/stock-handler.ts +1 -1
  65. package/stock-input.ts +9 -9
  66. package/stock-machine.ts +17 -6
  67. package/stock-paths.ts +18 -14
  68. package/stock-petscii.ts +1 -1
  69. package/stock-protocol.ts +17 -12
  70. package/stock-recycle.ts +83 -2
  71. package/stock-registers.ts +54 -29
  72. package/stock-reproducible-run.ts +811 -0
  73. package/stock-run-until.ts +100 -1
  74. package/stock-sprites.ts +3 -3
  75. package/stock-symbols.ts +13 -13
  76. package/stock-timing.ts +2 -2
  77. package/stock-vicii.ts +1 -1
  78. package/stop-oracle.ts +167 -0
  79. package/text-capability-probe.ts +660 -0
  80. package/text-connect.ts +157 -0
  81. package/text-protocol.ts +810 -0
  82. package/text-tools.ts +778 -0
  83. package/textmon-backtrace.ts +385 -0
  84. package/textmon-cpuhistory.ts +335 -0
  85. package/textmon-memmap.ts +494 -0
  86. package/textmon-profile.ts +458 -0
  87. package/textmon-registers.ts +748 -0
  88. package/tools-manifest.stock.json +864 -3
  89. package/version.ts +1 -1
  90. package/vice-broker-client.ts +189 -42
  91. package/vice-errors.ts +268 -0
  92. package/vice-proxy.ts +392 -2175
  93. package/vsf-slice.ts +640 -0
  94. package/capability-registry.ts +0 -388
  95. package/r2000-cli.ts +0 -1103
  96. package/r2000-d64.ts +0 -310
  97. package/r2000-enum-gen.ts +0 -574
  98. package/r2000-launch.ts +0 -357
  99. package/r2000-mcp-client.ts +0 -596
  100. package/r2000-project.ts +0 -190
  101. package/r2000-symbols.ts +0 -388
  102. package/r2000-tools.ts +0 -914
  103. package/r2000-verify.ts +0 -184
  104. package/refresh-manifest.ts +0 -124
  105. package/tools-manifest.json +0 -1223
  106. package/vice-probe.ts +0 -278
  107. package/vice-sync.ts +0 -336
  108. package/vice.ts +0 -772
package/r2000-verify.ts DELETED
@@ -1,184 +0,0 @@
1
- #!/usr/bin/env node
2
- // r2000-verify.ts -- the ONE place that interprets regenerator2000's
3
- // `--verify` output.
4
- //
5
- // WHY PARSING IS REQUIRED AT ALL (D-10, the concrete incident): with ACME
6
- // absent from PATH and ca65 present, a real `regenerator2000 0.9.20 --verify`
7
- // run on this host printed
8
- //
9
- // ✗ ACME — ACME not found in PATH (skipped)
10
- // ✓ All roundtrip verifications passed.
11
- // EXIT=0
12
- //
13
- // -- exit 0, and a summary line that reads as a full pass, while the one
14
- // assembler this project actually cares about (`!cpu 6510`, ACME 0.97) never
15
- // ran at all. Trusting the exit code here would let ACME be silently
16
- // skipped and still report success. This is the WHAT-NOT-TO-DO for any
17
- // future edit to this file: never derive `ok` from `status`, ever, no
18
- // matter how tempting a bare zero-exit-status check looks. The verdict this
19
- // module produces comes ONLY from parsing the per-assembler result lines
20
- // and reading ACME's own line -- never from the process exit code, and
21
- // never from the aggregate "All roundtrip verifications passed." summary
22
- // line, which is itself the thing that lied in the transcript above.
23
- //
24
- // Both captured transcripts (the honest pass and this exact false-pass trap)
25
- // are pinned verbatim as fixtures in r2000-verify.test.ts, so a future
26
- // "simplification" back to an exit-code check fails a unit test immediately.
27
- //
28
- // WR-04 (10-REVIEW.md, fixed in plan 11-01): the verdict must also never
29
- // trust just the FIRST ACME result line. `acmeVerdict()` used to select
30
- // ACME's line with a bare array .find() over the first matching entry, so
31
- // a transcript containing both a
32
- // passing and a failing ACME line (a shape --verify has never printed as of
33
- // 0.9.20, but one this defensive parser is explicitly meant to survive)
34
- // reported `ok: true` from the first (passing) line while discarding the
35
- // later failure -- exactly the "misleading success" this module exists to
36
- // refuse. The fix requires UNANIMITY: every parsed ACME line must be `ok`,
37
- // the first non-ok line (if any) drives the verdict, and if more than one
38
- // ACME line is present after passing that check, the module refuses to
39
- // guess which one is authoritative rather than picking one arbitrarily.
40
- // Both the mixed-transcript case and the too-many-ACME-lines case are pinned
41
- // verbatim as fixtures in r2000-verify.test.ts.
42
- //
43
- // Import nothing from `hostpath.ts`/`containerpath.ts` -- plan 10-01's
44
- // absence assertion in `hostpath-consumers.test.ts` already names this file.
45
-
46
- import { buildVerifyArgs, runR2000 } from "./r2000-launch.ts";
47
-
48
- /** The three possible outcomes for a single per-assembler `--verify` result
49
- * line. `"skipped"` and `"ok"` are DIFFERENT outcomes and must never be
50
- * conflated -- a skipped assembler did not run, so it proves nothing, while
51
- * an `ok` assembler was actually invoked and its output byte-diffed. */
52
- export type AssemblerOutcome = "ok" | "skipped" | "failed";
53
-
54
- export interface VerifyLine {
55
- assembler: string;
56
- outcome: AssemblerOutcome;
57
- detail: string;
58
- }
59
-
60
- // Matches exactly a per-assembler result line, e.g.:
61
- // ✓ ACME — byte-identical (44 bytes)
62
- // ✗ 64tass — 64tass not found in PATH (skipped)
63
- // Tolerates an em-dash (U+2014), en-dash (U+2013) or plain hyphen as the
64
- // separator, since only the ACME verdict is load-bearing here and the exact
65
- // glyph regenerator2000 prints is an upstream formatting detail, not
66
- // something this parser should be brittle against. Deliberately does NOT
67
- // match the aggregate "✓ All roundtrip verifications passed." summary line
68
- // (no separator token present there) or the "EXIT=N" line some transcripts
69
- // carry -- both are excluded from the returned array by construction, not
70
- // by a special-cased skip: they simply never match this shape.
71
- const VERIFY_LINE_PATTERN = /^[✓✗]\s+(.+?)\s+[—–-]\s+(.+)$/;
72
-
73
- /**
74
- * Parses `regenerator2000 --verify`'s stdout into one `VerifyLine` per
75
- * per-assembler result line. The aggregate `✓ All roundtrip verifications
76
- * passed.` line is a summary, not an assembler line -- it never matches
77
- * `VERIFY_LINE_PATTERN` (no `—`/`-` separator), so it is excluded from the
78
- * result by construction rather than filtered out after the fact.
79
- */
80
- export function parseVerifyOutput(stdout: string): VerifyLine[] {
81
- const lines: VerifyLine[] = [];
82
- for (const rawLine of stdout.split(/\r?\n/)) {
83
- const trimmed = rawLine.trim();
84
- if (!trimmed) continue;
85
- const match = trimmed.match(VERIFY_LINE_PATTERN);
86
- if (!match) continue;
87
- const [, assemblerRaw, detailRaw] = match;
88
- const assembler = assemblerRaw!.trim();
89
- const detail = detailRaw!.trim();
90
- // A "(skipped)" suffix always means skipped, regardless of the leading
91
- // glyph. Anything else takes its outcome from the leading glyph: a
92
- // leading ✓ is "ok", any other non-skipped result line is "failed" --
93
- // this project has never observed a real ACME/ca65 failure transcript,
94
- // but a future one must not silently parse as "ok".
95
- const outcome: AssemblerOutcome = /\(skipped\)\s*$/i.test(detail)
96
- ? "skipped"
97
- : trimmed.startsWith("✓")
98
- ? "ok"
99
- : "failed";
100
- lines.push({ assembler, outcome, detail });
101
- }
102
- return lines;
103
- }
104
-
105
- /**
106
- * Derives the ACME-specific verdict from a parsed line set. `ok` only when
107
- * at least one ACME line exists, EVERY parsed ACME line has outcome `"ok"`
108
- * (unanimity -- WR-04), AND exactly one ACME line is present. A missing
109
- * ACME line, any ACME line with outcome `"skipped"` or `"failed"`, and more
110
- * than one `"ok"` ACME line each return `ok: false` with a distinct,
111
- * quotable reason -- never conflate "skipped" with "passed", never let a
112
- * passing line hide a later failing one, and never fall back to the summary
113
- * line (which this module never even parses as a VerifyLine, see
114
- * `parseVerifyOutput`).
115
- */
116
- export function acmeVerdict(lines: VerifyLine[]): { ok: boolean; reason: string } {
117
- const acmeLines = lines.filter((l) => l.assembler.toLowerCase() === "acme");
118
-
119
- if (acmeLines.length === 0) {
120
- return {
121
- ok: false,
122
- reason:
123
- "no ACME line found in --verify output -- ACME was never invoked at all, which is a failure, " +
124
- "not an absence of evidence",
125
- };
126
- }
127
-
128
- // First non-ok line wins -- so a passing ACME line earlier in the
129
- // transcript can never hide a failing one later in it (WR-04).
130
- const bad = acmeLines.find((l) => l.outcome !== "ok");
131
-
132
- if (bad?.outcome === "skipped") {
133
- return {
134
- ok: false,
135
- reason:
136
- `ACME was skipped, not run -- "${bad.detail}". A skipped ACME is a failure, never a pass ` +
137
- `(D-10): --verify can print "✓ All roundtrip verifications passed." and exit 0 even when ` +
138
- `ACME never ran at all -- exactly the false pass observed live on this host with ACME absent ` +
139
- `and ca65 present.`,
140
- };
141
- }
142
-
143
- if (bad?.outcome === "failed") {
144
- return { ok: false, reason: `ACME reported a failure: "${bad.detail}"` };
145
- }
146
-
147
- // Every ACME line is ok at this point. Still refuse to guess which one is
148
- // authoritative if more than one was printed (WR-04) -- unanimous is not
149
- // the same as unambiguous.
150
- if (acmeLines.length > 1) {
151
- return {
152
- ok: false,
153
- reason: `--verify printed ${acmeLines.length} ACME result lines -- refusing to guess which one is the verdict`,
154
- };
155
- }
156
-
157
- return { ok: true, reason: `ACME reported: ${acmeLines[0]!.detail}` };
158
- }
159
-
160
- export interface VerifyProjectResult {
161
- ok: boolean;
162
- reason: string;
163
- lines: VerifyLine[];
164
- status: number | null;
165
- stdout: string;
166
- stderr: string;
167
- }
168
-
169
- /**
170
- * Runs `regenerator2000 --verify` against `projectPath` (via
171
- * `buildVerifyArgs()`/`runR2000()`, so the `--vice` scan applies here too),
172
- * parses stdout, and derives `ok` from `acmeVerdict()` -- deliberately NOT
173
- * from `status`. Returns the raw status and streams so a caller can print
174
- * them for diagnostics, but no code path in this function ever lets a zero
175
- * exit status alone make `ok` true. A warning on stderr is never treated as
176
- * a failure -- only the parsed ACME result line decides the verdict.
177
- */
178
- export function verifyProject(projectPath: string): VerifyProjectResult {
179
- const argv = buildVerifyArgs({ projectPath });
180
- const { status, stdout, stderr } = runR2000(argv);
181
- const lines = parseVerifyOutput(stdout);
182
- const verdict = acmeVerdict(lines);
183
- return { ok: verdict.ok, reason: verdict.reason, lines, status, stdout, stderr };
184
- }
@@ -1,124 +0,0 @@
1
- #!/usr/bin/env node
2
- // The ONLY writer of tools-manifest.json (see vice-proxy.mjs's tools/list
3
- // handler doc comment -- that handler is a pure, offline READ of the file
4
- // this CLI produces). An operator runs this by hand against a running host
5
- // to refresh the snapshot; vice-proxy.mjs never imports this file, so this
6
- // file's stdout is a normal CLI stream, not the MCP channel.
7
- //
8
- // Sibling import: the transport module lives in this skill's own scripts/
9
- // directory (plan 01.1-04 relocated it from the now-retired `vice-session`
10
- // skill).
11
- import { serverInfo, activeInstance, type ServerInfoPayload, type ToolInfo } from "./vice.ts";
12
- import { writeFileSync, chmodSync, renameSync } from "node:fs";
13
- import { fileURLToPath } from "node:url";
14
- import { basename, dirname, join, resolve } from "node:path";
15
-
16
- const HERE = dirname(fileURLToPath(import.meta.url));
17
- const DEFAULT_MANIFEST_PATH = join(HERE, "tools-manifest.json");
18
-
19
- // The hand-authored stock surface's own filename (stock-dispatch.ts's
20
- // tools-manifest.stock.json, plan 02-09) -- named here ONLY so
21
- // writeManifestAtomic() below can refuse to ever target it, never as
22
- // something this file writes.
23
- const STOCK_MANIFEST_BASENAME = "tools-manifest.stock.json";
24
-
25
- function manifestPath(): string {
26
- return process.env.VICE_TOOLS_MANIFEST
27
- ? resolve(process.env.VICE_TOOLS_MANIFEST)
28
- : DEFAULT_MANIFEST_PATH;
29
- }
30
-
31
- export interface ToolsManifest {
32
- generated_at: string;
33
- endpoint: string;
34
- tools: ToolInfo[];
35
- }
36
-
37
- /** Write `manifest` to `path` via the tmp sibling -> restrict-mode -> content
38
- * -> rename sequence (01.6.1-06 Decision 1), copied verbatim in shape from
39
- * vice-broker.mts's writeBrokerRecord(). The tmp file is created EMPTY and
40
- * mode-tightened to 0o600 BEFORE any content reaches it, then renamed into
41
- * place -- so a crash or a full disk mid-write can only ever leave a stray
42
- * tmp sibling behind, never a partial or empty file at the real path. This
43
- * is what makes this module's own long-standing promise ("a partial or
44
- * empty manifest is never written over a good one on failure") true against
45
- * a crash, not only against a rejected handshake -- the rejected-handshake
46
- * half of that guarantee already held (the early return below, unchanged
47
- * from the original), but a crash between "open the file" and "write the
48
- * content" could previously still truncate a good manifest to nothing.
49
- *
50
- * T-02-31 (02-09): this function regenerates the manifest from a LIVE fork
51
- * host's tools/list, so its output path must NEVER be
52
- * tools-manifest.stock.json -- that file is the hand-authored, separately
53
- * committed stock surface (D-07/D-09), and this refresh path overwriting it
54
- * with the fork's full tool list would silently destroy the trimming that
55
- * surface exists to enforce. This assertion makes that impossible by
56
- * accident; a future `--stock` flag pointing this generator at the stock
57
- * file has to edit this line deliberately, not merely change a default. */
58
- function writeManifestAtomic(path: string, manifest: ToolsManifest): void {
59
- if (basename(path) === STOCK_MANIFEST_BASENAME) {
60
- throw new Error(
61
- `refresh-manifest: refusing to write ${STOCK_MANIFEST_BASENAME} -- this generator regenerates the fork's ` +
62
- `manifest from a live host and must never overwrite the hand-authored stock surface (D-07/D-09)`
63
- );
64
- }
65
- const tmpPath = `${path}.tmp-${process.pid}-${Date.now()}`;
66
- writeFileSync(tmpPath, "");
67
- chmodSync(tmpPath, 0o600);
68
- writeFileSync(tmpPath, JSON.stringify(manifest, null, 2) + "\n");
69
- renameSync(tmpPath, path);
70
- }
71
-
72
- /** Parses no arguments (there are none), performs the host handshake, and
73
- * writes the manifest -- or leaves the existing one untouched and sets a
74
- * non-zero exit code if the handshake fails. Exported (01.6.1-06 Decision 2)
75
- * so this can be driven directly in a test without spawning a process; the
76
- * bottom-of-module entry-point guard below is what makes that safe -- import
77
- * alone must never perform a handshake or write anything. */
78
- export async function main(): Promise<void> {
79
- const path = manifestPath();
80
-
81
- // serverInfo() performs the host handshake (initialize + tools/list) and
82
- // already strips DENY_LIST names before returning (vice.ts's own
83
- // documented choke point) -- vice-proxy.mjs's tools/list handler applies
84
- // the SAME filter again at read time, so a snapshot generated by any other
85
- // means is still safe, but this refresh path inherits the omission for
86
- // free.
87
- let info: ServerInfoPayload;
88
- try {
89
- info = (await serverInfo()) as ServerInfoPayload;
90
- } catch (e) {
91
- const message = e instanceof Error ? e.message : String(e);
92
- console.error(
93
- `refresh-manifest: could not reach the host VICE MCP server (${message}) -- ` +
94
- `manifest at ${path} left UNCHANGED. A partial or empty manifest is never written over a good one on ` +
95
- `failure -- whether the failure is a rejected handshake (this path) or a crash mid-write, the latter now ` +
96
- `also covered by the tmp-sibling -> restrict-mode -> content -> rename sequence below.`
97
- );
98
- process.exitCode = 1;
99
- return;
100
- }
101
-
102
- const tools = Array.isArray(info?.tools) ? info.tools : [];
103
- const manifest: ToolsManifest = {
104
- generated_at: new Date().toISOString(),
105
- endpoint: activeInstance().url,
106
- tools,
107
- };
108
-
109
- writeManifestAtomic(path, manifest);
110
- console.log(`refresh-manifest: wrote ${tools.length} tool${tools.length === 1 ? "" : "s"} to ${path}`);
111
- }
112
-
113
- // -------------------------------------------------------------------- CLI
114
- // Guarded exactly like vice-broker.mts's own entry point: importing this
115
- // module (e.g. from a test) must never itself perform a handshake or write
116
- // anything. process.exitCode, never process.exit(), so any pending I/O
117
- // flushes first.
118
- if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
119
- main().catch((e: unknown) => {
120
- const detail = e instanceof Error && e.stack ? e.stack : String(e);
121
- console.error(`refresh-manifest: unexpected error -- ${detail}`);
122
- process.exitCode = 1;
123
- });
124
- }