@henols/c64-re-tools 0.2.3 → 0.2.5

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/bin/cli.mjs CHANGED
@@ -30,7 +30,9 @@ const PKG_ROOT = dirname(HERE);
30
30
  const SKILLS_SRC = join(PKG_ROOT, "skills");
31
31
 
32
32
  // The single version-resolution seam this repo maintains is
33
- // `src/mcp/vice/version.ts` (quick-260819-tsz, D-5). This package
33
+ // `src/mcp/vice/version.ts` -- one algorithm for resolving a VERSION
34
+ // template against a published version, kept in exactly one place after
35
+ // this repo once carried several independent copies of it. This package
34
36
  // deliberately does NOT import it: it ships without the seam file (its
35
37
  // `files[]` is `bin/`, `skills/`, `README.md`) and targets node >= 18, which
36
38
  // cannot type-strip the seam's `.ts` the way the vice-mcp package's own
@@ -42,12 +44,13 @@ const SKILLS_SRC = join(PKG_ROOT, "skills");
42
44
  // only ever published with a concrete version already stamped in. That
43
45
  // number is PRODUCED elsewhere: `scripts/version.mjs stamp` writes the
44
46
  // working-tree placeholder, and CI's `npm version` writes the real one at
45
- // publish time. Do not reimplement D-2's template-resolution rules here.
47
+ // publish time. Do not reimplement the seam's own VERSION-template
48
+ // parsing and prefix-resolution rules here.
46
49
  const SELF = readJson(join(PKG_ROOT, "package.json")) ?? {};
47
50
  const SELF_VERSION = typeof SELF.version === "string" ? SELF.version : "0.0.0";
48
51
  const MCP_PKG = "@henols/vice-mcp";
49
52
  // The dev placeholder every derived, publishable version string carries in
50
- // the working tree (R-2, quick-260819-tsz). Defined authoritatively as
53
+ // the working tree outside a stamped release. Defined authoritatively as
51
54
  // `DEV_PLACEHOLDER` in `src/mcp/vice/version.ts` -- repeated here as a
52
55
  // literal, NOT imported, because this package deliberately ships without
53
56
  // that seam file (see the comment above) and targets node >= 18, which
@@ -57,7 +60,7 @@ const MCP_PKG = "@henols/vice-mcp";
57
60
  // so a future edit to the seam's placeholder is not missed.
58
61
  const MCP_DEV_PLACEHOLDER = "0.0.0-dev";
59
62
  // Wire the project to the exact vice-mcp version this installer was built
60
- // against -- EXCEPT when run from an unstamped dev checkout (MED-3):
63
+ // against -- EXCEPT when run from an unstamped dev checkout:
61
64
  // `installer/package.json`'s dependency pin is the permanent working-tree
62
65
  // placeholder outside a CI-stamped publish job, and `@henols/vice-mcp` at
63
66
  // that literal version will never exist on the npm registry. Silently
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henols/c64-re-tools",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Installer that adds the C64 reverse-engineering skills and the VICE emulator MCP server (@henols/vice-mcp) to a project.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -45,7 +45,7 @@
45
45
  "installer"
46
46
  ],
47
47
  "dependencies": {
48
- "@henols/vice-mcp": "0.2.3"
48
+ "@henols/vice-mcp": "0.2.5"
49
49
  },
50
50
  "scripts": {
51
51
  "sync-skills": "node scripts/sync-skills.mjs",
@@ -22,10 +22,10 @@ absent `run` verb is not an omission). It contacts nothing.
22
22
  Options: `-o FILE` `--out-dir DIR` `-f FORMAT` `--setpc ADDR` `-DSYM=VAL`
23
23
  `-I DIR` `--no-report` `--json`.
24
24
 
25
- `-I DIR` is resolved **workspace-relative** to the project root the host
26
- broker was launched with — the same resolution `source`/`--out-dir` already
27
- go through — before it ever reaches the assembler. An absolute or escaping
28
- `-I` directory is refused by the seam rather than passed to ACME; this is a
25
+ The seam resolves `-I DIR` **workspace-relative** to the project root the host
26
+ broker starts with — the same resolution `source`/`--out-dir` already
27
+ go through — before it ever reaches the assembler. The seam refuses an absolute
28
+ or escaping `-I` directory rather than passing it to ACME. This is a
29
29
  documented contract change from earlier releases, when an absolute include
