@henols/vice-mcp 0.2.2 → 0.2.4
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/README.md +2 -2
- package/THIRD-PARTY-NOTICES.md +422 -1
- package/anno-bank.ts +171 -0
- package/anno-cli.ts +1736 -163
- package/anno-confidence.ts +2 -2
- package/anno-derive.ts +6 -6
- package/anno-details.ts +4 -4
- package/anno-enum-gen.ts +416 -30
- package/anno-export-asm.ts +1211 -126
- package/anno-graphics.ts +338 -0
- package/anno-hazard-report.ts +1367 -0
- package/anno-import.ts +495 -0
- package/anno-index.ts +8 -8
- package/anno-join.ts +480 -0
- package/anno-memmap-render.ts +22 -21
- package/anno-provenance-ledger.ts +472 -0
- package/anno-regbits-gen.ts +13 -13
- package/anno-register.ts +159 -0
- package/anno-store-export.ts +661 -0
- package/anno-store.ts +635 -124
- package/anno-symbols.ts +7 -7
- package/anno-tools.ts +1169 -16
- package/anno-types.ts +313 -40
- package/backend-detect.mts +124 -312
- package/build.ts +3 -1
- package/capture-predicate.ts +597 -0
- package/channel-lock.ts +349 -0
- package/evid-ingest.ts +217 -0
- package/evid-reconcile.ts +316 -0
- package/host-tool-client.ts +430 -0
- package/incident-record.ts +23 -12
- package/install-resources.ts +29 -13
- package/memmap-lookup.ts +285 -0
- package/package.json +27 -8
- package/prg-image.ts +1 -2
- package/repo-root.ts +87 -3
- package/resources/backend-detect.mjs +98 -236
- package/resources/broker-control.mjs +220 -54
- package/resources/broker-epoch.mjs +7 -8
- package/resources/broker-kill.mjs +36 -31
- package/resources/broker-launch.mjs +511 -374
- package/resources/broker-state.mjs +69 -24
- package/resources/container-guard.mjs +1 -1
- package/resources/ghidra-project.mjs +790 -0
- package/resources/host-tool.mjs +2533 -0
- package/resources/vice-broker.mjs +434 -290
- package/resources/vice-launcher.sh +127 -9
- package/stock-address.ts +1 -1
- package/stock-condition.ts +1 -1
- package/stock-connect.ts +9 -5
- package/stock-derived.ts +29 -37
- package/stock-diagnose.ts +200 -36
- package/stock-dispatch.ts +179 -77
- package/stock-handler.ts +1 -1
- package/stock-paths.ts +18 -14
- package/stock-petscii.ts +1 -1
- package/stock-protocol.ts +1 -1
- package/stock-recycle.ts +83 -2
- package/stock-reproducible-run.ts +811 -0
- package/stock-run-until.ts +100 -1
- package/stock-symbols.ts +4 -4
- package/stock-timing.ts +1 -1
- package/stop-oracle.ts +167 -0
- package/text-capability-probe.ts +660 -0
- package/text-connect.ts +157 -0
- package/text-protocol.ts +810 -0
- package/text-tools.ts +778 -0
- package/textmon-backtrace.ts +385 -0
- package/textmon-cpuhistory.ts +335 -0
- package/textmon-memmap.ts +494 -0
- package/textmon-profile.ts +458 -0
- package/textmon-registers.ts +748 -0
- package/tools-manifest.stock.json +864 -3
- package/vice-broker-client.ts +253 -108
- package/vice-errors.ts +268 -0
- package/vice-proxy.ts +339 -2144
- package/vsf-slice.ts +640 -0
- package/anno-d64.ts +0 -310
- package/capability-registry.ts +0 -390
- package/refresh-manifest.ts +0 -124
- package/tools-manifest.json +0 -1223
- package/vice-probe.ts +0 -278
- package/vice-sync.ts +0 -336
- package/vice.ts +0 -772
package/memmap-lookup.ts
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// memmap-lookup.ts
|
|
3
|
+
//
|
|
4
|
+
// Phase 37, plan 37-01 (IMP-01/AUTO-01's shared foundation): the ONE loader
|
|
5
|
+
// for `c64-memory-mapping`'s own `memmap.json`, its content digest, and
|
|
6
|
+
// narrowest-containing-range selection over its 959 entries.
|
|
7
|
+
//
|
|
8
|
+
// THIS MODULE MUST NEVER IMPORT `hostpath.ts` OR `containerpath.ts` -- every
|
|
9
|
+
// path here is repo-relative and derived from this module's own location, the
|
|
10
|
+
// same posture `anno-regbits-gen.ts` and `dxa-blocks.ts` take for themselves.
|
|
11
|
+
// It also NEVER NAMES `node:sqlite` and NEVER OPENS THE ANNOTATION STORE:
|
|
12
|
+
// `anno-store.ts` is the one module `anno-seam.test.ts` allows to name that
|
|
13
|
+
// dependency, and this module answers a pure question about a static JSON
|
|
14
|
+
// file that has nothing to do with the store's own persistence.
|
|
15
|
+
//
|
|
16
|
+
// D-37-03: `anno-regbits-gen.ts:62` already computes the SAME `HERE`-relative
|
|
17
|
+
// five-hop path to `memmap.json` and `anno-regbits-gen.ts:377` already
|
|
18
|
+
// computes its sha256 digest, for a different purpose (bit-name generation).
|
|
19
|
+
// This module COPIES that formula rather than importing the generator --
|
|
20
|
+
// `anno-regbits-gen.ts`'s own header declares itself "the sole read of
|
|
21
|
+
// c64-memory-mapping's own memmap.json ... for this purpose", and widening
|
|
22
|
+
// that consumer set is a separate decision this plan does not make.
|
|
23
|
+
//
|
|
24
|
+
// WHAT THIS IS THE ONE AUTHORITATIVE PLACE FOR: loading and caching
|
|
25
|
+
// `memmap.json`'s 959 entries, computing the file's own sha256 provenance
|
|
26
|
+
// digest, and the NARROWEST-CONTAINING-RANGE selection rule an address
|
|
27
|
+
// resolves through. `anno-join.ts` calls this module; nothing else needs to.
|
|
28
|
+
//
|
|
29
|
+
// WHAT NOT TO DO:
|
|
30
|
+
// - Never re-derive `MEMMAP_PATH` from a different relative offset. This
|
|
31
|
+
// formula has already broken once when the skills tree moved (plan
|
|
32
|
+
// 16-01's own deviation note) -- one HERE-relative constant, copied
|
|
33
|
+
// verbatim in shape from `anno-regbits-gen.ts:62`.
|
|
34
|
+
// - Never mutate the cached entries array. `loadMemmap()` returns the same
|
|
35
|
+
// frozen array on every call after the first; a caller that needs a
|
|
36
|
+
// filtered view copies it.
|
|
37
|
+
// - Never derive "is this range bank-conditional" from `memmap.json`'s own
|
|
38
|
+
// fields. `BANK_CONDITIONAL_RANGES` below is HAND-MAINTAINED on purpose
|
|
39
|
+
// (37-RESEARCH.md Pitfall 2) -- the schema carries no structured
|
|
40
|
+
// bank-condition field, only free prose inside `desc`.
|
|
41
|
+
|
|
42
|
+
import { createHash } from "node:crypto";
|
|
43
|
+
import { readFileSync } from "node:fs";
|
|
44
|
+
import { dirname, join } from "node:path";
|
|
45
|
+
import { fileURLToPath } from "node:url";
|
|
46
|
+
|
|
47
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
48
|
+
|
|
49
|
+
/** The `HERE`-relative path to `c64-memory-mapping`'s `memmap.json`, using the
|
|
50
|
+
* SAME five-hop formula `anno-regbits-gen.ts:62` uses. Copied, not imported
|
|
51
|
+
* (D-37-03). */
|
|
52
|
+
export const MEMMAP_PATH = join(HERE, "..", "..", "..", "src", "skills", "c64-memory-mapping", "memmap.json");
|
|
53
|
+
|
|
54
|
+
/** One entry as `memmap.json` holds it. MEASURED at plan time: 959 entries,
|
|
55
|
+
* `start`/`end` are INCLUSIVE integers with no length field, and `sym` is
|
|
56
|
+
* present on 219 of them. */
|
|
57
|
+
export interface MemmapEntry {
|
|
58
|
+
start: number;
|
|
59
|
+
end: number;
|
|
60
|
+
label: string;
|
|
61
|
+
section: string;
|
|
62
|
+
desc: string;
|
|
63
|
+
src: string;
|
|
64
|
+
sym?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface MemmapDocument {
|
|
68
|
+
sources: unknown[];
|
|
69
|
+
entries: MemmapEntry[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Module-level cache, exactly as `anno-enum-gen.ts`'s `loadRegBits()` caches
|
|
73
|
+
* its own generated table -- `memmap.json` is read and parsed once per
|
|
74
|
+
* process, never per call. */
|
|
75
|
+
let cachedEntries: readonly MemmapEntry[] | undefined;
|
|
76
|
+
|
|
77
|
+
/** Reads and parses `memmap.json`, caching the parsed array. Refuses BY NAME
|
|
78
|
+
* if the top-level shape is not `{sources, entries}` or if `entries` is
|
|
79
|
+
* empty -- a malformed or truncated memmap.json must not read as "a memmap
|
|
80
|
+
* with nothing in it". */
|
|
81
|
+
export function loadMemmap(): readonly MemmapEntry[] {
|
|
82
|
+
if (cachedEntries !== undefined) return cachedEntries;
|
|
83
|
+
|
|
84
|
+
let parsed: unknown;
|
|
85
|
+
try {
|
|
86
|
+
parsed = JSON.parse(readFileSync(MEMMAP_PATH, "utf8"));
|
|
87
|
+
} catch (err) {
|
|
88
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
89
|
+
throw new Error(`memmap-lookup: ${MEMMAP_PATH} could not be read/parsed as JSON (${reason}).`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const doc = parsed as Partial<MemmapDocument> | null;
|
|
93
|
+
if (doc === null || typeof doc !== "object" || !Array.isArray(doc.sources) || !Array.isArray(doc.entries)) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`memmap-lookup: ${MEMMAP_PATH} is not a { sources, entries } document -- refusing to guess a shape for a ` +
|
|
96
|
+
"malformed memmap.json.",
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (doc.entries.length === 0) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`memmap-lookup: ${MEMMAP_PATH}'s "entries" array is empty -- "a memmap with nothing in it" and "a memmap ` +
|
|
102
|
+
'that failed to load" must not read the same, so this refuses rather than returning an empty selector.',
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
cachedEntries = Object.freeze(doc.entries.slice());
|
|
107
|
+
return cachedEntries;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The sha256 hex digest of `memmap.json`'s RAW BYTES -- the provenance value
|
|
111
|
+
* `AUTO-08` (a later plan) appends to derived comment text. MEASURED at plan
|
|
112
|
+
* time: this equals the same value `anno-regbits.json`'s committed banner
|
|
113
|
+
* carries (`_generated.memmapSha256`), and the test that pins this asserts
|
|
114
|
+
* that RELATION rather than a hard-coded literal, because the map is a living
|
|
115
|
+
* file. */
|
|
116
|
+
export function memmapDigest(): string {
|
|
117
|
+
return createHash("sha256").update(readFileSync(MEMMAP_PATH)).digest("hex");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* D-37-13: the fixed, machine-parseable prefix `anno-join.ts` appends
|
|
122
|
+
* `memmapDigest()`'s full 64-character lowercase hex digest to, on every
|
|
123
|
+
* derived comment (`AUTO-08`). Chosen to be unlikely to collide with
|
|
124
|
+
* ordinary comment prose and to sit LAST in the comment text (no closing
|
|
125
|
+
* delimiter follows it), so a regex can find it without knowing the
|
|
126
|
+
* preceding label's own shape: `new RegExp(PROVENANCE_TOKEN_PREFIX_ESCAPED +
|
|
127
|
+
* "[0-9a-f]{64}$")`. Exported from here, not `anno-join.ts`, because the
|
|
128
|
+
* digest and the prefix that names it belong to the same module as
|
|
129
|
+
* `memmapDigest()` itself.
|
|
130
|
+
*/
|
|
131
|
+
export const PROVENANCE_TOKEN_PREFIX = "[memmap-sha256:";
|
|
132
|
+
|
|
133
|
+
/** WHICH of the three steps decided a selection, or `unique` when only one
|
|
134
|
+
* entry contained the address at all. `AUTO-02` names two steps
|
|
135
|
+
* (narrowest-range-wins, then the `sym` tie-break); D-37-10 adds a third,
|
|
136
|
+
* because the phase's own headline `$D020` example is NOT resolved by the
|
|
137
|
+
* first two -- MEASURED at plan time, its two 1-byte contenders both lack a
|
|
138
|
+
* `sym`. Reported rather than left for a caller to infer, so a test (and
|
|
139
|
+
* plan 37-04's controls) can assert WHICH rule decided, not merely what it
|
|
140
|
+
* decided. */
|
|
141
|
+
export type MemmapTieBreak = "unique" | "width" | "symbol" | "order";
|
|
142
|
+
|
|
143
|
+
/** What `selectMemmapEntry()` returns for a unique or a resolved-tie hit.
|
|
144
|
+
* `contenderCount` is the number of containing entries the scan considered,
|
|
145
|
+
* so a caller can tell a unique hit from a resolved tie without re-running
|
|
146
|
+
* the scan itself. */
|
|
147
|
+
export interface MemmapSelection {
|
|
148
|
+
entry: MemmapEntry;
|
|
149
|
+
width: number;
|
|
150
|
+
contenderCount: number;
|
|
151
|
+
tieBrokenBy: MemmapTieBreak;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Inclusive width of one entry: `end - start`. Named once so every step
|
|
155
|
+
* below computes it identically. */
|
|
156
|
+
function inclusiveWidth(entry: MemmapEntry): number {
|
|
157
|
+
return entry.end - entry.start;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* STEP ONE, WIDTH -- the whole of narrowest-range-wins. Prefers the smallest
|
|
162
|
+
* `end - start`; returns every entry tied at that minimum, since a single
|
|
163
|
+
* winner here is `unique`ly correct only when nothing else shares its width.
|
|
164
|
+
* Kept as its own named function (not folded into a single comparator) so
|
|
165
|
+
* plan 37-04's control -- "switch selection to first-match" -- is a single,
|
|
166
|
+
* small, textual replacement of exactly this step, per this plan's own
|
|
167
|
+
* `<read_first>` instruction.
|
|
168
|
+
*/
|
|
169
|
+
function narrowestWidthSurvivors(containing: readonly MemmapEntry[]): MemmapEntry[] {
|
|
170
|
+
let minWidth = Infinity;
|
|
171
|
+
for (const entry of containing) {
|
|
172
|
+
const width = inclusiveWidth(entry);
|
|
173
|
+
if (width < minWidth) minWidth = width;
|
|
174
|
+
}
|
|
175
|
+
return containing.filter((entry) => inclusiveWidth(entry) === minWidth);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* STEP TWO, SYMBOL -- `AUTO-02`'s own tie-break. Among step one's survivors,
|
|
180
|
+
* prefers an entry carrying a non-empty `sym` over one that does not.
|
|
181
|
+
* D-37-11's `$0000` fixture (three equal-width contenders, exactly one
|
|
182
|
+
* carrying `sym: "D6510"`) is this step's own fixture. Returns every
|
|
183
|
+
* `sym`-carrying survivor when at least one exists, else returns every
|
|
184
|
+
* survivor unchanged (this step decided nothing -- step three must run).
|
|
185
|
+
*/
|
|
186
|
+
function symbolSurvivors(survivors: readonly MemmapEntry[]): MemmapEntry[] {
|
|
187
|
+
const withSym = survivors.filter((entry) => typeof entry.sym === "string" && entry.sym.length > 0);
|
|
188
|
+
return withSym.length > 0 ? withSym : survivors.slice();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* STEP THREE, ORDER -- D-37-10. Among step two's survivors, the entry
|
|
193
|
+
* appearing FIRST in `memmap.json`'s own `entries` array wins. Stated
|
|
194
|
+
* explicitly, rather than left to whatever order a scan happened to
|
|
195
|
+
* produce, because the phase's own headline `$D020` example needs it: two
|
|
196
|
+
* 1-byte contenders there are tied on width AND neither carries a `sym`, so
|
|
197
|
+
* without this named third rule the selection would be an unstated,
|
|
198
|
+
* scan-order accident that a later refactor could change silently. No
|
|
199
|
+
* `sort()` is used here -- a single linear scan over `entries` (the same
|
|
200
|
+
* order the caller supplied) finds the first survivor, which is cheaper and
|
|
201
|
+
* keeps this step's own mutation (plan 37-05's "reverse the tie-break")
|
|
202
|
+
* a one-line replacement rather than a sort-comparator edit.
|
|
203
|
+
*/
|
|
204
|
+
function orderWinner(survivors: readonly MemmapEntry[], entries: readonly MemmapEntry[]): MemmapEntry {
|
|
205
|
+
for (const entry of entries) {
|
|
206
|
+
if (survivors.includes(entry)) return entry;
|
|
207
|
+
}
|
|
208
|
+
// Unreachable: `survivors` is always drawn from `entries` by reference, so
|
|
209
|
+
// the scan above always finds one before falling through.
|
|
210
|
+
return survivors[0]!;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* The THREE-DEEP selection order (D-37-10): narrowest-containing-range,
|
|
215
|
+
* then the `sym` tie-break (`AUTO-02`), then -- because those two do not
|
|
216
|
+
* resolve every real tie in the committed `memmap.json` -- the stated
|
|
217
|
+
* residual rule of "first in `entries` order". Collects every entry whose
|
|
218
|
+
* inclusive `[start, end]` contains `address`; returns `undefined` if none
|
|
219
|
+
* does. `tieBrokenBy` names WHICH step decided, so a caller (and plan
|
|
220
|
+
* 37-04's controls) can tell width-decided from symbol-decided from
|
|
221
|
+
* order-decided, not merely infer it from the winning entry's own shape.
|
|
222
|
+
*/
|
|
223
|
+
export function selectMemmapEntry(address: number, entries: readonly MemmapEntry[] = loadMemmap()): MemmapSelection | undefined {
|
|
224
|
+
const containing: MemmapEntry[] = [];
|
|
225
|
+
for (const entry of entries) {
|
|
226
|
+
if (address < entry.start || address > entry.end) continue;
|
|
227
|
+
containing.push(entry);
|
|
228
|
+
}
|
|
229
|
+
const contenderCount = containing.length;
|
|
230
|
+
if (contenderCount === 0) return undefined;
|
|
231
|
+
if (contenderCount === 1) {
|
|
232
|
+
const entry = containing[0]!;
|
|
233
|
+
return { entry, width: inclusiveWidth(entry), contenderCount, tieBrokenBy: "unique" };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const widthSurvivors = narrowestWidthSurvivors(containing);
|
|
237
|
+
if (widthSurvivors.length === 1) {
|
|
238
|
+
const entry = widthSurvivors[0]!;
|
|
239
|
+
return { entry, width: inclusiveWidth(entry), contenderCount, tieBrokenBy: "width" };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const symSurvivors = symbolSurvivors(widthSurvivors);
|
|
243
|
+
if (symSurvivors.length === 1) {
|
|
244
|
+
const entry = symSurvivors[0]!;
|
|
245
|
+
return { entry, width: inclusiveWidth(entry), contenderCount, tieBrokenBy: "symbol" };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const entry = orderWinner(symSurvivors, entries);
|
|
249
|
+
return { entry, width: inclusiveWidth(entry), contenderCount, tieBrokenBy: "order" };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** One hand-maintained bank-conditional range: its meaning depends on the
|
|
253
|
+
* processor port's bits #0-#2 at `$0001`. */
|
|
254
|
+
export interface BankConditionalRange {
|
|
255
|
+
start: number;
|
|
256
|
+
end: number;
|
|
257
|
+
why: string;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* The three inclusive ranges whose interpretation depends on the processor
|
|
262
|
+
* port, HAND-MAINTAINED rather than derived from `memmap.json` (37-RESEARCH.md
|
|
263
|
+
* Pitfall 2): only 6 of 959 entries even mention the condition in free-text
|
|
264
|
+
* `desc`, and there is no structured bank-condition field to scan instead.
|
|
265
|
+
* Nothing in THIS module branches on this list -- a later plan's bank-decode
|
|
266
|
+
* logic does. Landed here so the list has one home rather than being
|
|
267
|
+
* reinvented per caller.
|
|
268
|
+
*/
|
|
269
|
+
export const BANK_CONDITIONAL_RANGES: readonly BankConditionalRange[] = Object.freeze([
|
|
270
|
+
{
|
|
271
|
+
start: 0xa000,
|
|
272
|
+
end: 0xbfff,
|
|
273
|
+
why: "BASIC ROM when banked in, otherwise RAM (bits #0-#2 of $0001 select between them).",
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
start: 0xd000,
|
|
277
|
+
end: 0xdfff,
|
|
278
|
+
why: "I/O area, Character ROM, or RAM depending on $0001 bits #0-#2 -- the canonical three-way case.",
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
start: 0xe000,
|
|
282
|
+
end: 0xffff,
|
|
283
|
+
why: "KERNAL ROM when banked in, otherwise RAM (bits #0-#2 of $0001 select between them).",
|
|
284
|
+
},
|
|
285
|
+
]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henols/vice-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "VICE emulator MCP server for C64 reverse-engineering: a stdio MCP server that proxies vice tools to a host VICE MCP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,23 +9,24 @@
|
|
|
9
9
|
"main": "vice-proxy.ts",
|
|
10
10
|
"files": [
|
|
11
11
|
"vice-proxy.ts",
|
|
12
|
-
"vice.ts",
|
|
13
|
-
"vice-sync.ts",
|
|
14
|
-
"vice-probe.ts",
|
|
12
|
+
"vice-errors.ts",
|
|
15
13
|
"vice-broker-client.ts",
|
|
14
|
+
"host-tool-client.ts",
|
|
16
15
|
"version.ts",
|
|
17
16
|
"stock-protocol.ts",
|
|
18
17
|
"stock-connect.ts",
|
|
18
|
+
"text-protocol.ts",
|
|
19
|
+
"text-connect.ts",
|
|
20
|
+
"text-tools.ts",
|
|
21
|
+
"channel-lock.ts",
|
|
19
22
|
"containerpath.ts",
|
|
20
23
|
"hostpath.ts",
|
|
21
24
|
"repo-root.ts",
|
|
22
25
|
"install-resources.ts",
|
|
23
26
|
"incident-record.ts",
|
|
24
|
-
"refresh-manifest.ts",
|
|
25
27
|
"build.ts",
|
|
26
28
|
"container-guard.mts",
|
|
27
29
|
"backend-detect.mts",
|
|
28
|
-
"capability-registry.ts",
|
|
29
30
|
"stock-dispatch.ts",
|
|
30
31
|
"stock-derived.ts",
|
|
31
32
|
"stock-handler.ts",
|
|
@@ -44,6 +45,12 @@
|
|
|
44
45
|
"disasm-opcodes.ts",
|
|
45
46
|
"disasm-decoder.ts",
|
|
46
47
|
"disasm-renderer.ts",
|
|
48
|
+
"textmon-memmap.ts",
|
|
49
|
+
"textmon-cpuhistory.ts",
|
|
50
|
+
"textmon-backtrace.ts",
|
|
51
|
+
"textmon-profile.ts",
|
|
52
|
+
"textmon-registers.ts",
|
|
53
|
+
"text-capability-probe.ts",
|
|
47
54
|
"stock-memory-search.ts",
|
|
48
55
|
"stock-symbols.ts",
|
|
49
56
|
"stock-vicii.ts",
|
|
@@ -51,9 +58,9 @@
|
|
|
51
58
|
"stock-sprites.ts",
|
|
52
59
|
"stock-timing.ts",
|
|
53
60
|
"stock-run-until.ts",
|
|
61
|
+
"stock-reproducible-run.ts",
|
|
54
62
|
"stock-diagnose.ts",
|
|
55
63
|
"stock-recycle.ts",
|
|
56
|
-
"anno-d64.ts",
|
|
57
64
|
"anno-cli.ts",
|
|
58
65
|
"anno-symbols.ts",
|
|
59
66
|
"anno-regbits-gen.ts",
|
|
@@ -63,18 +70,30 @@
|
|
|
63
70
|
"anno-confidence.ts",
|
|
64
71
|
"anno-memmap-render.ts",
|
|
65
72
|
"anno-export-asm.ts",
|
|
73
|
+
"anno-provenance-ledger.ts",
|
|
66
74
|
"anno-coverage.ts",
|
|
67
75
|
"block-class.ts",
|
|
76
|
+
"anno-hazard-report.ts",
|
|
77
|
+
"evid-reconcile.ts",
|
|
78
|
+
"evid-ingest.ts",
|
|
68
79
|
"prg-image.ts",
|
|
80
|
+
"vsf-slice.ts",
|
|
81
|
+
"capture-predicate.ts",
|
|
82
|
+
"stop-oracle.ts",
|
|
69
83
|
"anno-types.ts",
|
|
70
84
|
"anno-index.ts",
|
|
71
85
|
"anno-store.ts",
|
|
86
|
+
"anno-store-export.ts",
|
|
72
87
|
"anno-tools.ts",
|
|
73
88
|
"anno-derive.ts",
|
|
74
89
|
"anno-details.ts",
|
|
75
90
|
"anno-register.ts",
|
|
91
|
+
"anno-import.ts",
|
|
92
|
+
"anno-join.ts",
|
|
93
|
+
"anno-bank.ts",
|
|
94
|
+
"anno-graphics.ts",
|
|
95
|
+
"memmap-lookup.ts",
|
|
76
96
|
"resources",
|
|
77
|
-
"tools-manifest.json",
|
|
78
97
|
"tools-manifest.stock.json",
|
|
79
98
|
"README.md",
|
|
80
99
|
"THIRD-PARTY-NOTICES.md"
|
package/prg-image.ts
CHANGED
|
@@ -36,8 +36,7 @@
|
|
|
36
36
|
// census) imports `decodeRawData`, `parsePrg` and `flatImageOrigin` for the
|
|
37
37
|
// same three facts on the CLI's route. `scripts/check-npm-packages.mjs` walks
|
|
38
38
|
// that closure over `files[]` and fails the pack the moment a reachable module
|
|
39
|
-
// sits outside the listed set
|
|
40
|
-
// for the same reason.
|
|
39
|
+
// sits outside the listed set.
|
|
41
40
|
//
|
|
42
41
|
// CORRECTED 2026-08-30 (WR-07, plan 29-16). This paragraph previously named
|
|
43
42
|
// `anno-cli.ts` as importing `parsePrg` and `flatImageOrigin` and rested the
|
package/repo-root.ts
CHANGED
|
@@ -182,11 +182,95 @@ export function repoRoot({ from = HERE, env = process.env, exists = existsSync }
|
|
|
182
182
|
return resolve(from, "..", "..", "..");
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
+
/** The ONE definition of the tool-written root every writer in this codebase
|
|
186
|
+
* ultimately derives its location from (D-33, 2026-09-08 clean-break
|
|
187
|
+
* consolidation): `join(repoRoot(...), ".c64-re-tools")`.
|
|
188
|
+
*
|
|
189
|
+
* CORRECTED 2026-09-08 (gap `G-40-1`; see
|
|
190
|
+
* .planning/notes/ghidra-dot-path-check-semantics.md): this comment used to
|
|
191
|
+
* claim (a) that the Ghidra runs directory was among the writers resolving
|
|
192
|
+
* through THIS function, and (b) that the literal string below had exactly
|
|
193
|
+
* one non-comment occurrence in the codebase. Both were false when written,
|
|
194
|
+
* and neither was ever measured before being written down. The corrected
|
|
195
|
+
* picture:
|
|
196
|
+
*
|
|
197
|
+
* - FIVE files call `toolsDir()`/`supervisorDir()` directly, across FOUR
|
|
198
|
+
* distinct subdirectories: incident-record.ts (`incidents`),
|
|
199
|
+
* stock-paths.ts (`snapshots`), vice-proxy.ts (`bin`, reading back the
|
|
200
|
+
* deployed launcher path), and vice.ts + vice-broker-client.ts, both via
|
|
201
|
+
* `supervisorDir()` (`supervisor`, the broker state directory -- one
|
|
202
|
+
* writer, two readers).
|
|
203
|
+
* - FOUR files cannot import this container-side module at all, so each
|
|
204
|
+
* joins `".c64-re-tools"` with its own trailing segment(s) directly,
|
|
205
|
+
* matching this function's shape by CONVENTION, never by shared code:
|
|
206
|
+
* install-resources.ts's `installTargetDir()` (`bin` -- module-cycle
|
|
207
|
+
* avoidance, since THIS file's own bottom-of-module call invokes it),
|
|
208
|
+
* vice-broker.mts's `parseArgs()` state-dir fallback (`supervisor` --
|
|
209
|
+
* host-bound, compiled separately by build.ts), host-tool.mts's
|
|
210
|
+
* `oracle.run` scratch directory (`runs/oracle` -- host-bound), and
|
|
211
|
+
* ghidra-project.mts's `ghidraRunsRoot()`/`ghidraRunsRealRoot()`
|
|
212
|
+
* (`runs/ghidra` -- host-bound, reached through a symlinked alias
|
|
213
|
+
* handle, see below). Every one of these four must keep its literal
|
|
214
|
+
* equal to `join(toolsDir(...), <same segments>)`, by convention, or the
|
|
215
|
+
* two halves of this codebase silently disagree on where the root is.
|
|
216
|
+
*
|
|
217
|
+
* The Ghidra runs root is emphatically NOT, and never was, one of the five
|
|
218
|
+
* files that call this function directly -- it is host-bound
|
|
219
|
+
* (ghidra-project.mts) and cannot import this file at all.
|
|
220
|
+
*
|
|
221
|
+
* The literal string ".c64-re-tools" therefore has exactly 6 non-comment
|
|
222
|
+
* occurrences in this codebase, across 5 files. repo-root.test.ts's census
|
|
223
|
+
* gate reads BOTH the count and this file list straight out of this
|
|
224
|
+
* sentence and the bullet list below -- never duplicated by hand a second
|
|
225
|
+
* time in the test -- and compares both against the real tree, with a
|
|
226
|
+
* planted-violation control proving the comparison predicate actually
|
|
227
|
+
* fires. That is the mechanism that stops this specific claim going false
|
|
228
|
+
* again (threat `T-40-10-02`):
|
|
229
|
+
* - repo-root.ts -- this definition, the line below (1)
|
|
230
|
+
* - install-resources.ts -- `installTargetDir()`'s `bin` join (1)
|
|
231
|
+
* - vice-broker.mts -- `parseArgs()`'s state-dir fallback, BOTH branches
|
|
232
|
+
* of one ternary on the same line (2)
|
|
233
|
+
* - ghidra-project.mts -- `GHIDRA_RUNS_HANDLE_TARGET`, the alias handle's
|
|
234
|
+
* relative symlink target (1)
|
|
235
|
+
* - host-tool.mts -- `oracle.run`'s scratch-directory join (1)
|
|
236
|
+
*
|
|
237
|
+
* The Ghidra alias handle: a non-dotted sibling of this root
|
|
238
|
+
* (`<repoRoot>/c64-re-tools`, no leading dot), a symlink whose RELATIVE
|
|
239
|
+
* target is this root's own directory name, minted host-side by the broker
|
|
240
|
+
* at startup (vice-broker.mts) and re-asserted, idempotently, as a
|
|
241
|
+
* precondition by ghidra-project.mts's `ensureGhidraRunsHandle()` on every
|
|
242
|
+
* resolve -- refused BY NAME, never repaired, when something unexpected
|
|
243
|
+
* already sits at the handle path. It exists because Ghidra's own
|
|
244
|
+
* project-location refusal binds the ABSOLUTIZED path argument it is
|
|
245
|
+
* handed (`ProjectLocator` calls `java.io.File.getAbsolutePath()`, never
|
|
246
|
+
* `getCanonicalPath()` -- MEASURED from the class's own bytecode -- so it
|
|
247
|
+
* absolutizes a relative argument but does not resolve a symlink), so a
|
|
248
|
+
* tool that refuses a dot-prefixed segment in the path it is HANDED can
|
|
249
|
+
* still be pointed, indirectly, at bytes that live physically inside this
|
|
250
|
+
* one root. The superseded method that produced the original overstated
|
|
251
|
+
* claim was running this project's OWN dot-segment-refusal check
|
|
252
|
+
* (`hasDotPrefixedSegment()`) against a synthetic string -- which observes
|
|
253
|
+
* this project, never Ghidra. See
|
|
254
|
+
* .planning/notes/ghidra-dot-path-check-semantics.md for the full live
|
|
255
|
+
* measurement against real Ghidra 12.1.3.
|
|
256
|
+
*
|
|
257
|
+
* This is a clean break, not a migration: no code path falls back to any of
|
|
258
|
+
* the five previous locations when the new one is absent, and there is no
|
|
259
|
+
* opt-back-in environment variable. A pre-existing tree at one of the old
|
|
260
|
+
* locations is simply left on disk, unread, for the user to delete by hand.
|
|
261
|
+
* `VICE_POOL_DIR` / `VICE_EPOCH_FILE` / `VICE_SUPERVISOR_DIR` /
|
|
262
|
+
* `VICE_INCIDENTS_DIR` are unaffected by this move -- they still override
|
|
263
|
+
* their respective resolved default, exactly as before; only the DEFAULT
|
|
264
|
+
* moved. */
|
|
265
|
+
export function toolsDir(opts: RepoRootOptions = {}): string {
|
|
266
|
+
return join(repoRoot(opts), ".c64-re-tools");
|
|
267
|
+
}
|
|
268
|
+
|
|
185
269
|
/** The one shared directory name every module in this skill reads/writes
|
|
186
|
-
* host-synchronised state through -- `join(
|
|
187
|
-
*
|
|
270
|
+
* host-synchronised state through -- `join(toolsDir(...), "supervisor")`,
|
|
271
|
+
* a subdirectory of the single tool-written root `toolsDir()` owns. */
|
|
188
272
|
export function supervisorDir(opts: RepoRootOptions = {}): string {
|
|
189
|
-
return join(
|
|
273
|
+
return join(toolsDir(opts), "supervisor");
|
|
190
274
|
}
|
|
191
275
|
|
|
192
276
|
// Fires once per process, on whatever entry point happens to import THIS
|