@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/block-class.ts ADDED
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env node
2
+ // block-class.ts
3
+ //
4
+ // The ONE place that translates an annotation store's own block-type
5
+ // vocabulary into a neutral block class. Nothing else in this tree may
6
+ // compare a store block-type string, and nothing else may read a store block
7
+ // listing on the census's behalf (SEAM-03).
8
+ //
9
+ // ---------------------------------------------------------------------------
10
+ // WHY THIS FILE EXISTS
11
+ // ---------------------------------------------------------------------------
12
+ // `anno-coverage.ts` is the coverage instrument: it implements two Validated
13
+ // requirements and measures how much of a binary has actually been reverse-
14
+ // engineered. Its census is a pure function of the raw bytes and the seed set
15
+ // the caller supplies -- deliberately, because deriving completeness from the
16
+ // store's own block table measures the annotator's bookkeeping rather than
17
+ // the annotation.
18
+ //
19
+ // Yet the census's ONLY tie to the external analyser this project rents its
20
+ // annotation store from was four inline comparisons against that analyser's
21
+ // Rust `Display` strings, spread across three code regions and reached from
22
+ // two lookup call sites. Four string literals were the entire reason a
23
+ // 2,292-line instrument looked like glue around somebody else's data model.
24
+ // Any prefix-driven deletion of the rented substrate would have taken the
25
+ // instrument with it.
26
+ //
27
+ // So the tie lives here, in one small module, behind one named function. A
28
+ // later phase swaps THIS module -- the store's vocabulary changes, the census
29
+ // does not. That is the whole point: the boundary is the module, never an
30
+ // argument threaded through the census.
31
+ //
32
+ // The neutral classes are LOWERCASE. That lowercase-ness was once claimed as
33
+ // a protection in its own right -- the argument being that a capitalised
34
+ // store spelling could not accidentally agree with it, so a comparison site
35
+ // left behind somewhere else would get a different answer and move a measured
36
+ // number, loudly. THAT PREMISE IS NOW FALSE, and the loss is recorded here
37
+ // rather than left to be rediscovered: this project's own
38
+ // store's vocabulary is LOWERCASE, and two of its twelve members -- the code
39
+ // spelling and the undefined spelling -- are string-identical to their
40
+ // neutral classes. A left-behind raw comparison against the store's own
41
+ // spelling therefore CAN accidentally agree now, silently, which is exactly
42
+ // what the old rationale promised could not happen.
43
+ //
44
+ // Two guards replace it, and they are why the mapping below is still
45
+ // defended. Neither is a claim in a header:
46
+ //
47
+ // (a) the derived TOTAL cross-check in `block-class.test.ts`. It iterates
48
+ // the store's frozen twelve-member vocabulary from its single home and
49
+ // asserts the class this module returns for EVERY member, with its own
50
+ // non-vacuity assertions on the counts. It reddens the moment either
51
+ // side drifts; a spot check would not.
52
+ // (b) `anno-coverage.test.ts`'s zero-overlap substitutability proof. Its
53
+ // substituted vocabulary (`EXECUTABLE_EXTENT` and its two siblings)
54
+ // shares no string with EITHER accepted vocabulary, so a left-behind
55
+ // comparison site is still observable there -- which is why that
56
+ // vocabulary list is DERIVED from both, not hand-written at four
57
+ // entries.
58
+ //
59
+ // ---------------------------------------------------------------------------
60
+ // WHAT NOT TO DO -- each of these is a specific, named trap
61
+ // ---------------------------------------------------------------------------
62
+ // 1. NEVER accept the census, the raw program bytes, a decoder, or a
63
+ // confidence grade as an argument here. `anno-coverage.ts` records a
64
+ // BYTES-VERSUS-STORE independence axis: one side classifies an address
65
+ // using only the raw bytes and the census, the other using only the
66
+ // store's own documentation, and NEITHER SIDE READS THE OTHER'S INPUT.
67
+ // That axis is the reason its reproducibility figure means anything.
68
+ // One extra argument here collapses it -- and it collapses QUIETLY: the
69
+ // independence test would keep passing while the claim it protects
70
+ // became void. The signature below is two arguments and must stay two.
71
+ // 2. NEVER compare a store vocabulary string outside this module. A second
72
+ // comparison site is a second answer to "what class is this address",
73
+ // and the census must have exactly one.
74
+ // 3. NEVER hold module-level mutable state. The lookup is a pure function
75
+ // of its two arguments, so interleaved or repeated calls cannot observe
76
+ // each other. There is nothing to reset and nothing to synchronise.
77
+ // 4. NEVER import anything census-side, disassembler-side, transport-side
78
+ // or path-translation-side. This module's import list is EMPTY and a
79
+ // committed structural assertion in `block-class.test.ts` keeps it that
80
+ // way -- see trap 1 for why the emptiness is load-bearing rather than
81
+ // tidy.
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // The neutral vocabulary
85
+ // ---------------------------------------------------------------------------
86
+
87
+ /**
88
+ * The three neutral block classes every consumer speaks. Lowercase tokens.
89
+ *
90
+ * These tokens were once ALSO offered as a guard in their own right, on the
91
+ * ground that no store spelling could collide with them. Two of the store's
92
+ * twelve members now do collide, so that reading is gone -- see this file's
93
+ * header for the loss and for the two derived cross-checks that defend the
94
+ * mapping instead.
95
+ *
96
+ * Three-valued and no finer. The consumers compare this against a
97
+ * classification derived from completely different inputs, so a richer
98
+ * vocabulary would manufacture disagreement out of vocabulary drift rather
99
+ * than measure anything.
100
+ */
101
+ export type BlockClass = "code" | "data" | "undefined";
102
+
103
+ /**
104
+ * One entry of a store's block listing, exactly as the curated read tool
105
+ * returns it.
106
+ *
107
+ * `type` carries the STORE'S OWN vocabulary -- whatever spelling the
108
+ * annotation substrate happens to use for its block kinds. This module is
109
+ * the only place in the tree that interprets that field. A consumer that
110
+ * reads `.type` and compares it is re-opening the boundary this file exists
111
+ * to close.
112
+ */
113
+ export interface BlockEntry {
114
+ start_address: number;
115
+ end_address: number;
116
+ /** The store's own block-kind spelling. Interpreted HERE and nowhere else. */
117
+ type: string;
118
+ }
119
+
120
+ /**
121
+ * The shape of a block classifier: given a store's block listing and an
122
+ * address, the neutral class covering that address, or `null` when no block
123
+ * covers it.
124
+ *
125
+ * Named as a type so a consumer can be handed a substitute implementation
126
+ * for a substitutability proof without that consumer ever naming a concrete
127
+ * store vocabulary.
128
+ */
129
+ export type BlockClassifier = (blocks: readonly BlockEntry[], address: number) => BlockClass | null;
130
+
131
+ /**
132
+ * The one production classifier.
133
+ *
134
+ * A linear scan, first-match-wins, with both range ends INCLUSIVE. A
135
+ * `null`/`undefined` hole in the array is skipped rather than thrown on --
136
+ * the listing arrives from a project file this process did not author. That
137
+ * premise applies to `blocks` ITSELF as well, so a non-array argument returns
138
+ * `null` here rather than throwing a `TypeError` out of the `for ... of`
139
+ * (IN-04). Both production callers already pre-guard with
140
+ * `Array.isArray(blocks) ? blocks : []`, so nothing is reachable today; the
141
+ * point is that the defence now lives in the module that DOCUMENTS the
142
+ * premise, instead of only in callers outside it.
143
+ *
144
+ * The mapping is total by construction over BOTH ACCEPTED VOCABULARIES: a
145
+ * code spelling from either becomes `"code"`, an undefined spelling from
146
+ * either becomes `"undefined"`, and EVERY other spelling becomes `"data"` --
147
+ * whether it belongs to one of the two vocabularies or to neither. That
148
+ * fallthrough is not a simplification; it is exactly what the two comparisons
149
+ * this function replaced did when read together, and it is what keeps an
150
+ * unrecognised spelling -- including one from a producer this module has
151
+ * never heard of -- from silently reading as code.
152
+ *
153
+ * The two arms are two VOCABULARIES that happen to differ in case, never one
154
+ * vocabulary compared case-insensitively. A case-insensitive or
155
+ * whitespace-trimming comparison here would silently accept a third spelling
156
+ * nobody chose; `block-class.test.ts` pins that it does not.
157
+ */
158
+ export const blockClassAt: BlockClassifier = (blocks, address) => {
159
+ if (!Array.isArray(blocks)) return null;
160
+ for (const block of blocks) {
161
+ if (!block) continue;
162
+ if (address >= block.start_address && address <= block.end_address) {
163
+ // TWO ACCEPTED VOCABULARIES, one arm each, in the same order so the
164
+ // pairing reads at a glance. Deliberately NOT folded into a
165
+ // case-insensitive test -- see this function's doc comment.
166
+ //
167
+ // This project's own annotation store, whose twelve block types are
168
+ // lowercase and live in one frozen home the test cross-checks against:
169
+ if (block.type === "code") return "code";
170
+ if (block.type === "undefined") return "undefined";
171
+ // TRANSITIONAL -- the capitalised vocabulary, whose PRODUCER (the
172
+ // external analyser this project used to rent an annotation store from,
173
+ // whose Rust `Display` emitted these spellings) IS GONE AS OF PHASE 29,
174
+ // 2026-08-29. The trigger the previous comment named has therefore
175
+ // already fired, and the arm still stands. That is a decision, recorded
176
+ // here rather than left to be rediscovered as an inert branch:
177
+ //
178
+ // WHY IT SURVIVES ITS OWN TRIGGER: every committed coverage fixture
179
+ // under `fixtures/coverage/**/store.json` is still SPELLED in this
180
+ // vocabulary ("Code", "Byte", "Undefined"). Deleting the two arms
181
+ // below today reclassifies every fixture block as `data` -- silently,
182
+ // because `data` is the total fallthrough and no error is raised
183
+ // anywhere. The fixtures are the census's own controls, so that would
184
+ // move the numbers the controls exist to pin.
185
+ //
186
+ // THE NEW REMOVAL TRIGGER is therefore the FIXTURES being re-spelled
187
+ // into the store's own lowercase vocabulary -- not the producer being
188
+ // deleted, which has happened. Re-spell the fixtures (and their
189
+ // generator) first, observe the census unchanged, then delete these
190
+ // two lines.
191
+ //
192
+ // FATE: carried as a Phase 32 guard-fate item ("every guard pinned to
193
+ // the deleted subject has a recorded fate"), so the ledger picks this
194
+ // arm up deliberately instead of finding it red or inert in CI.
195
+ if (block.type === "Code") return "code";
196
+ if (block.type === "Undefined") return "undefined";
197
+ return "data";
198
+ }
199
+ }
200
+ return null;
201
+ };
package/build.ts CHANGED
@@ -48,6 +48,8 @@ export const HOST_BOUND_ARTIFACTS: string[] = [
48
48
  "broker-epoch.mjs",
49
49
  "broker-control.mjs",
50
50
  "backend-detect.mjs",
51
+ "host-tool.mjs",
52
+ "ghidra-project.mjs",
51
53
  ];