30
30
  reached the assembler unchecked.
31
31
 
@@ -96,13 +96,13 @@ Inspect the result with `od`:
96
96
  od -An -tx1 game.prg | head -2
97
97
  ```
98
98
 
99
- The first two bytes are the little-endian load address (`01 08` = `$0801`); code
99
+ The first two bytes are the little-endian load address (`01 08` = `$0801`). Code
100
100
  follows.
101
101
 
102
102
  ## Writing source
103
103
 
104
- Start from the scaffold — it carries a BASIC stub whose `SYS` target is computed,
105
- so the entry point stays correct as the program grows:
104
+ Start from the scaffold — it carries a BASIC stub that computes its `SYS` target
105
+ rather than hard-coding it, so the entry point stays correct as the program grows:
106
106
 
107
107
  ```bash
108
108
  node $A new game.asm
@@ -142,17 +142,17 @@ you also assemble by hand, so these stay recognised as mnemonics.
142
142
  This skill does not disassemble. The route that does is `anno export-asm`, and
143
143
  it lives in the `anno` CLI rather than here.
144
144
 
145
- **Dated withdrawal 2026-08-29, dated return 2026-08-31 — both halves are kept,
146
- because the withdrawal is the record of why the route is shaped the way it is.**
145
+ **Dated withdrawal 2026-08-29, dated return 2026-08-31 — this document keeps
146
+ both halves, because the withdrawal records why the route has its current shape.**
147
147
  Whole-program static disassembly was WITHDRAWN on 2026-08-29 rather than left standing on an unverified reassembly claim: the removed route
148
148
  settled correctness with a transcript parser, and the recorded false pass that
149
149
  discipline exists against read `ACME not found in PATH (skipped)` / `All
150
150
  roundtrip verifications passed.` / `EXIT=0` — exit zero, an aggregate line
151
151
  reading as a full pass, and the one assembler this project cares about never
152
152
  having run. On **2026-08-31 the route returned**, rebuilt over the annotation
153
- store as `anno export-asm`. It is not a rename of what was removed: its
154
- correctness is settled by **assembling the output with a real ACME and diffing
155
- the bytes against the input** — never by an exit code, and never by a string
153
+ store as `anno export-asm`. It is not a rename of the removed route: its
154
+ correctness comes from **assembling the output with a real ACME and diffing
155
+ the bytes against the input** — never from an exit code, and never from a string
156
156
  match on the exporter's own output.
157
157
 
158
158
  **The live invocation:**
@@ -162,14 +162,14 @@ npx -y @henols/vice-mcp anno export-asm game.prg --store game.annostore --out ga
162
162
  node <plugin-root>/src/mcp/vice/vice-proxy.ts anno export-asm game.prg --store game.annostore
163
163
  ```
164
164
 
165
- `<image>` and `--store` are **two separate arguments and neither is derived from
165
+ `<image>` and `--store` are **two separate arguments, and neither one derives from
166
166
  the other** — the image supplies the bytes, the store supplies the names, ranges,
167
167
  typed regions and comments, and naming one does not name the other. `--out` names
168
- a **directory** the whole export is written into, defaulting to the image's
168
+ a **directory**: the verb writes the whole export into it, defaulting to the image's
169
169
  basename stem beside the **store**, not beside the image, because the export is a
170
170
  generated view of the annotations. The directory holds a root file that sources
171
171
  the rest, one file per annotation scope, and an `unscoped.a` for anything inside
172
- no scope. A non-empty destination is refused rather than overwritten unless you
172
+ no scope. The verb refuses a non-empty destination rather than overwriting it, unless you
173
173
  pass `--force`.
174
174
 
175
175
  **The verb writes source text and runs no assembler.** Its own second output line
@@ -180,10 +180,10 @@ export-asm: this file has NOT been assembled -- this command writes source text
180
180
  ```
181
181
 
182
182
  The real-ACME byte-diff oracle is **test-only** — it lives in
183
- `src/mcp/vice/acme-verify.ts`, is exercised by `acme-verify.test.ts` (hard-failed
184
- in CI with `VICE_REQUIRE_ACME=1`), and is deliberately absent from the published
183
+ `src/mcp/vice/acme-verify.ts`, `acme-verify.test.ts` exercises it (hard-failed
184
+ in CI with `VICE_REQUIRE_ACME=1`), and it stays deliberately absent from the published
185
185
  package, so nothing on the runtime path can reach it. A clean `export-asm` run is
