@henols/vice-mcp 0.2.1 → 0.2.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 (57) hide show
  1. package/README.md +2 -1
  2. package/THIRD-PARTY-NOTICES.md +1 -24
  3. package/{r2000-acme-ident.ts → anno-acme-ident.ts} +13 -13
  4. package/anno-cli.ts +1465 -0
  5. package/{r2000-confidence.ts → anno-confidence.ts} +22 -22
  6. package/anno-coverage.ts +2465 -0
  7. package/{r2000-d64.ts → anno-d64.ts} +5 -5
  8. package/anno-derive.ts +590 -0
  9. package/anno-details.ts +169 -0
  10. package/anno-enum-gen.ts +533 -0
  11. package/anno-export-asm.ts +1310 -0
  12. package/anno-index.ts +150 -0
  13. package/{r2000-memmap-render.ts → anno-memmap-render.ts} +236 -95
  14. package/{r2000-regbits-gen.ts → anno-regbits-gen.ts} +20 -15
  15. package/{r2000-regbits.json → anno-regbits.json} +2 -2
  16. package/anno-register.ts +240 -0
  17. package/anno-store.ts +3486 -0
  18. package/anno-symbols.ts +266 -0
  19. package/anno-tools.ts +2111 -0
  20. package/anno-types.ts +1636 -0
  21. package/block-class.ts +201 -0
  22. package/build.ts +1 -1
  23. package/capability-registry.ts +3 -1
  24. package/disasm-decoder.ts +14 -14
  25. package/disasm-opcodes.ts +4 -4
  26. package/disasm-renderer.ts +2 -2
  27. package/hostpath.ts +1 -1
  28. package/install-resources.ts +1 -1
  29. package/package.json +23 -17
  30. package/prg-image.ts +119 -0
  31. package/repo-root.ts +20 -5
  32. package/resources/broker-launch.mjs +8 -4
  33. package/resources/vice-launcher.sh +3 -3
  34. package/stock-address.ts +5 -5
  35. package/stock-cia.ts +2 -2
  36. package/stock-condition.ts +7 -7
  37. package/stock-connect.ts +1 -1
  38. package/stock-dispatch.ts +35 -5
  39. package/stock-execution.ts +5 -3
  40. package/stock-input.ts +9 -9
  41. package/stock-machine.ts +17 -6
  42. package/stock-protocol.ts +16 -11
  43. package/stock-registers.ts +54 -29
  44. package/stock-sprites.ts +3 -3
  45. package/stock-symbols.ts +9 -9
  46. package/stock-timing.ts +1 -1
  47. package/stock-vicii.ts +1 -1
  48. package/version.ts +1 -1
  49. package/vice-proxy.ts +68 -46
  50. package/r2000-cli.ts +0 -1103
  51. package/r2000-enum-gen.ts +0 -574
  52. package/r2000-launch.ts +0 -357
  53. package/r2000-mcp-client.ts +0 -596
  54. package/r2000-project.ts +0 -190
  55. package/r2000-symbols.ts +0 -388
  56. package/r2000-tools.ts +0 -914
  57. package/r2000-verify.ts +0 -184