52
54
 
53
55
  /** The generated-file banner (01.6-RESEARCH.md §F), a function of the
@@ -61,7 +63,7 @@ export function GENERATED_BANNER(relSourcePath: string): string {
61
63
  `// Compiled by \`tsc\` from ${relSourcePath}. Edit the TypeScript source and rebuild;\n` +
62
64
  "// changes made directly to this file are silently overwritten by the next build, and are never\n" +
63
65
  "// deployed to the host on their own -- install-resources.mjs copies THIS file's on-disk contents\n" +
64
- "// verbatim to tools/, so an edit made only here reaches the host but is lost on the very next\n" +
66
+ "// verbatim to .c64-re-tools/bin/, so an edit made only here reaches the host but is lost on the very next\n" +
65
67
  "// rebuild.\n"
66
68
  );
67
69
  }
@@ -138,7 +140,7 @@ export interface BuildOptions {
138
140
  * `renameSync()` per artifact, and `rename(2)` fails `EXDEV` across mounts.
139
141
  * That is why the original implementation staged at `dirname(outDirAbs)`.
140
142
  * 2. **Outside any directory a test walks.** Staging at `dirname(outDirAbs)`
141
- * put a transient `.build-tmp-*` inside `.claude/mcp/vice/`, and
143
+ * put a transient `.build-tmp-*` inside `src/mcp/vice/`, and
142
144
  * `vice-mcp-selector-docs.test.ts`'s `walkFiles()` recurses through every
143
145
  * directory there except `node_modules` — so a concurrent walk descended
144
146
  * into the staging dir and died `ENOENT` when the rename removed it. That