186
- therefore evidence that source was written and nothing more; it is not an
186
+ therefore evidence that the verb wrote source and nothing more. It is not an
187
187
  assembler verdict. If you need to know the emitted source reassembles, assemble
188
188
  it yourself — that is what this skill's own build route is for.
189
189
 
@@ -191,7 +191,7 @@ it yourself — that is what this skill's own build route is for.
191
191
  `anno_read_region` and `anno_disassemble` render an explicit inclusive range out
192
192
  of the image on demand, capped at 4096 bytes per call and REFUSED by name above
193
193
  the cap rather than truncated. `c64-program-recon` documents that route and this
194
- history together; it is not restated there.
194
+ history together. It is not restated there.
195
195
 
196
196
  ## Setup
197
197
 
@@ -245,11 +245,11 @@ This one turns source into bytes. It does not restate what the others carry.
245
245
  Record findings that make RE faster in your own project notes **at the moment you
246
246
  find them**, graded with `Evidence:` and `Confidence:`. Promote a finding by
247
247
  re-logging it with the new evidence, never by editing an old grade in place — the
248
- grade is only worth anything if it says what was actually known when it was written.
248
+ grade is only worth anything if it says what you actually knew when you wrote it.
249
249
 
250
250
  ## Troubleshooting
251
251
 
252
- | Symptom | Fix |
252
+ | Symptom | Correct |
253
253
  |---|---|
254
254
  | `install the ACME cross assembler and put acme on PATH` | Install ACME. |
255
255
  | `for <...> includes, set $ACME to …` | `export ACME=<dir holding cbm/c64/vic.a>`. |
@@ -257,4 +257,4 @@ grade is only worth anything if it says what was actually known when it was writ
257
257
  | `Label name not in leftmost column` + `Syntax error` on a mnemonic | Add `!cpu 6510`. |
258
258
  | `Output file already chosen` | Remove `!to` from the source and keep `-o`. |
259
259
  | `Number does not fit in 8 bits` | Pass a value 0–255, or drop the `#` if you meant an address. |
260
- | `This tool cannot read binary files. The file appears to be a binary .a file.` | The file is fine — ACME source is plain text; the agent's Read tool refuses the `.a` extension regardless of content, and Edit needs a prior successful Read. Scaffold and write source as `.asm` instead — the driver accepts `.a`/`.asm`/`.s`, all three assemble byte-identically (verified both directions in this container, 2026-08-04). To read an existing `.a` file, use `sed -n '1,60p' file.a`. |
260
+ | `This tool cannot read binary files. The file appears to be a binary .a file.` | The file is fine. ACME source is plain text. The agent's Read tool refuses the `.a` extension regardless of content. Edit needs a prior successful Read. Scaffold and write source as `.asm` instead — the driver accepts `.a`/`.asm`/`.s`, all three assemble byte-identically (verified both directions in this container, 2026-08-04). To read an existing `.a` file, use `sed -n '1,60p' file.a`. |
@@ -20,7 +20,7 @@ node $S audit --image path/to/image.d64 # find fabricated/co
20
20
  ```
21
21
 
22
22
  The script wraps `c1541` and nothing else — **read-only**. `bam`/`dir`/`audit`
23
- need only `--image`; `entry`/`chain`/`read` also need `--name`, a CBM
23
+ need only `--image`. `entry`/`chain`/`read` also need `--name`, a CBM
24
24
  filename or glob pattern (never a path). No `-format`, `-write`, `-bwrite`
25
25
  or `-delete` verb is reachable from here, deliberately: this skill only
26
26
  ever reads a disk image, never mutates one.
@@ -30,11 +30,11 @@ Options: `--image PATH` `--name CBM-NAME` `--out-dir DIR` `--json`.
30
30
  `--out-dir` defaults to the image's own directory, exactly like
31
31
  `acme-build`'s own `--out-dir` default. Both `--image` and `--out-dir` are
32
32
  resolved **workspace-relative** to the smallest ancestor directory
33
- containing both, before the request ever reaches the seam — the same
34
- resolution `acme-build`'s `source`/`--out-dir` already go through. `--name`
35
- is passed straight through, never resolved as a path; a value beginning
36
- with `-` is refused by the seam before any child process is spawned (it
37
- would otherwise be read as a flag by `c1541`'s own CLI).
33
+ containing both, before the request reaches the seam. This is the same
34
+ resolution `acme-build`'s `source`/`--out-dir` already go through. The seam
35
+ passes `--name` straight through and never resolves it as a path. The seam
36
+ refuses a value beginning with `-` before it spawns any child process.
37
+ Otherwise `c1541`'s own CLI would read it as a flag.
38
38
 
39
39
  ## Directory listing
40
40
 
@@ -57,9 +57,9 @@ Prints the seam's response verbatim as one line of JSON:
57
57
  node $S bam --image game.d64 --json
58
58
  ```