@@ -0,0 +1,266 @@
1
+ #!/usr/bin/env node
2
+ // anno-symbols.ts -- the ONE authoritative place in this repo for the
3
+ // PRE-SPAWN half of the symbol round trip between an annotation store and
4
+ // stock VICE's symbol table (ANNO-14/ANNO-15, ARCHITECTURE.md Rule A20).
5
+ //
6
+ // WHAT LEFT, WHAT STAYED, AND WHERE THE ROUTE RETURNS (plan 29-10, D-01/D-14,
7
+ // 2026-08-30). Read this paragraph before looking for a function that is not
8
+ // here.
9
+ //
10
+ // WHAT LEFT: the ROUTE. `exportLabels()`, `importLabels()` and
11
+ // `regenerateAndReload()` are gone. All three drove the retired external
12
+ // static-analysis child -- one built and spawned its `--export_lbl` argv,
13
+ // one built its `--import_lbl` argv and held its stdio session open across a
14
+ // save-and-verify, one wrote a name through its curated tool surface before
15
+ // re-exporting. Every module they reached was deleted in the same commit,
16
+ // so the functions could not have been kept in any form that still ran.
17
+ //
18
+ // WHAT STAYED: the KNOWLEDGE. The label-file format is not this module's to
19
+ // re-parse (`stock-symbols.ts` owns it, see below), but the DISCIPLINE
20
+ // around it is this project's own and survives intact:
21
+ // - `validateLabelFileForImport()`, below, is the whole pre-spawn gate the
22
+ // deleted `importLabels()` ran BEFORE any child existed: the byte-size
23
+ // ceiling check (T-11-LBL-SIZE), the single-parser pass, and the
24
+ // per-name ACME identifier validation that REJECTS rather than
25
+ // sanitizes (T-11-NAME-INJECT, closed) while naming the offending name,
26
+ // its 1-based line number and that line's own text. It is unchanged
27
+ // from the code it was lifted out of; it never touched the deleted
28
+ // modules and so had no reason to go with them.
29
+ // - `ImportLabelsResult`'s discriminated union, and the reason it is one:
30
+ // a caller must not be able to read `importedNames` and mistake "the
31
+ // import call returned no error" for "the names are actually on disk".
32
+ // - The three rules of the `.lbl` loop, recorded in WHAT NOT TO DO below:
33
+ // the store is the merge point, `vice_symbols_load` is
34
+ // replace-not-merge so a regeneration is WHOLE, and no second
35
+ // `al C:xxxx .Name` parser is ever added here.
36
+ // - The two measured facts about the retired producer's own behaviour,
37
+ // kept in the past tense because they are why the discipline has the
38
+ // shape it has, not because anything still calls that producer.
39
+ //
40
+ // WHERE THE ROUTE RETURNS: **NO PHASE CURRENTLY OWNS ITS RETURN**, and the
41
+ // earlier version of this line said otherwise. It forecast that the `.lbl`
42
+ // round trip would be rebuilt over the annotation store alongside the ACME
43
+ // export oracle. Half of that happened on 2026-08-31 -- the ACME export
44
+ // route came back as the `anno export-asm` CLI verb -- but the work that
45
+ // rebuilt it covered that route ONLY: no requirement and no success
46
+ // criterion of it mentioned `export-lbl` or `import-lbl`. So the forecast
47
+ // was wrong, and it is CORRECTED here rather than deleted, because deleting
48
+ // a withdrawal notice erases the record that a capability went missing and
49
+ // why. `.planning/PROJECT.md` carries the dated ANNO-14/ANNO-15 notice and
50
+ // says the same thing: this is a temporary loss of a capability that was
51
+ // genuinely Validated, not a completed one being tidied away. The
52
+ // demonstration was made end to end against genuine unpatched stock `x64sc`
53
+ // and that record still stands. A reader checking today whether the symbol
54
+ // round trip works should read this as: it does not, nobody currently owns
55
+ // making it work again, and the specification for whoever eventually does is
56
+ // the measured-facts block below.
57
+ //
58
+ // WHY THIS FILE EXISTS: static-analysis symbols going OUT to VICE and
59
+ // live-discovered symbols coming IN from VICE must flow through explicit
60
+ // adapter code -- neither side may parse the other's internal representation
61
+ // (Rule A20). This module is that adapter's own side of the boundary. It
62
+ // reuses `stock-symbols.ts`'s existing `al C:xxxx .Name` parser
63
+ // (`parseViceLabelFile()`, exported there for exactly this reuse) rather than
64
+ // adding this repo's THIRD copy of that format -- `stock-symbols.ts` and
65
+ // `acme-build/scripts/acme.mjs`'s `curateLabels()` are the two that already
66
+ // exist.
67
+ //
68
+ // MEASURED FACTS, PAST TENSE, kept because they are the reasons for the
69
+ // discipline above rather than instructions to anyone:
70
+ // - `--export_lbl` exported USER labels only. Measured (Phase 9, and
71
+ // re-confirmed by the round-trip test that was deleted with its subject):
72
+ // an annotated project emitted exactly the labels a caller had set, and
73
+ // the auto-generated `a_D011` / `a_D020` / `e_FFD2` externals were NOT
74
+ // exported. A rebuilt exporter that emits an `a_`-prefixed name is
75
+ // emitting something the old one did not.
76
+ // - `--import_lbl` under plain `--headless` DISCARDED. `main.rs:800-806`
77
+ // was `if headless && !mcp_server { return Ok(()) }`: an import ran into
78
+ // memory and then hit that early return without ever calling save, so the
79
+ // import was silently discarded -- measured live: two names imported that
80
+ // way, and a subsequent export read back from disk returned only the
81
+ // pre-existing label. The lesson that outlives the producer is the one
82
+ // `ImportLabelsResult` encodes: never report an import as persisted on
83
+ // the strength of a no-error response.
84
+ //
85
+ // WHAT NOT TO DO, named concretely:
86
+ // - Never add a second `al C:xxxx .Name` regex anywhere in this file.
87
+ // Every read of a label file's TEXT goes through `stock-symbols.ts`'s
88
+ // exported `parseViceLabelFile()` (T-11-LBL-PARSER-DUP).
89
+ // - Never call `vice_symbols_load` (`stock-symbols.ts`'s handlers)
90
+ // incrementally. A rebuilt regeneration path regenerates the WHOLE `.lbl`
91
+ // and returns its path for the CALLER to hand to `vice_symbols_load`
92
+ // exactly once -- `vice_symbols_load` is deliberately replace-not-merge
93
+ // (T-05-02-05), so a full regeneration is what keeps that semantics
94
+ // correct rather than a limitation. A merge mode on `vice_symbols_load`
95
+ // itself was rejected: it would reopen a v0.2.0 decision and make a tool
96
+ // advertised on both backends diverge in semantics.
97
+ // - Never let an illegal label name from a `.lbl` file reach a spawned
98
+ // child (T-11-NAME-INJECT, closed). `validateLabelFileForImport()` below
99
+ // validates every name against `anno-acme-ident.ts`'s
100
+ // `assertLegalAcmeIdentifier()` BEFORE any argv is built -- REJECT, never
101
+ // sanitize. Any rebuilt import route must call it first, for the same
102
+ // reason and in the same position -- the obligation is on the route,
103
+ // whenever one is built, and is not held by a numbered phase.
104
+ import { readFileSync, statSync } from "node:fs";
105
+
106
+ import { parseViceLabelFile, MAX_LABEL_FILE_BYTES } from "./stock-symbols.ts";
107
+ import { assertLegalAcmeIdentifier } from "./anno-acme-ident.ts";
108
+
109
+ /** This module's own error class, minimal shape (message-only, `.name` set to
110
+ * the class name). Never thrown for a ceiling violation on a `.lbl` file's
111
+ * TEXT -- those come from `stock-symbols.ts`'s `StockSymbolsError`, surfaced
112
+ * verbatim, never re-wrapped as this class. Reserved for this module's OWN
113
+ * failure modes: an oversized file caught before `parseViceLabelFile()` is
114
+ * ever called, or (T-11-NAME-INJECT, closed) an illegal label name caught
115
+ * before any child could be spawned -- naming the offending name, its 1-based
116
+ * line number, and that line's own text. */
117
+ export class AnnoSymbolsError extends Error {
118
+ constructor(message: string) {
119
+ super(message);
120
+ this.name = "AnnoSymbolsError";
121
+ }
122
+ }
123
+
124
+ export interface LabelEntry {
125
+ name: string;
126
+ address: number;
127
+ }
128
+
129
+ export interface ExportLabelsOptions {
130
+ projectPath: string;
131
+ outPath: string;
132
+ }
133
+
134
+ export interface ExportLabelsResult {
135
+ path: string;
136
+ symbolCount: number;
137
+ symbols: LabelEntry[];
138
+ skippedLines: number;
139
+ duplicateNames: number;
140
+ lineCount: number;
141
+ }
142
+
143
+ export interface ImportLabelsOptions {
144
+ projectPath: string;
145
+ lblPath: string;
146
+ }
147
+
148
+ export interface ImportLabelsVerified {
149
+ diskVerified: true;
150
+ importedNames: string[];
151
+ /** The fresh, independent export result used to prove persistence -- a
152
+ * caller can inspect it without re-exporting itself. */
153
+ exported: ExportLabelsResult;
154
+ }
155
+
156
+ export interface ImportLabelsUnverified {
157
+ diskVerified: false;
158
+ importedNames: string[];
159
+ /** Names present in the imported `.lbl` file that a fresh export from
160
+ * disk did NOT contain. Always non-empty when `diskVerified` is `false`. */
161
+ missingNames: string[];
162
+ reason: string;
163
+ }
164
+
165
+ /**
166
+ * A discriminated union, deliberately -- `diskVerified: true` and
167
+ * `diskVerified: false` are structurally distinct shapes, so a caller cannot
168
+ * read `result.importedNames` and mistake "the import call returned no error"
169
+ * for "the names are actually on disk".
170
+ *
171
+ * KEPT ACROSS THE CUT (plan 29-10). The route that produced it is gone; the
172
+ * distinction it encodes is the whole lesson of the `--import_lbl` discard
173
+ * measured in this module's header. Because no phase currently owns rebuilding
174
+ * that route, this type is its only surviving contract; whenever a route IS
175
+ * built it is expected to return this shape rather than
176
+ * invent a weaker one. A rebuilt route that returns a bare name list has
177
+ * silently dropped the property this type exists to make unrepresentable.
178
+ */
179
+ export type ImportLabelsResult = ImportLabelsVerified | ImportLabelsUnverified;
180
+
181
+ export interface ValidatedLabelFile {
182
+ /** Every label name the file declares, in file order. */
183
+ names: string[];
184
+ /** The file's full text, already read -- returned so a caller never reads
185
+ * it a second time to locate a line. */
186
+ text: string;
187
+ /** `parseViceLabelFile()`'s own counts, passed through unchanged. */
188
+ symbolCount: number;
189
+ skippedLines: number;
190
+ duplicateNames: number;
191
+ lineCount: number;
192
+ }
193
+
194
+ /**
195
+ * THE PRE-SPAWN GATE (T-11-LBL-SIZE + T-11-NAME-INJECT, both closed) -- the
196
+ * half of the deleted `importLabels()` that never touched a child process,
197
+ * lifted out unchanged when the route around it was removed (plan 29-10).
198
+ *
199
+ * In order, and the order matters:
200
+ * 1. A byte-size check against `stock-symbols.ts`'s own
201
+ * `MAX_LABEL_FILE_BYTES`, so an oversized file is refused before it is
202
+ * even read into memory.
203
+ * 2. One full `parseViceLabelFile()` pass, whose `StockSymbolsError`
204
+ * ceiling violations (`MAX_LABEL_FILE_LINES`/`MAX_SYMBOLS`) propagate
205
+ * VERBATIM rather than being re-wrapped.
206
+ * 3. Every discovered name validated against `anno-acme-ident.ts`'s
207
+ * `assertLegalAcmeIdentifier()`. An illegal name throws
208
+ * `AnnoSymbolsError` naming the offending name, its 1-based line
209
+ * number, and that line's own text -- REJECT, never sanitize.
210
+ *
211
+ * The offending line is located by a substring search over the already-read
212
+ * text, never a second `al C:` regex (this module's header forbids a third
213
+ * parser for that format).
214
+ *
215
+ * A rebuilt import route -- whenever one is built, since no phase currently
216
+ * owns writing it -- calls this FIRST, before it builds any argv. That
217
+ * position is the property T-11-NAME-INJECT was closed on: no illegal name can
218
+ * reach a child, because no child exists yet when this runs.
219
+ */
220
+ export function validateLabelFileForImport({ lblPath }: { lblPath: string }): ValidatedLabelFile {
221
+ let size: number;
222
+ try {
223
+ size = statSync(lblPath).size;
224
+ } catch (err) {
225
+ throw new AnnoSymbolsError(
226
+ `validateLabelFileForImport: could not stat "${lblPath}" (${err instanceof Error ? err.message : String(err)})`,
227
+ );
228
+ }
229
+ if (size > MAX_LABEL_FILE_BYTES) {
230
+ throw new AnnoSymbolsError(
231
+ `validateLabelFileForImport: "${lblPath}" is ${size} bytes, which exceeds the ${MAX_LABEL_FILE_BYTES}-byte ceiling`,
232
+ );
233
+ }
234
+
235
+ const text = readFileSync(lblPath, "utf8");
236
+ // stock-symbols.ts's ONE parser, never a second regex. A ceiling violation
237
+ // here (StockSymbolsError) propagates unmodified.
238
+ const parsed = parseViceLabelFile(text);
239
+ const names = Array.from(parsed.table.byName.keys());
240
+
241
+ for (const name of names) {
242
+ try {
243
+ assertLegalAcmeIdentifier(name, "importLabels label name");
244
+ } catch (err) {
245
+ const reason = err instanceof Error ? err.message : String(err);
246
+ const lines = text.split(/\r?\n/);
247
+ const lineIndex = lines.findIndex((line) => line.includes(name));
248
+ const lineNumber = lineIndex === -1 ? 0 : lineIndex + 1;
249
+ const lineText = lineIndex === -1 ? "(line not found)" : lines[lineIndex];
250
+ throw new AnnoSymbolsError(
251
+ `validateLabelFileForImport: "${lblPath}" line ${lineNumber} carries an illegal label name "${name}" ` +
252
+ `(${reason}) -- line text: ${JSON.stringify(lineText)}. REJECTED, never sanitized or quoted, before ` +
253
+ "any child is spawned.",
254
+ );
255
+ }
256
+ }
257
+
258
+ return {
259
+ names,
260
+ text,
261
+ symbolCount: parsed.symbolCount,
262
+ skippedLines: parsed.skippedLines,
263
+ duplicateNames: parsed.duplicateNames,
264
+ lineCount: parsed.lineCount,
265
+ };
266
+ }