@henols/vice-mcp 1.0.0 → 1.1.0
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/THIRD-PARTY-NOTICES.md +6 -4
- package/anno-cli.ts +1 -1
- package/anno-store.ts +1 -1
- package/backend-detect.mts +143 -19
- package/build.ts +42 -1
- package/evid-ingest.ts +7 -3
- package/host-tool-client.ts +7 -3
- package/package.json +3 -1
- package/prerequisites.json +416 -0
- package/prg-image.ts +1 -1
- package/repo-root.ts +19 -8
- package/resources/backend-detect.mjs +90 -18
- package/resources/broker-launch.mjs +11 -2
- package/resources/ghidra-project.mjs +2 -2
- package/resources/host-tool.mjs +306 -82
- package/resources/prerequisites.json +416 -0
- package/resources/tool-location.mjs +750 -0
- package/resources/vice-broker.mjs +85 -12
- package/stock-checkpoints.ts +2 -2
- package/stock-connect.ts +12 -10
- package/stock-execution.ts +4 -2
- package/stock-memory-search.ts +3 -2
- package/stock-protocol.ts +60 -28
- package/stock-registers.ts +3 -1
- package/text-protocol.ts +6 -3
- package/textmon-profile.ts +8 -3
- package/version.ts +34 -246
- package/vice-proxy.ts +23 -23
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"tools": {
|
|
4
|
+
"x64sc": {
|
|
5
|
+
"id": "x64sc",
|
|
6
|
+
"location": {
|
|
7
|
+
"envVar": "VICE_BIN",
|
|
8
|
+
"fileOverridable": true
|
|
9
|
+
},
|
|
10
|
+
"kind": "executable",
|
|
11
|
+
"unblocks": {
|
|
12
|
+
"skills": [
|
|
13
|
+
"c64-program-recon",
|
|
14
|
+
"c64-ram-capture",
|
|
15
|
+
"vice-wedge-triage"
|
|
16
|
+
],
|
|
17
|
+
"mcp": [
|
|
18
|
+
"c1541.bam",
|
|
19
|
+
"c1541.dir",
|
|
20
|
+
"c1541.entry",
|
|
21
|
+
"c1541.chain",
|
|
22
|
+
"c1541.read",
|
|
23
|
+
"petcat.decode"
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"remedies": {
|
|
27
|
+
"linux": [
|
|
28
|
+
{
|
|
29
|
+
"ecosystem": "debian-trixie",
|
|
30
|
+
"text": "sudo apt install vice (enable the contrib component first — VICE ships there, not main; a stock debian:trixie installation has no vice candidate until it is)",
|
|
31
|
+
"provenance": "carried",
|
|
32
|
+
"source": "README.md:101"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"ecosystem": "debian-forky",
|
|
36
|
+
"text": "sudo apt install vice (same contrib-component requirement as trixie)",
|
|
37
|
+
"provenance": "carried",
|
|
38
|
+
"source": "README.md:102"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"ecosystem": "ubuntu-2510",
|
|
42
|
+
"text": "sudo apt install vice (enable multiverse first)",
|
|
43
|
+
"provenance": "carried",
|
|
44
|
+
"source": "README.md:103"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"ecosystem": "arch",
|
|
48
|
+
"text": "sudo pacman -S vice",
|
|
49
|
+
"provenance": "carried",
|
|
50
|
+
"source": "README.md:104"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"ecosystem": "fedora-rpmfusion",
|
|
54
|
+
"text": "enable RPM Fusion Non-Free, then sudo dnf install vice",
|
|
55
|
+
"provenance": "carried",
|
|
56
|
+
"source": "README.md:105"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"ecosystem": "alpine-edge",
|
|
60
|
+
"text": "enable the edge/testing repo, then apk add vice",
|
|
61
|
+
"provenance": "carried",
|
|
62
|
+
"source": "README.md:106"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"ecosystem": "homebrew",
|
|
66
|
+
"text": "brew install vice",
|
|
67
|
+
"provenance": "carried",
|
|
68
|
+
"source": "README.md:107"
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"darwin": [
|
|
72
|
+
{
|
|
73
|
+
"ecosystem": "homebrew",
|
|
74
|
+
"text": "brew install vice",
|
|
75
|
+
"provenance": "carried",
|
|
76
|
+
"source": "README.md:107"
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
"win32": [
|
|
80
|
+
{
|
|
81
|
+
"ecosystem": "windows-official",
|
|
82
|
+
"text": "download the GTK3/SDL2 win64 zip",
|
|
83
|
+
"provenance": "carried",
|
|
84
|
+
"source": "README.md:108"
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
"c1541": {
|
|
90
|
+
"id": "c1541",
|
|
91
|
+
"location": {
|
|
92
|
+
"fileOverridable": true
|
|
93
|
+
},
|
|
94
|
+
"kind": "executable",
|
|
95
|
+
"unblocks": {
|
|
96
|
+
"skills": [
|
|
97
|
+
"c64-disk-access"
|
|
98
|
+
],
|
|
99
|
+
"mcp": [
|
|
100
|
+
"c1541.bam",
|
|
101
|
+
"c1541.dir",
|
|
102
|
+
"c1541.entry",
|
|
103
|
+
"c1541.chain",
|
|
104
|
+
"c1541.read"
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
"remedies": {
|
|
108
|
+
"linux": [
|
|
109
|
+
{
|
|
110
|
+
"ecosystem": "debian-trixie",
|
|
111
|
+
"text": "sudo apt install vice (enable the contrib component first — VICE ships there, not main; a stock debian:trixie installation has no vice candidate until it is)",
|
|
112
|
+
"provenance": "carried",
|
|
113
|
+
"source": "README.md:101"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"ecosystem": "debian-forky",
|
|
117
|
+
"text": "sudo apt install vice (same contrib-component requirement as trixie)",
|
|
118
|
+
"provenance": "carried",
|
|
119
|
+
"source": "README.md:102"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"ecosystem": "ubuntu-2510",
|
|
123
|
+
"text": "sudo apt install vice (enable multiverse first)",
|
|
124
|
+
"provenance": "carried",
|
|
125
|
+
"source": "README.md:103"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"ecosystem": "arch",
|
|
129
|
+
"text": "sudo pacman -S vice",
|
|
130
|
+
"provenance": "carried",
|
|
131
|
+
"source": "README.md:104"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"ecosystem": "fedora-rpmfusion",
|
|
135
|
+
"text": "enable RPM Fusion Non-Free, then sudo dnf install vice",
|
|
136
|
+
"provenance": "carried",
|
|
137
|
+
"source": "README.md:105"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"ecosystem": "alpine-edge",
|
|
141
|
+
"text": "enable the edge/testing repo, then apk add vice",
|
|
142
|
+
"provenance": "carried",
|
|
143
|
+
"source": "README.md:106"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"ecosystem": "homebrew",
|
|
147
|
+
"text": "brew install vice",
|
|
148
|
+
"provenance": "carried",
|
|
149
|
+
"source": "README.md:107"
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
"darwin": [
|
|
153
|
+
{
|
|
154
|
+
"ecosystem": "homebrew",
|
|
155
|
+
"text": "brew install vice",
|
|
156
|
+
"provenance": "carried",
|
|
157
|
+
"source": "README.md:107"
|
|
158
|
+
}
|
|
159
|
+
],
|
|
160
|
+
"win32": [
|
|
161
|
+
{
|
|
162
|
+
"ecosystem": "windows-official",
|
|
163
|
+
"text": "download the GTK3/SDL2 win64 zip",
|
|
164
|
+
"provenance": "carried",
|
|
165
|
+
"source": "README.md:108"
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"petcat": {
|
|
171
|
+
"id": "petcat",
|
|
172
|
+
"location": {
|
|
173
|
+
"fileOverridable": true
|
|
174
|
+
},
|
|
175
|
+
"kind": "executable",
|
|
176
|
+
"unblocks": {
|
|
177
|
+
"skills": [
|
|
178
|
+
"c64-petcat",
|
|
179
|
+
"c64-program-recon"
|
|
180
|
+
],
|
|
181
|
+
"mcp": [
|
|
182
|
+
"petcat.decode"
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
"remedies": {
|
|
186
|
+
"linux": [
|
|
187
|
+
{
|
|
188
|
+
"ecosystem": "debian-trixie",
|
|
189
|
+
"text": "sudo apt install vice (enable the contrib component first — VICE ships there, not main; a stock debian:trixie installation has no vice candidate until it is)",
|
|
190
|
+
"provenance": "carried",
|
|
191
|
+
"source": "README.md:101"
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"ecosystem": "debian-forky",
|
|
195
|
+
"text": "sudo apt install vice (same contrib-component requirement as trixie)",
|
|
196
|
+
"provenance": "carried",
|
|
197
|
+
"source": "README.md:102"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"ecosystem": "ubuntu-2510",
|
|
201
|
+
"text": "sudo apt install vice (enable multiverse first)",
|
|
202
|
+
"provenance": "carried",
|
|
203
|
+
"source": "README.md:103"
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"ecosystem": "arch",
|
|
207
|
+
"text": "sudo pacman -S vice",
|
|
208
|
+
"provenance": "carried",
|
|
209
|
+
"source": "README.md:104"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"ecosystem": "fedora-rpmfusion",
|
|
213
|
+
"text": "enable RPM Fusion Non-Free, then sudo dnf install vice",
|
|
214
|
+
"provenance": "carried",
|
|
215
|
+
"source": "README.md:105"
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"ecosystem": "alpine-edge",
|
|
219
|
+
"text": "enable the edge/testing repo, then apk add vice",
|
|
220
|
+
"provenance": "carried",
|
|
221
|
+
"source": "README.md:106"
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"ecosystem": "homebrew",
|
|
225
|
+
"text": "brew install vice",
|
|
226
|
+
"provenance": "carried",
|
|
227
|
+
"source": "README.md:107"
|
|
228
|
+
}
|
|
229
|
+
],
|
|
230
|
+
"darwin": [
|
|
231
|
+
{
|
|
232
|
+
"ecosystem": "homebrew",
|
|
233
|
+
"text": "brew install vice",
|
|
234
|
+
"provenance": "carried",
|
|
235
|
+
"source": "README.md:107"
|
|
236
|
+
}
|
|
237
|
+
],
|
|
238
|
+
"win32": [
|
|
239
|
+
{
|
|
240
|
+
"ecosystem": "windows-official",
|
|
241
|
+
"text": "download the GTK3/SDL2 win64 zip",
|
|
242
|
+
"provenance": "carried",
|
|
243
|
+
"source": "README.md:108"
|
|
244
|
+
}
|
|
245
|
+
]
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
"acme": {
|
|
249
|
+
"id": "acme",
|
|
250
|
+
"location": {
|
|
251
|
+
"envVar": "ACME_BIN",
|
|
252
|
+
"fileOverridable": true
|
|
253
|
+
},
|
|
254
|
+
"kind": "executable",
|
|
255
|
+
"unblocks": {
|
|
256
|
+
"skills": [
|
|
257
|
+
"acme-build"
|
|
258
|
+
],
|
|
259
|
+
"mcp": [
|
|
260
|
+
"acme.build"
|
|
261
|
+
]
|
|
262
|
+
},
|
|
263
|
+
"remedies": {
|
|
264
|
+
"linux": [
|
|
265
|
+
{
|
|
266
|
+
"ecosystem": "ubuntu",
|
|
267
|
+
"text": "sudo apt-get install -y acme",
|
|
268
|
+
"provenance": "measured",
|
|
269
|
+
"source": ".github/workflows/ci.yml:78"
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"ecosystem": "debian",
|
|
273
|
+
"text": "sudo apt-get install -y acme",
|
|
274
|
+
"provenance": "carried",
|
|
275
|
+
"source": ".github/workflows/ci.yml:48"
|
|
276
|
+
}
|
|
277
|
+
],
|
|
278
|
+
"universal": [
|
|
279
|
+
{
|
|
280
|
+
"ecosystem": "generic",
|
|
281
|
+
"text": "Install ACME.",
|
|
282
|
+
"provenance": "carried",
|
|
283
|
+
"source": "src/skills/acme-build/SKILL.md:254"
|
|
284
|
+
}
|
|
285
|
+
]
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
"acme-lib": {
|
|
289
|
+
"id": "acme-lib",
|
|
290
|
+
"location": {
|
|
291
|
+
"envVar": "ACME",
|
|
292
|
+
"fileOverridable": true
|
|
293
|
+
},
|
|
294
|
+
"kind": "directory",
|
|
295
|
+
"marker": "cbm/c64/vic.a",
|
|
296
|
+
"unblocks": {
|
|
297
|
+
"skills": [
|
|
298
|
+
"acme-build"
|
|
299
|
+
],
|
|
300
|
+
"mcp": [
|
|
301
|
+
"acme.build"
|
|
302
|
+
]
|
|
303
|
+
},
|
|
304
|
+
"remedies": {
|
|
305
|
+
"universal": [
|
|
306
|
+
{
|
|
307
|
+
"ecosystem": "generic",
|
|
308
|
+
"text": "export ACME=<dir holding cbm/c64/vic.a>.",
|
|
309
|
+
"provenance": "carried",
|
|
310
|
+
"source": "src/skills/acme-build/SKILL.md:255"
|
|
311
|
+
}
|
|
312
|
+
]
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
"ghidra": {
|
|
316
|
+
"id": "ghidra",
|
|
317
|
+
"location": {
|
|
318
|
+
"envVar": "GHIDRA_HOME",
|
|
319
|
+
"fileOverridable": true
|
|
320
|
+
},
|
|
321
|
+
"kind": "directory",
|
|
322
|
+
"marker": "support/analyzeHeadless",
|
|
323
|
+
"unblocks": {
|
|
324
|
+
"skills": [
|
|
325
|
+
"c64-program-recon"
|
|
326
|
+
],
|
|
327
|
+
"mcp": [
|
|
328
|
+
"ghidra.analyze",
|
|
329
|
+
"ghidra.installExtension"
|
|
330
|
+
]
|
|
331
|
+
},
|
|
332
|
+
"remedies": {
|
|
333
|
+
"universal": [
|
|
334
|
+
{
|
|
335
|
+
"ecosystem": "generic",
|
|
336
|
+
"text": "Set the GHIDRA_HOME environment variable to name a Ghidra installation directory.",
|
|
337
|
+
"provenance": "carried",
|
|
338
|
+
"source": "src/mcp/vice/host-tool.mts:1352"
|
|
339
|
+
}
|
|
340
|
+
]
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
"dxa": {
|
|
344
|
+
"id": "dxa",
|
|
345
|
+
"location": {
|
|
346
|
+
"fileOverridable": false,
|
|
347
|
+
"reason": "dxa is vendored and built by this project, so an override could only select a binary it did not build and did not pin."
|
|
348
|
+
},
|
|
349
|
+
"kind": "executable",
|
|
350
|
+
"unblocks": {
|
|
351
|
+
"skills": [
|
|
352
|
+
"routine-queue-walker"
|
|
353
|
+
],
|
|
354
|
+
"mcp": [
|
|
355
|
+
"dxa.disassemble"
|
|
356
|
+
]
|
|
357
|
+
},
|
|
358
|
+
"remedies": {
|
|
359
|
+
"universal": [
|
|
360
|
+
{
|
|
361
|
+
"ecosystem": "generic",
|
|
362
|
+
"text": "bash vendor/dxa/build.bash build",
|
|
363
|
+
"provenance": "carried",
|
|
364
|
+
"source": "src/mcp/vice/host-tool.mts:1472"
|
|
365
|
+
}
|
|
366
|
+
]
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
"node": {
|
|
370
|
+
"id": "node",
|
|
371
|
+
"versionFloor": ">=24.0.0",
|
|
372
|
+
"location": {
|
|
373
|
+
"fileOverridable": false,
|
|
374
|
+
"reason": "vice-launcher.sh is bash and reads VICE_BROKER_NODE before any working Node exists to parse JSON with."
|
|
375
|
+
},
|
|
376
|
+
"kind": "executable",
|
|
377
|
+
"unblocks": {
|
|
378
|
+
"skills": [
|
|
379
|
+
"acme-build",
|
|
380
|
+
"c64-disk-access",
|
|
381
|
+
"c64-memory-mapping",
|
|
382
|
+
"c64-petcat",
|
|
383
|
+
"c64-program-recon",
|
|
384
|
+
"c64-provenance-diff",
|
|
385
|
+
"c64-ram-capture",
|
|
386
|
+
"routine-queue-walker",
|
|
387
|
+
"vice-wedge-triage"
|
|
388
|
+
],
|
|
389
|
+
"mcp": [
|
|
390
|
+
"acme.build",
|
|
391
|
+
"ghidra.analyze",
|
|
392
|
+
"oracle.probe",
|
|
393
|
+
"oracle.run",
|
|
394
|
+
"dxa.disassemble",
|
|
395
|
+
"ghidra.installExtension",
|
|
396
|
+
"c1541.bam",
|
|
397
|
+
"c1541.dir",
|
|
398
|
+
"c1541.entry",
|
|
399
|
+
"c1541.chain",
|
|
400
|
+
"c1541.read",
|
|
401
|
+
"petcat.decode"
|
|
402
|
+
]
|
|
403
|
+
},
|
|
404
|
+
"remedies": {
|
|
405
|
+
"universal": [
|
|
406
|
+
{
|
|
407
|
+
"ecosystem": "generic",
|
|
408
|
+
"text": "Install Node >= v24 and put it on PATH, or set VICE_BROKER_NODE to an absolute path to one.",
|
|
409
|
+
"provenance": "carried",
|
|
410
|
+
"source": "src/mcp/vice/resources/vice-launcher.sh:219"
|
|
411
|
+
}
|
|
412
|
+
]
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
package/prg-image.ts
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
// `flatImageOrigin` from here with a plain top-level import, and `vice-proxy.ts`
|
|
35
35
|
// imports `anno-tools.ts` statically. `anno-coverage.ts` (the byte-coverage
|
|
36
36
|
// census) imports `decodeRawData`, `parsePrg` and `flatImageOrigin` for the
|
|
37
|
-
// same three facts on the CLI's route.
|
|
37
|
+
// same three facts on the CLI's route. The published closure once walked
|
|
38
38
|
// that closure over `files[]` and fails the pack the moment a reachable module
|
|
39
39
|
// sits outside the listed set.
|
|
40
40
|
//
|
package/repo-root.ts
CHANGED
|
@@ -200,17 +200,21 @@ export function repoRoot({ from = HERE, env = process.env, exists = existsSync }
|
|
|
200
200
|
* deployed launcher path), and vice.ts + vice-broker-client.ts, both via
|
|
201
201
|
* `supervisorDir()` (`supervisor`, the broker state directory -- one
|
|
202
202
|
* writer, two readers).
|
|
203
|
-
* -
|
|
203
|
+
* - FIVE files cannot import this container-side module at all, so each
|
|
204
204
|
* joins `".c64-re-tools"` with its own trailing segment(s) directly,
|
|
205
205
|
* matching this function's shape by CONVENTION, never by shared code:
|
|
206
206
|
* install-resources.ts's `installTargetDir()` (`bin` -- module-cycle
|
|
207
207
|
* avoidance, since THIS file's own bottom-of-module call invokes it),
|
|
208
208
|
* vice-broker.mts's `parseArgs()` state-dir fallback (`supervisor` --
|
|
209
|
-
* host-bound, compiled separately by build.ts)
|
|
210
|
-
*
|
|
209
|
+
* host-bound, compiled separately by build.ts) and its `run()` tool-
|
|
210
|
+
* location deps (the bare root -- same host-bound reason), host-tool.mts's
|
|
211
|
+
* `oracle.run` scratch directory (`runs/oracle` -- host-bound),
|
|
211
212
|
* ghidra-project.mts's `ghidraRunsRoot()`/`ghidraRunsRealRoot()`
|
|
212
213
|
* (`runs/ghidra` -- host-bound, reached through a symlinked alias
|
|
213
|
-
* handle, see below)
|
|
214
|
+
* handle, see below), and backend-detect.mts's `resolvedBackend()`
|
|
215
|
+
* cwd-relative fallback (the bare root -- host-bound, and the ONE place
|
|
216
|
+
* the emulator binary's own location is resolved, Phase 60 LOC-01/LOC-02).
|
|
217
|
+
* Every one of these five must keep its literal
|
|
214
218
|
* equal to `join(toolsDir(...), <same segments>)`, by convention, or the
|
|
215
219
|
* two halves of this codebase silently disagree on where the root is.
|
|
216
220
|
*
|
|
@@ -218,8 +222,8 @@ export function repoRoot({ from = HERE, env = process.env, exists = existsSync }
|
|
|
218
222
|
* files that call this function directly -- it is host-bound
|
|
219
223
|
* (ghidra-project.mts) and cannot import this file at all.
|
|
220
224
|
*
|
|
221
|
-
* The literal string ".c64-re-tools" therefore has exactly
|
|
222
|
-
* occurrences in this codebase, across
|
|
225
|
+
* The literal string ".c64-re-tools" therefore has exactly 10 non-comment
|
|
226
|
+
* occurrences in this codebase, across 6 files. repo-root.test.ts's census
|
|
223
227
|
* gate reads BOTH the count and this file list straight out of this
|
|
224
228
|
* sentence and the bullet list below -- never duplicated by hand a second
|
|
225
229
|
* time in the test -- and compares both against the real tree, with a
|
|
@@ -229,10 +233,17 @@ export function repoRoot({ from = HERE, env = process.env, exists = existsSync }
|
|
|
229
233
|
* - repo-root.ts -- this definition, the line below (1)
|
|
230
234
|
* - install-resources.ts -- `installTargetDir()`'s `bin` join (1)
|
|
231
235
|
* - vice-broker.mts -- `parseArgs()`'s state-dir fallback, BOTH branches
|
|
232
|
-
* of one ternary on the same line (2)
|
|
236
|
+
* of one ternary on the same line (2), plus `run()`'s `toolsDir` for the
|
|
237
|
+
* once-per-process emulator-binary resolution (1) = 3
|
|
233
238
|
* - ghidra-project.mts -- `GHIDRA_RUNS_HANDLE_TARGET`, the alias handle's
|
|
234
239
|
* relative symlink target (1)
|
|
235
|
-
* - host-tool.mts -- `oracle.run`'s scratch-directory join (1)
|
|
240
|
+
* - host-tool.mts -- `oracle.run`'s scratch-directory join (1), plus
|
|
241
|
+
* Phase 60 (LOC-01, plan 60-03)'s `HostToolLocator` plumbing:
|
|
242
|
+
* `locatorFrom()`'s `process.cwd()`-derived fallback (1) and
|
|
243
|
+
* `runHostTool()`'s own locator built from its already-resolved
|
|
244
|
+
* `repoRootAbs` (1) = 3
|
|
245
|
+
* - backend-detect.mts -- `resolvedBackend()`'s cwd-relative `toolsDir`
|
|
246
|
+
* fallback, used only when no caller supplied one (1)
|
|
236
247
|
*
|
|
237
248
|
* The Ghidra alias handle: a non-dotted sibling of this root
|
|
238
249
|
* (`<repoRoot>/c64-re-tools`, no leading dot), a symlink whose RELATIVE
|
|
@@ -47,8 +47,34 @@
|
|
|
47
47
|
// is the exact failure class the 2026-08-01 triple-launch outage came
|
|
48
48
|
// from (D-03/T-02-25) -- unchanged reasoning from before this plan, only
|
|
49
49
|
// the mechanism inside resolvedBackend() changed.
|
|
50
|
+
//
|
|
51
|
+
// Phase 60 (LOC-01/LOC-02, PD-01/PD-02/PD-03): resolvedBackend() gains a real
|
|
52
|
+
// VALUE dependency on the tool-location seam (tool-location.mts) for the
|
|
53
|
+
// emulator binary's own resolution -- when neither `viceBin` nor
|
|
54
|
+
// `resolveBinPath` is injected, resolution goes through the seam's
|
|
55
|
+
// `resolveTool("x64sc", ...)` instead of this file's own ordering, so a
|
|
56
|
+
// `.c64-re-tools/tools.json` entry for `x64sc` now changes what this broker
|
|
57
|
+
// actually spawns. `defaultResolveBinPath()` collapses into a thin wrapper
|
|
58
|
+
// over the seam's own exported `resolveOnPath()` -- the first of Phase 59
|
|
59
|
+
// D-02's three independent `$PATH`-walk copies to collapse.
|
|
60
|
+
//
|
|
61
|
+
// This file ships two ways (see the cache-section comment below): unbuilt,
|
|
62
|
+
// imported directly by container-side .ts (vice-proxy.ts's own `import *
|
|
63
|
+
// as backendDetect from "./backend-detect.mts"`), and compiled into
|
|
64
|
+
// resources/ for the host (vice-broker.mts's own `./backend-detect.mjs`
|
|
65
|
+
// value import). A STATIC `import ... from "./tool-location.mjs"` would
|
|
66
|
+
// resolve in only the SECOND form -- that file exists as a real sibling
|
|
67
|
+
// only once both are compiled into resources/, never beside the unbuilt
|
|
68
|
+
// source. Loading it instead through node:module's `createRequire()`
|
|
69
|
+
// (`toolLocationSeam()` below) defers resolution to the call site rather
|
|
70
|
+
// than parse time, so trying the compiled sibling first and falling back to
|
|
71
|
+
// the unbuilt source sibling keeps this ONE seam call working in both
|
|
72
|
+
// shipped forms -- one implementation, no #ifdef-style split, and every
|
|
73
|
+
// existing unbuilt importer of this file needs no change at all.
|
|
50
74
|
import { existsSync, readFileSync, writeFileSync, chmodSync, renameSync, mkdirSync, statSync, } from "node:fs";
|
|
51
|
-
import {
|
|
75
|
+
import { dirname, join } from "node:path";
|
|
76
|
+
import { fileURLToPath } from "node:url";
|
|
77
|
+
import { createRequire } from "node:module";
|
|
52
78
|
/** The client-side capability-decision schema version stamped into every
|
|
53
79
|
* capability record this module writes (see BackendCacheRecord's own field
|
|
54
80
|
* comment). BUMP THIS whenever the client's capability probing or the
|
|
@@ -124,20 +150,36 @@ function writeCacheRecordAtomic(supervisorDir, record) {
|
|
|
124
150
|
writeFileSync(tmpPath, JSON.stringify(record, null, 2) + "\n");
|
|
125
151
|
renameSync(tmpPath, finalPath);
|
|
126
152
|
}
|
|
153
|
+
/** This module's own directory. Computed once, purely to seed
|
|
154
|
+
* `toolLocationSeam()`'s two-candidate join below -- mirrors
|
|
155
|
+
* `tool-location.mts`'s own `HERE` constant and its `readDeclaration()`
|
|
156
|
+
* "beside `here`, take the first that exists" idiom exactly. */
|
|
157
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
158
|
+
/** Loads the tool-location seam through `node:module`'s `createRequire()`
|
|
159
|
+
* rather than a static ESM import -- see this file's own header for why a
|
|
160
|
+
* static specifier cannot work in both of this file's two shipped forms.
|
|
161
|
+
* `require()` resolves at THIS call site, not at parse time, so trying the
|
|
162
|
+
* compiled resources/ sibling first and falling back to the unbuilt source
|
|
163
|
+
* sibling (the exact two-candidate order `tool-location.mts`'s own
|
|
164
|
+
* `readDeclaration()` already uses for `prerequisites.json`) lets the SAME
|
|
165
|
+
* source file resolve correctly whichever way this file itself was loaded.
|
|
166
|
+
* Never memoised here -- `resolvedBackend()`'s own `memoisedResult` already
|
|
167
|
+
* ensures this runs at most once in the one production path that reaches
|
|
168
|
+
* it, and a test process that resets that memo between scenarios must be
|
|
169
|
+
* free to call this again, cheaply, rather than replay a stale answer. */
|
|
170
|
+
function toolLocationSeam() {
|
|
171
|
+
const req = createRequire(import.meta.url);
|
|
172
|
+
const specifier = existsSync(join(HERE, "tool-location.mjs")) ? "./tool-location.mjs" : "./tool-location.mts";
|
|
173
|
+
return req(specifier);
|
|
174
|
+
}
|
|
175
|
+
/** Reduced (Phase 60) to a thin wrapper over the seam's own exported
|
|
176
|
+
* `resolveOnPath()` -- the first of Phase 59 D-02's three independent
|
|
177
|
+
* `$PATH`-walk copies to collapse. Kept as a named function (rather than
|
|
178
|
+
* inlined at its one call site) only because `ResolvedBackendDeps.resolveBinPath`
|
|
179
|
+
* needs a real default to fall back to when a caller supplies `viceBin` but
|
|
180
|
+
* not this override (PD-02). */
|
|
127
181
|
function defaultResolveBinPath(bin, env) {
|
|
128
|
-
|
|
129
|
-
const abs = resolvePath(bin);
|
|
130
|
-
return existsSync(abs) ? abs : null;
|
|
131
|
-
}
|
|
132
|
-
const pathEnv = env.PATH ?? "";
|
|
133
|
-
for (const dir of pathEnv.split(":")) {
|
|
134
|
-
if (!dir)
|
|
135
|
-
continue;
|
|
136
|
-
const candidate = join(dir, bin);
|
|
137
|
-
if (existsSync(candidate))
|
|
138
|
-
return candidate;
|
|
139
|
-
}
|
|
140
|
-
return null;
|
|
182
|
+
return toolLocationSeam().resolveOnPath(bin, env).path;
|
|
141
183
|
}
|
|
142
184
|
function defaultStat(resolvedPath) {
|
|
143
185
|
try {
|
|
@@ -190,11 +232,41 @@ export function resolvedBackend(deps = {}) {
|
|
|
190
232
|
if (memoisedResult !== null)
|
|
191
233
|
return memoisedResult;
|
|
192
234
|
const env = deps.env ?? process.env;
|
|
193
|
-
const viceBin = deps.viceBin ?? env.VICE_BIN ?? "x64sc";
|
|
194
|
-
const resolveBinPath = deps.resolveBinPath ?? defaultResolveBinPath;
|
|
195
235
|
const stat = deps.stat ?? defaultStat;
|
|
196
236
|
const now = deps.now ?? (() => Date.now());
|
|
197
|
-
|
|
237
|
+
let viceBin;
|
|
238
|
+
let resolvedPath;
|
|
239
|
+
// Phase 60 gap closure (LOC-03, PD-13): non-null only in the PD-01 branch
|
|
240
|
+
// below, and only when the seam itself refused. Carried verbatim onto the
|
|
241
|
+
// returned result's own `locationRefusal` field (see that field's own doc
|
|
242
|
+
// comment for why); this module reads no environment variable to compose
|
|
243
|
+
// it, and reads none to name it here either.
|
|
244
|
+
let locationRefusal = null;
|
|
245
|
+
if (deps.viceBin !== undefined || deps.resolveBinPath !== undefined) {
|
|
246
|
+
// PD-02: an explicit override bypasses the seam entirely -- byte-for-byte
|
|
247
|
+
// the same behaviour every existing injected test case already exercises.
|
|
248
|
+
viceBin = deps.viceBin ?? "x64sc";
|
|
249
|
+
const resolveBinPath = deps.resolveBinPath ?? defaultResolveBinPath;
|
|
250
|
+
resolvedPath = resolveBinPath(viceBin, env);
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
// PD-01: resolvedBackend() gains the tools.json layer internally by
|
|
254
|
+
// calling the seam; it keeps no ordering of its own. The seam's WHOLE
|
|
255
|
+
// result is taken here (PD-13), not only its `path`: the display name
|
|
256
|
+
// handed to binPathFields() below falls back to the seam's own
|
|
257
|
+
// `envCandidate` -- the value the developer actually wrote -- when the
|
|
258
|
+
// seam reports one, and to the literal "x64sc" only when it does not
|
|
259
|
+
// (retiring the hardcoded display name IN-01 logged). `refusal` is
|
|
260
|
+
// carried onto `locationRefusal` above unchanged.
|
|
261
|
+
const projectRoot = deps.projectRoot ?? (deps.supervisorDir !== undefined ? dirname(dirname(deps.supervisorDir)) : process.cwd());
|
|
262
|
+
const toolsDir = deps.toolsDir ?? (deps.supervisorDir !== undefined ? dirname(deps.supervisorDir) : join(process.cwd(), ".c64-re-tools"));
|
|
263
|
+
const locate = deps.locate ?? toolLocationSeam().resolveTool;
|
|
264
|
+
const locateDeps = { toolsDir, projectRoot, env };
|
|
265
|
+
const locateResult = locate("x64sc", locateDeps);
|
|
266
|
+
resolvedPath = locateResult.path;
|
|
267
|
+
viceBin = locateResult.envCandidate ?? "x64sc";
|
|
268
|
+
locationRefusal = locateResult.refusal;
|
|
269
|
+
}
|
|
198
270
|
const identity = resolvedPath ? stat(resolvedPath) : null;
|
|
199
271
|
const cacheEligible = resolvedPath !== null && identity !== null && typeof deps.supervisorDir === "string";
|
|
200
272
|
if (cacheEligible) {
|
|
@@ -213,7 +285,7 @@ export function resolvedBackend(deps = {}) {
|
|
|
213
285
|
});
|
|
214
286
|
}
|
|
215
287
|
}
|
|
216
|
-
const result = { backend: "stock", ...binPathFields(resolvedPath, viceBin) };
|
|
288
|
+
const result = { backend: "stock", ...binPathFields(resolvedPath, viceBin), locationRefusal };
|
|
217
289
|
memoisedResult = result;
|
|
218
290
|
return result;
|
|
219
291
|
}
|
|
@@ -320,7 +320,13 @@ export function buildViceArgs(port, { backend, mcpHost, binmonHost, viceArgsEnv,
|
|
|
320
320
|
function spawnAndRecordInstance(reason, port, deps) {
|
|
321
321
|
const spawnFn = deps.spawn ?? ((cmd, args, opts) => nodeSpawn(cmd, args, opts));
|
|
322
322
|
const now = deps.now ?? (() => Date.now());
|
|
323
|
-
|
|
323
|
+
// Phase 60 (LOC-02): no environment-variable fallback here any more -- the
|
|
324
|
+
// real broker resolves the binary ONCE at startup through backend-detect.mts's
|
|
325
|
+
// resolvedBackend() (which itself now consults the tool-location seam) and
|
|
326
|
+
// threads that SAME value down through deps.viceBin on every call. The
|
|
327
|
+
// literal "x64sc" default below is only ever reached by a caller (a test)
|
|
328
|
+
// that supplies neither.
|
|
329
|
+
const viceBin = deps.viceBin ?? "x64sc";
|
|
324
330
|
const backend = deps.backend ?? "stock";
|
|
325
331
|
// The ONE construction site for a fresh InstanceRecord asserts the
|
|
326
332
|
// invariant every downstream consumer (HeldLease, textConnect(), etc.) was
|
|
@@ -1156,7 +1162,10 @@ function launchSupervised(reason, port, deps, crashTimes, backoffMs, remoteMonit
|
|
|
1156
1162
|
const logDir = deps.epoch.instanceLogDirFor(deps.stateDir, port);
|
|
1157
1163
|
mkdirSync(logDir, { recursive: true });
|
|
1158
1164
|
const epoch = deps.epoch.nextEpochFor(supervisorDir);
|
|
1159
|
-
|
|
1165
|
+
// Phase 60 (LOC-02): same narrowing as spawnAndRecordInstance() above --
|
|
1166
|
+
// no environment-variable fallback here, the real broker always threads
|
|
1167
|
+
// its once-resolved viceBin down through deps.viceBin.
|
|
1168
|
+
const viceBin = deps.viceBin ?? "x64sc";
|
|
1160
1169
|
// Timestamp PLUS the epoch number: Date.now() alone can collide across
|
|
1161
1170
|
// two respawns inside the same millisecond when the injected sleepMs
|
|
1162
1171
|
// resolves immediately (exactly what this module's own tests do to stay
|
|
@@ -366,8 +366,8 @@ const SLAFILE_ATTR_PATTERN = /\bslafile\s*=\s*"([^"]*)"/;
|
|
|
366
366
|
* whether that `slafile` exists on disk beside its own `.ldefs` -- a SORTED
|
|
367
367
|
* list (by `id`) so any assertion over the result is order-independent.
|
|
368
368
|
*
|
|
369
|
-
* `ghidraHome` is an explicit parameter
|
|
370
|
-
* `
|
|
369
|
+
* `ghidraHome` is an explicit parameter; this function never reads the
|
|
370
|
+
* `GHIDRA_HOME` environment variable internally, so the whole function is drivable
|
|
371
371
|
* against a synthetic directory tree with no real Ghidra installation
|
|
372
372
|
* present.
|
|
373
373
|
*
|