59
59
 
60
- Same response shape as `dir`; `results[0].path` names a file carrying one
61
- per-sector allocation row per track (a run of `*`/`.` characters, `*` for
62
- an allocated sector).
60
+ Same response shape as `dir`. `results[0].path` names a file carrying one
61
+ per-sector allocation row per track. Each row is a run of `*`/`.`
62
+ characters, `*` for an allocated sector.
63
63
 
64
64
  ## One directory entry's raw fields
65
65
 
@@ -68,10 +68,11 @@ node $S entry --image game.d64 --name FILENAME --json
68
68
  ```
69
69
 
70
70
  `results[0].path` names a file carrying the entry's raw 32-byte directory
71
- record (as a hex dump) followed by its `T/S: <t>/<s>, <n> blocks` summary
72
- line. This script ALSO parses that line back and adds `firstTrack`/
73
- `firstSector` as numeric fields on the JSON response, purely for display —
74
- the file itself is still the authoritative source.
71
+ record (as a hex dump). A `T/S: <t>/<s>, <n> blocks` summary line follows
72
+ the record. This script also parses that line back and adds
73
+ `firstTrack`/`firstSector` as numeric fields on the JSON response. These
74
+ fields exist purely to show the values — the file itself is still the
75
+ authoritative source.
75
76
 
76
77
  ## A named file's sector chain
77
78
 
@@ -81,7 +82,7 @@ node $S chain --image game.d64 --name FILENAME --json
81
82
 
82
83
  `results[0].path` names a file listing every `(track,sector)` hop the file
83
84
  occupies, in order. A single-sector file's chain shows one hop with no
84
- second tuple on the arrow's right side; a multi-sector file's chain repeats
85
+ second tuple on the arrow's right side. A multi-sector file's chain repeats
85
86
  the tuple at every hop.
86
87
 
87
88
  ## Extracting a named file's bytes
@@ -90,8 +91,8 @@ the tuple at every hop.
90
91
  node $S read --image game.d64 --name FILENAME --out-dir /scratch --json
91
92
  ```
92
93
 
93
- Writes the file's raw bytes (unlike the other four capabilities, this one
94
- is NOT a captured-stdout listing — `c1541` writes the output file itself).
94
+ Writes the file's raw bytes. Unlike the other four capabilities, this one
95
+ is NOT a captured-stdout listing — `c1541` writes the output file itself.
95
96
  `results[0]` carries that file's `path`/`sha256`/`byteLength`.
96
97
 
97
98
  ## Auditing for fabricated or corrupted entries
@@ -100,56 +101,58 @@ is NOT a captured-stdout listing — `c1541` writes the output file itself).
100
101
  node $S audit --image game.d64 --json
