@henols/vice-mcp 0.2.3 → 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.
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  // anno-memmap-render.ts -- the ONE authoritative place in this repo that
3
3
  // renders the human-readable Markdown memory map from this project's own
4
- // annotation store (D-24) plus a validated run-scoped provenance sidecar
5
- // (D-27's reconciliation, recorded in 11-10-PLAN.md's objective).
4
+ // annotation store plus a validated run-scoped provenance sidecar, using
5
+ // the reconciliation between those two sources this file implements.
6
6
  //
7
- // WHY THIS MODULE EXISTS (D-24): the store is canonical; the Markdown memory
7
+ // WHY THIS MODULE EXISTS: the store is canonical; the Markdown memory
8
8
  // map becomes a rendered VIEW. Criterion 1 says findings must be queryable
9
9
  // "instead of re-deriving from Markdown prose" -- that is only true by
10
10
  // construction if the prose is GENERATED from the queryable thing. Nothing
@@ -13,7 +13,8 @@
13
13
  // `listRanges()`/`listLabels()`/`listComments()` readers, never from a
14
14
  // human editing the output file directly.
15
15
  //
16
- // THE D-24/D-27 RECONCILIATION THIS FILE IMPLEMENTS: run-scoped facts (the
16
+ // THE RECONCILIATION THIS FILE IMPLEMENTS, BETWEEN THE STORE AND THE
17
+ // RUN-SCOPED SIDECAR: run-scoped facts (the
17
18
  // capture's SHA-256, `$01`, `$DD00`, the derived graphics chain, the video
18
19
  // standard, the live vector pair, observed raster positions) are facts about
19
20
  // a RUN, not about an address -- the store is address-keyed and has no shape
@@ -24,8 +25,8 @@
24
25
  // `<placeholder>` for one.
25
26
  //
26
27
  // WHY THE LAYOUT IS EMBEDDED IN TYPESCRIPT RATHER THAN READ FROM A TEMPLATE
27
- // FILE AT RUNTIME (the second decision this plan records): Phase 10's D-06
28
- // established that `.claude/mcp/vice/*.ts` exists as files on disk only
28
+ // FILE AT RUNTIME (the second decision this plan records): an earlier
29
+ // finding established that `.claude/mcp/vice/*.ts` exists as files on disk only
29
30
  // under the Claude Code plugin route -- both npm-installer routes launch via
30
31
  // `npx`. A renderer that resolved a template path into the skills tree at
31
32
  // runtime would silently fail to resolve for an npm-installed user. The
@@ -41,7 +42,7 @@
41
42
  // - drift detection (`checkRenderedMemoryMap()`) -- the one place a
42
43
  // rendered file on disk is compared against what the store (plus the
43
44
  // sidecar) would produce right now;
44
- // - Markdown-cell escaping (`escapeMarkdownCell()`, WR-04, closed) --
45
+ // - Markdown-cell escaping (`escapeMarkdownCell()`) --
45
46
  // every store-derived text interpolation in the generated document
46
47
  // (comment evidence, symbol names) is escaped through this one
47
48
  // function, never a second ad hoc `.replace()` at a call site.
@@ -50,7 +51,7 @@
50
51
  // - Never hand-edit the rendered output. The banner exists precisely so a
51
52
  // human editor is caught by `checkRenderedMemoryMap()` -- see the
52
53
  // `render_digest` comment below for exactly what it covers.
53
- // - Never read the layout from the skills tree at runtime (Phase 10 D-06).
54
+ // - Never read the layout from the skills tree at runtime (see the module header for why).
54
55
  // This module's own non-vacuity test asserts a zero-count grep for the
55
56
  // recon skill's template filename -- if you are tempted to add a
56
57
  // `readFileSync()` call reaching into `.claude/skills/`, don't; the