101
102
  ```
102
103
 
103
- Composes `dir` (names and block counts), `bam` (the per-sector allocation
104
- map), and one `entry` call per name (each file's own claimed first track/
105
- sector, and its directory sector's "next directory" pointer) into a ported,
104
+ The `audit` command composes `dir` (names and block counts) and `bam` (the
105
+ per-sector allocation map). For each name, it also makes one `entry` call
106
+ to get that file's own claimed first track/sector and its directory
107
+ sector's "next directory" pointer. Together these calls form a ported,
106
108
  read-only detector — no `-format`/`-write`/mutating verb, and no seventh
107
- `host_tool` id; this is three existing capabilities composed client-side.
109
+ `host_tool` id. This is three existing capabilities composed client-side.
108
110
 
109
- A directory entry is flagged `suspicious`, with **named reasons, never a
110
- bare boolean**, on any of:
111
+ The `audit` command flags a directory entry `suspicious`, with **named
112
+ reasons, never a bare boolean**, on any of:
111
113
 
112
- 1. its block count is `0`;
113
- 2. its first track/sector lies outside the image's own geometry (there is
114
- no such track, or no such sector on that track);
115
- 3. its first **sector** — not merely its whole track — is reported free by
116
- the allocation map, meaning the file cannot really start there. This is
114
+ 1. its block count is `0`.
115
+ 2. its first track/sector lies outside the image's own geometry. There is
116
+ no such track, or no such sector on that track.
117
+ 3. the allocation map reports its first **sector** — not merely its whole
118
+ track — free, meaning the file cannot really start there. This is
117
119
  sharper than checking only whether the whole track is free, because the
118
120
  per-sector map is available.
119
121
 
120
- A cyclic or self-referential directory chain (two entries claiming the same
121
- first track/sector, or a "next directory" pointer that refers back to a
122
- sector already seen — including the directory's own starting sector) stops
123
- being treated as new information and is reported as a top-level
124
- `chain_error` naming the repeated pointer, rather than looping. Every
125
- remaining entry is still audited afterward — a chain error on one entry
126
- never hides another entry's own independent flag.
122
+ A directory chain is cyclic or self-referential when two entries claim the
123
+ same first track/sector. It is also cyclic or self-referential when a
124
+ "next directory" pointer refers back to a sector already seen, including
125
+ the directory's own starting sector. When this happens, the command stops
126
+ treating the repeat as new information. Instead of looping, it reports a
127
+ top-level `chain_error` naming the repeated pointer. Every remaining entry
128
+ is still audited afterward — a chain error on one entry never hides another
129
+ entry's own independent flag.
127
130
 
128
131
  ```json
129
132
  {"entries":[{"name":"basicstub","blocks":1,"first_track":17,"first_sector":0,"suspicious":false,"suspicious_reasons":[]}],"chain_error":null}
130
133
  ```
131
134
 
132
135
  **A flag is a signal to investigate, not a verdict.** A directory entry a
133
- cracker fabricated for a file never actually written, a genuinely corrupted
134
- image, and (rarely) an unusual-but-legitimate disk layout can all produce a
135
- flag; this command reports what it finds, named, and leaves the
136
- interpretation to whoever is looking at the disk.
136
+ cracker fabricated for a file never actually written, a genuinely
137
+ corrupted image, and (rarely) an unusual-but-legitimate disk layout can
138
+ all produce a flag. This command reports what it finds, named, and leaves
139
+ the interpretation to whoever is looking at the disk.
137
140
 
138
141
  ## Failure shape
139
142
 
140
- A nonexistent image, a nonexistent named entry, or any other call `c1541`
141
- cannot service is reported as `{"ok":false,"message":"..."}` with a
142
- non-zero exit code — **never** a success envelope over an empty or partial
143
- result. `c1541` itself exits `0` even on a genuine failure (it prints its
144
- own `Error - ...` lines to stdout instead); the seam's own classifier, not
145
- the exit code, is what decides success here.
143
+ The seam reports a nonexistent image, a nonexistent named entry, or any
144
+ other call `c1541` cannot service as `{"ok":false,"message":"..."}` with a
145
+ non-zero exit code. This is **never** a success envelope over an empty or
146
+ partial result. `c1541` itself exits `0` even on a genuine failure (it
147
+ prints its own `Error - ...` lines to stdout instead). The seam's own
148
+ classifier, not the exit code, is what decides success here.
146
149
 
147
150
  ## What this skill does NOT do
148
151
 
149
152
  - **No mutating verb.** `-format`/`-write`/`-bwrite`/`-delete` are never
150
153
  reachable from this script, on the wire, or anywhere in this skill's tree
151
- — only the six read-only capabilities above are exposed.
152
- - **No direct binary spawn.** `c1541` runs host-side; this script only ever
154
+ — this skill exposes only the six read-only capabilities above.
155
+ - **No direct binary spawn.** `c1541` runs host-side. This script only ever
153
156
  constructs a typed request and reads the produced files back off the
154
157
  shared workspace tree — the host-tool execution seam is the only route.
155
158
  - **No emulator dependency.** This skill names no VICE emulator tool at