@@ -101,14 +102,14 @@ function errMsg(err: unknown): string {
101
102
  * offset at which parsing stopped, as ` (at byte offset N)`, or `""` when the
102
103
  * runtime did not name one.
103
104
  *
104
- * WHY THIS IS A DIGIT EXTRACTOR AND NOT A MESSAGE PASS-THROUGH (CR-03). V8's
105
+ * WHY THIS IS A DIGIT EXTRACTOR AND NOT A MESSAGE PASS-THROUGH. V8's
105
106
  * JSON `SyntaxError` embeds a SNIPPET OF THE INPUT in its own message --
106
107
  * `Unexpected token 'Q', "QQZZORACLE"... is not valid JSON` -- so any code
107
108
  * that forwards `err.message` from a JSON parse over caller-supplied bytes is
108
109
  * a content-disclosure oracle. The capture group here is `(\d+)` and nothing
109
110
  * else, so no byte of the parsed file can reach the returned string however
110
111
  * the runtime words its message. Widening this regex to capture anything but
111
- * digits reopens CR-03.
112
+ * digits reopens the same content-disclosure risk.
112
113
  *
113
114
  * Returns `""` rather than guessing when no position is present (`Unexpected
114
115
  * end of JSON input` carries none) -- an absent offset is reported by absence,
@@ -280,9 +281,9 @@ export function parseProvenanceHeader(json: unknown): ProvenanceHeader {
280
281
  * distinguishable from drift under the same one.
281
282
  *
282
283
  * Version 2 (260821-a86) escaped Markdown table cells via
283
- * `escapeMarkdownCell()` -- WR-04, an output-shape change.
284
+ * `escapeMarkdownCell()`, an output-shape change.
284
285
  *
285
- * Version 3 (D-17) is an INPUT change: `computeRenderDigest()` canonicalises
286
+ * Version 3 is an INPUT change: `computeRenderDigest()` canonicalises
286
287
  * this store's own `RangeRow`/`LabelRow`/`CommentRow` instead of the three
287
288
  * wire shapes recorded above, so the same underlying annotations hash
288
289
  * differently either side of it. Leaving the version at "2" across that
@@ -298,7 +299,7 @@ export const RENDERER_VERSION = "3";
298
299
  * other leg), because comment `evidence` legitimately contains `|` and
299
300
  * embedded newlines (`anno_set_comment`'s own schema documents multi-line
300
301
  * support) -- refusing here would refuse valid data, not an attack. Closes
301
- * WR-04 / T-11-NAME-INJECT's render leg: an unescaped `|` or newline in
302
+ * this control's own render leg of T-11-NAME-INJECT: an unescaped `|` or newline in
302
303
  * store text used to be able to inject an extra table cell or split a row
303
304
  * across lines in the generated Markdown. A plain string or an empty string
304
305
  * is returned unchanged. */
@@ -348,7 +349,7 @@ export interface RenderMemoryMapOptions {
348
349
  * reaching an equally real `readFileSync`, carried nothing -- so a reader
349
350
  * comparing the two would reasonably conclude the difference was
350
351
  * deliberate. It was not: the CLI read this argument raw, making it an
351
- * arbitrary-file read oracle (`29-REVIEW.md` CR-03). An absent comment
352
+ * arbitrary-file read oracle. An absent comment
352
353
  * beside a present one is a claim, and this one was false. */
353
354
  provenancePath: string;
354
355
  /** The workspace root both confinement checks are taken against. REQUIRED
@@ -396,7 +397,7 @@ export async function renderMemoryMap(opts: RenderMemoryMapOptions): Promise<Ren
396
397
  try {
397
398
  sidecarJson = JSON.parse(sidecarBytes);
398
399
  } catch (err) {
399
- // NEVER INTERPOLATE THE UNDERLYING PARSE ERROR HERE (CR-03). Node's
400
+ // NEVER INTERPOLATE THE UNDERLYING PARSE ERROR HERE. Node's
400
401
  // SyntaxError quotes a snippet of the input it choked on -- e.g.
401
402
  // `Unexpected token 'Q', "QQZZORACLE"... is not valid JSON` -- so passing
402
403
  // it through turns a read refusal into a CONTENT-DISCLOSURE ORACLE. That
@@ -410,7 +411,7 @@ export async function renderMemoryMap(opts: RenderMemoryMapOptions): Promise<Ren
410
411
  // where parsing stopped and not about what the file contains.
411
412
  throw new Error(
412
413
  `renderMemoryMap: provenance sidecar at "${provenancePath}" is not valid JSON${jsonParsePosition(err)}. ` +
413
- "The underlying parser message is deliberately NOT included -- it quotes the file's own bytes (CR-03).",
414
+ "The underlying parser message is deliberately NOT included -- it quotes the file's own bytes.",
414
415
  );
415
416
  }
416
417
  const provenance = parseProvenanceHeader(sidecarJson);
@@ -460,7 +461,7 @@ export async function renderMemoryMap(opts: RenderMemoryMapOptions): Promise<Ren
460
461
  // load-bearing detail rather than a formatting preference: every byte below
461
462
  // is re-rendered and compared BYTE FOR BYTE by `checkRenderedMemoryMap()`,
462
463
  // so an absolute path here would make the drift verdict a function of where
463
- // the checkout sits (CR-01). `workspaceRelativePath()` is the one definition
464
+ // the checkout sits. `workspaceRelativePath()` is the one definition
464
465
  // of that spelling; it computes a location and refuses one that escapes the
465
466
  // root. It is NOT a confinement check -- this module still performs no
466
467
  // confinement of its own, exactly as `RenderMemoryMapOptions` documents.
@@ -587,13 +588,13 @@ export interface CheckRenderedMemoryMapOptions {
587
588
  /** See `RenderMemoryMapOptions.provenancePath` -- same argument, one layer
588
589
  * up. The CALLER (`anno-cli.ts`'s `cmdRenderMemmap()`) confines it through
589
590
  * `storePathWithinWorkspace()`; this module performs no confinement of its
590
- * own (CR-03). */
591
+ * own. */
591
592
  provenancePath: string;
592
593
  /** The rendered file to compare against, read RAW by `readFileSync` below.
593
594
  * The CALLER confines it through `storePathWithinWorkspace()` -- the SAME
594
595
  * resolution that produces the write path on the non-`--check` branch, so
595
596
  * the drift check and the write are one confined value rather than two
596
- * rules. This module performs no confinement of its own (CR-02). */
597
+ * rules. This module performs no confinement of its own. */
597
598
  renderedPath: string;
598
599
  /** See `RenderMemoryMapOptions.workspaceRoot`. */
599
600
  workspaceRoot: string;
@@ -626,8 +627,8 @@ export type CheckRenderedMemoryMapResult =
626
627
  * the workspace root;
627
628
  * - a renderer change (output shape, or a `RENDERER_VERSION` bump).
628
629
  *
629
- * AND THE NEGATIVE, which is the defect this list was corrected for (CR-01,
630
- * `29-VERIFICATION.md` gap 1): relocating the checkout -- the same tree at a
630
+ * AND THE NEGATIVE, which is the defect this list was corrected for: relocating
631
+ * the checkout -- the same tree at a
631
632
  * different absolute path -- does NOT drift. The banner records
632
633
  * workspace-relative locations, so no compared byte is a function of where the
633
634
  * checkout sits. Before that fix this returned `drifted` for a byte-identical
@@ -1,14 +1,14 @@
1
1
  // anno-provenance-ledger.ts -- the ONE place `recovery/PROVENANCE.md`'s
2
- // generated tier is turned back into typed rows (BUILD-05, phase 46 plan 01).
2
+ // generated tier is turned back into typed rows.
3
3
  //
4
4
  // ---------------------------------------------------------------------------
5
5
  // WHY THIS FILE EXISTS
6
6
  // ---------------------------------------------------------------------------
7
7
  // Nothing in this repository has ever READ the provenance ledger. It has one
8
8
  // writer -- `renderLedger()` in `src/skills/c64-provenance-diff/scripts/
9
- // diff-images.mjs` -- and zero readers, because BUILD-05's own text requires
10
- // the verdict be READ from the existing ledger, "never re-derived"
11
- // (assumption A2, `46-01-PLAN.md`). Recomputing it in-process would force the
9
+ // diff-images.mjs` -- and zero readers, because the requirement governing this
10
+ // module requires the verdict be READ from the existing ledger, "never
11
+ // re-derived". Recomputing it in-process would force the
12
12
  // exporter to import a registry-resolution path it has no other reason to
13
13
  // know about, and would make "ledger absent" ambiguous between three
14
14
  // different missing things. This module exists so "read" has an implementation
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  // anno-regbits-gen.ts -- the ONE authoritative place in this repo that turns
3
3
  // c64-memory-mapping's memmap.json into the curated address->bit-name table
4
- // anno-enum-gen.ts decodes register values against (D-22, ANNO-13).
4
+ // anno-enum-gen.ts decodes register values against.
5
5
  //
6
- // WHY THIS EXISTS (D-22): neither register the phase's own pinned criterion-3
6
+ // WHY THIS EXISTS: neither register the phase's own pinned criterion-3
7
7
  // target needs ($D011) nor the registers a real game writes to constantly
8
8
  // ($D015/$D017/$D01A-$D01D) can be named from memmap.json's own `bits` prose
9
9
  // alone -- some of that prose is OCR-damaged ("O = Blank" uses a letter O for
@@ -54,11 +54,11 @@ const HERE = dirname(fileURLToPath(import.meta.url));
54
54
 
55
55
  /** The sole read of c64-memory-mapping's own memmap.json -- this generator is
56
56
  * its only consumer for this purpose (per this plan's key_links entry).
57
- * 2026-08-22 (plan 16-01): the skills tree moved from `.claude/skills/`
57
+ * 2026-08-22: the skills tree moved from `.claude/skills/`
58
58
  * (two levels up from `.claude/mcp/vice`) to `src/skills/` (three levels up,
59
59
  * since `src/` sits directly under the repo root rather than under `.claude/`).
60
- * This literal was not in plan 16-01's own enumerated consumer list and its
61
- * test failures caught the gap live -- see 16-01-SUMMARY.md deviations. */
60
+ * This literal was not in the original enumerated consumer list, and a
61
+ * test failure caught the gap live. */
62
62
  const MEMMAP_PATH = join(HERE, "..", "..", "..", "src", "skills", "c64-memory-mapping", "memmap.json");
63
63
 
64
64
  /** Where the generated, committed artifact lives -- always a sibling of this
@@ -163,14 +163,14 @@ export interface RegbitsRegisterOverride {
163
163
  * entries by their `bit` string. */
164
164
  fields?: readonly RegbitsFieldOverride[];
165
165
  /** A COMPLETE field list for an address memmap.json's `io` parser produced
166
- * no `bits` entry for at all (D-22's known gap) -- used only when no
166
+ * no `bits` entry for at all -- used only when no
167
167
  * memmap entry exists for this address, never to replace one that does. */
168
168
  synthetic?: readonly RegBitsField[];
169
169
  }
170
170
 
171
171
  /** Builds the 8 independent "bit N = sprite N" flag fields the five VIC
172
172
  * sprite-plane registers ($D015/$D017/$D01B/$D01C/$D01D) all share the same
173
- * shape for (D-22's named gap) -- each bit is silent (empty token) when
173
+ * shape for -- each bit is silent (empty token) when
174
174
  * clear and names the specific sprite when set, so a typical enum (most
175
175
  * sprites off, one or two on) renders as a short, readable name instead of
176
176
  * naming all eight sprites' negative state every time.
@@ -236,15 +236,15 @@ export const OVERRIDES: readonly RegbitsRegisterOverride[] = [
236
236
  label: "CIA Interrupt Control Register (Read NMIs/Write Mask)",
237
237
  },
238
238
  {
239
- // WHY: memmap.json's `io` parser produced no `bits` entry at all for this address (D-22's
240
- // named gap) even though a real game writes to it constantly -- sprite enable, one flag bit
239
+ // WHY: memmap.json's `io` parser produced no `bits` entry at all for this address,
240
+ // even though a real game writes to it constantly -- sprite enable, one flag bit
241
241
  // per sprite, is trivially regular and does not need memmap's own prose to describe correctly.
242
242
  address: 53269, // $D015 -- Sprite Enable
243
243
  label: "Sprite Enable",
244
244
  synthetic: spriteBitFields("EN"),
245
245
  },
246
246
  {
247
- // WHY: same D-22 gap as $D015 -- Sprite Y-Expand, one flag bit per sprite.
247
+ // WHY: same missing-`bits`-entry gap as $D015 -- Sprite Y-Expand, one flag bit per sprite.
248
248
  address: 53271, // $D017 -- Sprite Y-Expand
249
249
  label: "Sprite Y-Expand",
250
250
  synthetic: spriteBitFields("YEXP"),
@@ -264,20 +264,20 @@ export const OVERRIDES: readonly RegbitsRegisterOverride[] = [
264
264
  ],
265
265
  },
266
266
  {
267
- // WHY: same D-22 gap as $D015 -- Sprite Priority (behind/in-front of background), one flag bit
267
+ // WHY: same missing-`bits`-entry gap as $D015 -- Sprite Priority (behind/in-front of background), one flag bit
268
268
  // per sprite.
269
269
  address: 53275, // $D01B -- Sprite Data Priority
270
270
  label: "Sprite Data Priority",
271
271
  synthetic: spriteBitFields("BG"),
272
272
  },
273
273
  {
274
- // WHY: same D-22 gap as $D015 -- Sprite Multicolor, one flag bit per sprite.
274
+ // WHY: same missing-`bits`-entry gap as $D015 -- Sprite Multicolor, one flag bit per sprite.
275
275
  address: 53276, // $D01C -- Sprite Multicolor
276
276
  label: "Sprite Multicolor",
277
277
  synthetic: spriteBitFields("MC"),
278
278
  },
279
279
  {
280
- // WHY: same D-22 gap as $D015 -- Sprite X-Expand, one flag bit per sprite.
280
+ // WHY: same missing-`bits`-entry gap as $D015 -- Sprite X-Expand, one flag bit per sprite.
281
281
  address: 53277, // $D01D -- Sprite X-Expand
282
282
  label: "Sprite X-Expand",
283
283
  synthetic: spriteBitFields("XEXP"),
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // anno-store-export.ts
3
3
  //
4
- // Phase 45, plan 45-02 (D-02): the general JSON export/import module for a
4
+ // The general JSON export/import module for a
5
5
  // per-fixture `.annostore` -- NOT Ghidra-shaped (that is `anno-import.ts`'s
6
6
  // job, and it writes cross-references only). No general store-JSON round
7
7
  // trip exists anywhere else in this tree today (RESEARCH.md Section 5,
@@ -16,7 +16,7 @@
16
16
  //
17
17
  // WHAT THIS IS THE ONE AUTHORITATIVE PLACE FOR:
18
18
  // - The JSON schema for a full store export: typed ranges, labels,
19
- // comments (tagged `provenance: "derived" | "authored"` per D-03),
19
+ // comments (tagged `provenance: "derived" | "authored"`),
20
20
  // project enums and their usage bindings, cross-references, and
21
21
  // runtime-execution-observation rows.
22
22
  // - The three decline/provenance comment-text conventions
@@ -33,7 +33,7 @@
33
33
  // successfully (`anno-import.ts`'s own rule, copied deliberately: a
34
34
  // partial import must never leave the store in a state that is neither
35
35
  // the old nor the new content).
36
- // - Never collapse the derived/authored provenance tag (D-03) -- the
36
+ // - Never collapse the derived/authored provenance tag -- the
37
37
  // export schema exists specifically so a diff shows an authored
38
38
  // purpose-comment change, which a binary `.annostore` cannot.
39
39
  // - Never resolve a workspace path here -- the caller does, through
@@ -41,11 +41,11 @@
41
41
  // - Never persist `provenance` as a real store column. It is NOT one:
42
42
  // `RangeRow`/`CommentRow` (`anno-types.ts`) carry no such field, and
43
43
  // `setDataType()`/`setComment()` accept no such argument. Ranges are
44
- // ALWAYS the derived half (D-03: "block types ... regenerate
44
+ // ALWAYS the derived half ("block types ... regenerate
45
45
  // deterministically from the bytes") -- there is no per-row fact in the
46
46
  // schema that could ever make one authored instead, so `"derived"` is a
47
47
  // constant for every exported range, not a per-row classification.
48
- // Comments are ALWAYS the authored half (D-03, confirmed again by
48
+ // Comments are ALWAYS the authored half (confirmed again by
49
49
  // RESEARCH.md Section 5: "labels/comments/(and ... decline-comments) are
50
50
  // the authored half") -- `provenanceForComment()` is written as a
51
51
  // function of the comment's own text (rather than a bare constant) only
@@ -123,7 +123,7 @@ import type { DataType, CommentType, LabelKind, XrefAccessKind, EvidSourceBank }
123
123
  * standing decline-by-name pattern), never best-effort imported. */
124
124
  export const STORE_EXPORT_SCHEMA_VERSION = 1;
125
125
 
126
- /** The two provenance classes D-03 splits the store into. See this file's
126
+ /** The two provenance classes the store is split into (derived vs authored). See this file's
127
127
  * own header for why this is never a real store column. */
128
128
  export type RowProvenance = "derived" | "authored";
129
129
 
@@ -137,7 +137,7 @@ export const DECLINE_COMMENT_PREFIX = "DECLINED:";
137
137
  * reviewed and chose to accept rather than reclassify. */
138
138
  export const DISAGREEMENT_ACCEPTED_COMMENT_PREFIX = "DISAGREEMENT-ACCEPTED:";
139
139
 
140
- /** D-10's per-range authored marker: an explicit statement that a range's
140
+ /** The per-range authored marker: an explicit statement that a range's
141
141
  * typing rests on authored judgement rather than derivation or runtime
142
142
  * observation. */
143
143
  export const AUTHORED_PROVENANCE_COMMENT_PREFIX = "PROVENANCE: authored";
@@ -227,7 +227,7 @@ export interface StoreExportXrefRow {
227
227
  * and no `provenance`: `ScopeRow` (`anno-types.ts`) is a bare `{id, start,
228
228
  * endInclusive}` -- a lexical region, not a memory view, and not itself
229
229
  * derived from or authored over bytes the way a range or a comment is. Added
230
- * for the multi-file export path (Phase 47): `exportAsmTree()` reads scopes
230
+ * for the multi-file export path: `exportAsmTree()` reads scopes
231
231
  * straight off an open store handle via `listScopes()`, never from this
232
232
  * document, so a scope this generic export omitted would silently vanish the
233
233
  * moment a committed export got re-imported into a fresh store ahead of that
@@ -314,7 +314,7 @@ function assertRowProvenance(value: unknown, what: string): RowProvenance {
314
314
  }
315
315
 
316
316
  /**
317
- * 45-REVIEW WR-01's fix: `bank` is exported faithfully by `exportStoreDocument()`
317
+ * This fix: `bank` is exported faithfully by `exportStoreDocument()`
318
318
  * for every row kind (`StoreExportRangeRow.bank`, `StoreExportLabelRow.bank`,
319
319
  * `StoreExportCommentRow.bank`, `StoreExportEnumUsageRow.bank`,
320
320
  * `StoreExportXrefRow.bank`), but no write call on `anno-store.ts`'s current
@@ -322,8 +322,8 @@ function assertRowProvenance(value: unknown, what: string): RowProvenance {
322
322
  * accepts a `bank` argument -- every fresh insert hard-codes `bank: null`
323
323
  * (`anno-store.ts:1890`). Threading a real value through five write calls with
324
324
  * no bank-carrying writer anywhere in the codebase to prove it against would be
325
- * exactly the kind of speculative widening this project's other modules refuse
326
- * (D-10's "narrower than the review's own sketch" pattern). Refusing a non-null
325
+ * exactly the kind of speculative widening this project's other modules refuse.
326
+ * Refusing a non-null
327
327
  * `bank` BY NAME instead -- this project's standing "refuse by name, never
328
328
  * silently drop" convention (`enumUsage[i]` naming an undefined enum, above, is
329
329
  * the same shape) -- means the moment a real writer starts producing a