c64bridge 0.9.0 → 0.9.2
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 +161 -135
- package/data/assembly/assembly-spec.md +1 -1
- package/data/context/bootstrap.md +3 -3
- package/data/context/fast-paths.md +3 -1
- package/data/graphics/petscii-style-guide.md +1 -1
- package/data/graphics/sprite-charset-best-practices.md +14 -13
- package/data/sound/sid-file-structure.md +5 -5
- package/data/sound/sid-programming-best-practices.md +5 -5
- package/data/vice/vice-binary-monitor-spec.md +26 -10
- package/dist/.tsbuildinfo +1 -1
- package/dist/c64Client.js +107 -8
- package/dist/device.js +43 -3
- package/dist/rag/indexer.js +18 -8
- package/dist/sidwaveCompiler.js +1 -1
- package/dist/tools/debug.js +158 -5
- package/dist/tools/input.js +336 -0
- package/dist/tools/memory.js +326 -1
- package/dist/tools/programRunners.js +45 -6
- package/dist/tools/registry/batch.js +167 -0
- package/dist/tools/registry/graphics.js +101 -1
- package/dist/tools/registry/index.js +4 -0
- package/dist/tools/registry/input.js +2 -0
- package/dist/tools/registry/memory.js +61 -1
- package/dist/tools/responses.js +14 -1
- package/dist/tools/schema.js +4 -5
- package/dist/{assemblyConverter.js → tools/translation/assembler.js} +66 -163
- package/dist/{basicConverter.js → tools/translation/basicTokenizer.js} +1 -1
- package/dist/tools/translation/disassembler.js +150 -0
- package/dist/tools/translation/opcodeInventory.js +126 -0
- package/dist/tools/translation/symbolRegistry.js +49 -0
- package/dist/tools/types.js +19 -4
- package/dist/vice/process.js +43 -2
- package/dist/vice/viceClient.js +5 -1
- package/doc/reviews/audit.md +143 -0
- package/mcp/schemas/c64_batch.schema.json +53 -0
- package/mcp/schemas/c64_config.schema.json +461 -2
- package/mcp/schemas/c64_debug.schema.json +641 -3
- package/mcp/schemas/c64_disk.schema.json +340 -2
- package/mcp/schemas/c64_drive.schema.json +157 -2
- package/mcp/schemas/c64_extract.schema.json +359 -2
- package/mcp/schemas/c64_graphics.schema.json +415 -3
- package/mcp/schemas/c64_input.schema.json +254 -0
- package/mcp/schemas/c64_memory.schema.json +562 -3
- package/mcp/schemas/c64_printer.schema.json +253 -2
- package/mcp/schemas/c64_program.schema.json +499 -2
- package/mcp/schemas/c64_rag.schema.json +82 -2
- package/mcp/schemas/c64_select_backend.schema.json +35 -2
- package/mcp/schemas/c64_sound.schema.json +793 -2
- package/mcp/schemas/c64_stream.schema.json +73 -2
- package/mcp/schemas/c64_system.schema.json +301 -2
- package/mcp/schemas/c64_vice.schema.json +71 -2
- package/mcp/server.json +1 -1
- package/mcp/tools.json +7509 -1929
- package/mcp.json +1 -1
- package/package.json +2 -2
- package/scripts/c64-cli.mjs +2 -2
- package/scripts/run-coverage.mjs +61 -3
- package/scripts/run-tests.mjs +1 -0
- package/scripts/run-tests.ts +3 -0
- package/scripts/update-readme.ts +79 -2
- package/server.json +2 -2
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 6502/6510 disassembler covering the full 256-opcode space, including
|
|
3
|
+
* undocumented (illegal) opcodes. Mnemonic naming follows the canonical
|
|
4
|
+
* convention used by the 1541U monitor (HLT->JAM, ASO->SLO, LSE->SRE,
|
|
5
|
+
* DCM->DCP, INS->ISC).
|
|
6
|
+
*/
|
|
7
|
+
import { OpcodeAddressingMode as Mode, OPCODES_BY_BYTE as OPCODES, opcodeSize as modeSize, } from "./opcodeInventory.js";
|
|
8
|
+
function hex2(v) {
|
|
9
|
+
return v.toString(16).toUpperCase().padStart(2, "0");
|
|
10
|
+
}
|
|
11
|
+
function hex4(v) {
|
|
12
|
+
return v.toString(16).toUpperCase().padStart(4, "0");
|
|
13
|
+
}
|
|
14
|
+
function labelOrHex4(addr, symbols) {
|
|
15
|
+
const label = symbols?.get(addr);
|
|
16
|
+
return label ? `${label} ($${hex4(addr)})` : `$${hex4(addr)}`;
|
|
17
|
+
}
|
|
18
|
+
function formatOperand(mode, bytes, offset, instrAddr, symbols) {
|
|
19
|
+
switch (mode) {
|
|
20
|
+
case Mode.Imp:
|
|
21
|
+
return "";
|
|
22
|
+
case Mode.Acc:
|
|
23
|
+
return "A";
|
|
24
|
+
case Mode.Imm:
|
|
25
|
+
return `#$${hex2(bytes[offset + 1] ?? 0)}`;
|
|
26
|
+
case Mode.Zp:
|
|
27
|
+
return `$${hex2(bytes[offset + 1] ?? 0)}`;
|
|
28
|
+
case Mode.ZpX:
|
|
29
|
+
return `$${hex2(bytes[offset + 1] ?? 0)},X`;
|
|
30
|
+
case Mode.ZpY:
|
|
31
|
+
return `$${hex2(bytes[offset + 1] ?? 0)},Y`;
|
|
32
|
+
case Mode.ZpS:
|
|
33
|
+
return `$${hex2(bytes[offset + 1] ?? 0)},S`;
|
|
34
|
+
case Mode.Rel: {
|
|
35
|
+
const rel = bytes[offset + 1] ?? 0;
|
|
36
|
+
const signed = rel >= 0x80 ? rel - 0x100 : rel;
|
|
37
|
+
const target = (instrAddr + 2 + signed) & 0xffff;
|
|
38
|
+
return labelOrHex4(target, symbols);
|
|
39
|
+
}
|
|
40
|
+
case Mode.Abs: {
|
|
41
|
+
const lo = bytes[offset + 1] ?? 0;
|
|
42
|
+
const hi = bytes[offset + 2] ?? 0;
|
|
43
|
+
const addr = lo | (hi << 8);
|
|
44
|
+
return labelOrHex4(addr, symbols);
|
|
45
|
+
}
|
|
46
|
+
case Mode.AbsX: {
|
|
47
|
+
const lo = bytes[offset + 1] ?? 0;
|
|
48
|
+
const hi = bytes[offset + 2] ?? 0;
|
|
49
|
+
const addr = lo | (hi << 8);
|
|
50
|
+
return `${labelOrHex4(addr, symbols)},X`;
|
|
51
|
+
}
|
|
52
|
+
case Mode.AbsY: {
|
|
53
|
+
const lo = bytes[offset + 1] ?? 0;
|
|
54
|
+
const hi = bytes[offset + 2] ?? 0;
|
|
55
|
+
const addr = lo | (hi << 8);
|
|
56
|
+
return `${labelOrHex4(addr, symbols)},Y`;
|
|
57
|
+
}
|
|
58
|
+
case Mode.Ind: {
|
|
59
|
+
const lo = bytes[offset + 1] ?? 0;
|
|
60
|
+
const hi = bytes[offset + 2] ?? 0;
|
|
61
|
+
const addr = lo | (hi << 8);
|
|
62
|
+
return `($${hex4(addr)})`;
|
|
63
|
+
}
|
|
64
|
+
case Mode.IndX:
|
|
65
|
+
return `($${hex2(bytes[offset + 1] ?? 0)},X)`;
|
|
66
|
+
case Mode.IndY:
|
|
67
|
+
return `($${hex2(bytes[offset + 1] ?? 0)}),Y`;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Disassemble a block of 6502/6510 bytes.
|
|
72
|
+
*
|
|
73
|
+
* @param bytes Raw memory bytes (may be longer than needed)
|
|
74
|
+
* @param baseAddress The C64 memory address of `bytes[0]`
|
|
75
|
+
* @param countOrOptions Either a max-instruction count (legacy form) or an options object
|
|
76
|
+
* @param symbols Optional address->label map (legacy positional form)
|
|
77
|
+
*/
|
|
78
|
+
export function disassemble(bytes, baseAddress, countOrOptions, symbols) {
|
|
79
|
+
const options = typeof countOrOptions === "object" && countOrOptions !== null
|
|
80
|
+
? countOrOptions
|
|
81
|
+
: { count: countOrOptions, symbols };
|
|
82
|
+
const limit = options.count ?? Infinity;
|
|
83
|
+
const symMap = options.symbols ?? symbols;
|
|
84
|
+
const allowUndocumented = options.allowUndocumented !== false;
|
|
85
|
+
const lines = [];
|
|
86
|
+
let offset = 0;
|
|
87
|
+
while (offset < bytes.length && lines.length < limit) {
|
|
88
|
+
const instrAddr = (baseAddress + offset) & 0xffff;
|
|
89
|
+
const opcode = bytes[offset] ?? 0;
|
|
90
|
+
const op = OPCODES[opcode];
|
|
91
|
+
const label = symMap?.get(instrAddr);
|
|
92
|
+
if (op.illegal && !allowUndocumented) {
|
|
93
|
+
lines.push({
|
|
94
|
+
address: instrAddr,
|
|
95
|
+
bytes: [opcode],
|
|
96
|
+
label,
|
|
97
|
+
mnemonic: "???",
|
|
98
|
+
operand: `$${hex2(opcode)}`,
|
|
99
|
+
undocumented: true,
|
|
100
|
+
});
|
|
101
|
+
offset += 1;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const size = modeSize(op.mode);
|
|
105
|
+
const instrBytes = [];
|
|
106
|
+
for (let i = 0; i < size && offset + i < bytes.length; i += 1) {
|
|
107
|
+
instrBytes.push(bytes[offset + i] ?? 0);
|
|
108
|
+
}
|
|
109
|
+
const operand = formatOperand(op.mode, bytes, offset, instrAddr, symMap);
|
|
110
|
+
lines.push({
|
|
111
|
+
address: instrAddr,
|
|
112
|
+
bytes: instrBytes,
|
|
113
|
+
label,
|
|
114
|
+
mnemonic: op.mnemonic,
|
|
115
|
+
operand,
|
|
116
|
+
undocumented: op.illegal,
|
|
117
|
+
});
|
|
118
|
+
offset += size;
|
|
119
|
+
}
|
|
120
|
+
return lines;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Render disassembly lines as a human-readable string.
|
|
124
|
+
* Format: [label:\n] $ADDR XX XX XX MNEM operand ; *undocumented*
|
|
125
|
+
*/
|
|
126
|
+
export function formatDisassembly(lines) {
|
|
127
|
+
const parts = [];
|
|
128
|
+
for (const line of lines) {
|
|
129
|
+
if (line.label) {
|
|
130
|
+
parts.push(`${line.label}:`);
|
|
131
|
+
}
|
|
132
|
+
const addrStr = `$${hex4(line.address)}`;
|
|
133
|
+
const byteStr = line.bytes.map((b) => hex2(b)).join(" ").padEnd(8);
|
|
134
|
+
const mnemonic = line.undocumented ? `${line.mnemonic}*` : line.mnemonic;
|
|
135
|
+
const instrStr = line.operand
|
|
136
|
+
? `${mnemonic} ${line.operand}`
|
|
137
|
+
: mnemonic;
|
|
138
|
+
parts.push(` ${addrStr} ${byteStr} ${instrStr}`);
|
|
139
|
+
}
|
|
140
|
+
return parts.join("\n");
|
|
141
|
+
}
|
|
142
|
+
/** Returns true when the opcode is undocumented per the 1541U reference. */
|
|
143
|
+
export function isUndocumentedOpcode(opcode) {
|
|
144
|
+
return OPCODES[opcode & 0xff].illegal;
|
|
145
|
+
}
|
|
146
|
+
/** Lookup helper used by tests to verify table coverage. */
|
|
147
|
+
export function opcodeMetadata(opcode) {
|
|
148
|
+
const entry = OPCODES[opcode & 0xff];
|
|
149
|
+
return { mnemonic: entry.mnemonic, undocumented: entry.illegal, size: modeSize(entry.mode) };
|
|
150
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared 6502/6510 opcode inventory.
|
|
3
|
+
*
|
|
4
|
+
* Entries are sorted by increasing binary opcode and cover the complete
|
|
5
|
+
* 256-byte opcode space, including undocumented opcodes. Mnemonic naming
|
|
6
|
+
* follows the canonical 1541U monitor convention:
|
|
7
|
+
* HLT -> JAM, ASO -> SLO, LSE -> SRE, DCM -> DCP, INS -> ISC.
|
|
8
|
+
*/
|
|
9
|
+
export const OpcodeAddressingMode = {
|
|
10
|
+
Imp: "implied",
|
|
11
|
+
Acc: "accumulator",
|
|
12
|
+
Imm: "immediate",
|
|
13
|
+
Zp: "zeroPage",
|
|
14
|
+
ZpX: "zeroPageX",
|
|
15
|
+
ZpY: "zeroPageY",
|
|
16
|
+
ZpS: "zeroPageS",
|
|
17
|
+
Rel: "relative",
|
|
18
|
+
Abs: "absolute",
|
|
19
|
+
AbsX: "absoluteX",
|
|
20
|
+
AbsY: "absoluteY",
|
|
21
|
+
Ind: "indirect",
|
|
22
|
+
IndX: "indirectX",
|
|
23
|
+
IndY: "indirectY",
|
|
24
|
+
};
|
|
25
|
+
const M = OpcodeAddressingMode;
|
|
26
|
+
const OPCODE_ROWS = [
|
|
27
|
+
// 0x00-0x0F
|
|
28
|
+
[["BRK", M.Imp], ["ORA", M.IndX], ["HLT", M.Imp], ["ASO", M.IndX], ["NOP", M.Zp], ["ORA", M.Zp], ["ASL", M.Zp], ["ASO", M.Zp], ["PHP", M.Imp], ["ORA", M.Imm], ["ASL", M.Acc], ["ANC", M.Imm], ["NOP", M.Abs], ["ORA", M.Abs], ["ASL", M.Abs], ["ASO", M.Abs]],
|
|
29
|
+
// 0x10-0x1F
|
|
30
|
+
[["BPL", M.Rel], ["ORA", M.IndY], ["HLT", M.Imp], ["ASO", M.IndY], ["NOP", M.ZpX], ["ORA", M.ZpX], ["ASL", M.ZpX], ["ASO", M.ZpX], ["CLC", M.Imp], ["ORA", M.AbsY], ["NOP", M.Imp], ["ASO", M.AbsY], ["NOP", M.AbsX], ["ORA", M.AbsX], ["ASL", M.AbsX], ["ASO", M.AbsX]],
|
|
31
|
+
// 0x20-0x2F
|
|
32
|
+
[["JSR", M.Abs], ["AND", M.IndX], ["HLT", M.Imp], ["RLA", M.IndX], ["BIT", M.Zp], ["AND", M.Zp], ["ROL", M.Zp], ["RLA", M.Zp], ["PLP", M.Imp], ["AND", M.Imm], ["ROL", M.Acc], ["ANC", M.Imm], ["BIT", M.Abs], ["AND", M.Abs], ["ROL", M.Abs], ["RLA", M.Abs]],
|
|
33
|
+
// 0x30-0x3F
|
|
34
|
+
[["BMI", M.Rel], ["AND", M.IndY], ["HLT", M.Imp], ["RLA", M.IndY], ["NOP", M.ZpX], ["AND", M.ZpX], ["ROL", M.ZpX], ["RLA", M.ZpX], ["SEC", M.Imp], ["AND", M.AbsY], ["NOP", M.Imp], ["RLA", M.AbsY], ["NOP", M.AbsX], ["AND", M.AbsX], ["ROL", M.AbsX], ["RLA", M.AbsX]],
|
|
35
|
+
// 0x40-0x4F
|
|
36
|
+
[["RTI", M.Imp], ["EOR", M.IndX], ["HLT", M.Imp], ["LSE", M.IndX], ["NOP", M.Zp], ["EOR", M.Zp], ["LSR", M.Zp], ["LSE", M.Zp], ["PHA", M.Imp], ["EOR", M.Imm], ["LSR", M.Acc], ["ALR", M.Imm], ["JMP", M.Abs], ["EOR", M.Abs], ["LSR", M.Abs], ["LSE", M.Abs]],
|
|
37
|
+
// 0x50-0x5F
|
|
38
|
+
[["BVC", M.Rel], ["EOR", M.IndY], ["HLT", M.Imp], ["LSE", M.IndY], ["NOP", M.ZpX], ["EOR", M.ZpX], ["LSR", M.ZpX], ["LSE", M.ZpX], ["CLI", M.Imp], ["EOR", M.AbsY], ["NOP", M.Imp], ["LSE", M.AbsY], ["NOP", M.AbsX], ["EOR", M.AbsX], ["LSR", M.AbsX], ["LSE", M.AbsX]],
|
|
39
|
+
// 0x60-0x6F
|
|
40
|
+
[["RTS", M.Imp], ["ADC", M.IndX], ["HLT", M.Imp], ["RRA", M.IndX], ["NOP", M.Zp], ["ADC", M.Zp], ["ROR", M.Zp], ["RRA", M.Zp], ["PLA", M.Imp], ["ADC", M.Imm], ["ROR", M.Acc], ["ARR", M.Imm], ["JMP", M.Ind], ["ADC", M.Abs], ["ROR", M.Abs], ["RRA", M.Abs]],
|
|
41
|
+
// 0x70-0x7F
|
|
42
|
+
[["BVS", M.Rel], ["ADC", M.IndY], ["HLT", M.Imp], ["RRA", M.IndY], ["NOP", M.ZpX], ["ADC", M.ZpX], ["ROR", M.ZpX], ["RRA", M.ZpX], ["SEI", M.Imp], ["ADC", M.AbsY], ["NOP", M.Imp], ["RRA", M.AbsY], ["NOP", M.AbsX], ["ADC", M.AbsX], ["ROR", M.AbsX], ["RRA", M.AbsX]],
|
|
43
|
+
// 0x80-0x8F
|
|
44
|
+
[["NOP", M.Imm], ["STA", M.IndX], ["NOP", M.Imm], ["SAX", M.IndX], ["STY", M.Zp], ["STA", M.Zp], ["STX", M.Zp], ["SAX", M.Zp], ["DEY", M.Imp], ["NOP", M.Imm], ["TXA", M.Imp], ["XAA", M.Imm], ["STY", M.Abs], ["STA", M.Abs], ["STX", M.Abs], ["SAX", M.Abs]],
|
|
45
|
+
// 0x90-0x9F
|
|
46
|
+
[["BCC", M.Rel], ["STA", M.IndY], ["HLT", M.Imp], ["AHX", M.IndY], ["STY", M.ZpX], ["STA", M.ZpX], ["STX", M.ZpY], ["SAX", M.ZpY], ["TYA", M.Imp], ["STA", M.AbsY], ["TXS", M.Imp], ["TAS", M.AbsY], ["SHY", M.AbsX], ["STA", M.AbsX], ["SHX", M.AbsY], ["AHX", M.AbsY]],
|
|
47
|
+
// 0xA0-0xAF
|
|
48
|
+
[["LDY", M.Imm], ["LDA", M.IndX], ["LDX", M.Imm], ["LAX", M.IndX], ["LDY", M.Zp], ["LDA", M.Zp], ["LDX", M.Zp], ["LAX", M.Zp], ["TAY", M.Imp], ["LDA", M.Imm], ["TAX", M.Imp], ["LAX", M.Imm], ["LDY", M.Abs], ["LDA", M.Abs], ["LDX", M.Abs], ["LAX", M.Abs]],
|
|
49
|
+
// 0xB0-0xBF
|
|
50
|
+
[["BCS", M.Rel], ["LDA", M.IndY], ["HLT", M.Imp], ["LAX", M.IndY], ["LDY", M.ZpX], ["LDA", M.ZpX], ["LDX", M.ZpY], ["LAX", M.ZpY], ["CLV", M.Imp], ["LDA", M.AbsY], ["TSX", M.Imp], ["LAS", M.AbsY], ["LDY", M.AbsX], ["LDA", M.AbsX], ["LDX", M.AbsY], ["LAX", M.AbsY]],
|
|
51
|
+
// 0xC0-0xCF
|
|
52
|
+
[["CPY", M.Imm], ["CMP", M.IndX], ["NOP", M.Imm], ["DCM", M.IndX], ["CPY", M.Zp], ["CMP", M.Zp], ["DEC", M.Zp], ["DCM", M.Zp], ["INY", M.Imp], ["CMP", M.Imm], ["DEX", M.Imp], ["AXS", M.Imm], ["CPY", M.Abs], ["CMP", M.Abs], ["DEC", M.Abs], ["DCM", M.Abs]],
|
|
53
|
+
// 0xD0-0xDF
|
|
54
|
+
[["BNE", M.Rel], ["CMP", M.IndY], ["HLT", M.Imp], ["DCM", M.IndY], ["NOP", M.ZpX], ["CMP", M.ZpX], ["DEC", M.ZpX], ["DCM", M.ZpX], ["CLD", M.Imp], ["CMP", M.AbsY], ["NOP", M.Imp], ["DCM", M.AbsY], ["NOP", M.AbsX], ["CMP", M.AbsX], ["DEC", M.AbsX], ["DCM", M.AbsX]],
|
|
55
|
+
// 0xE0-0xEF
|
|
56
|
+
[["CPX", M.Imm], ["SBC", M.IndX], ["NOP", M.Imm], ["INS", M.IndX], ["CPX", M.Zp], ["SBC", M.Zp], ["INC", M.Zp], ["INS", M.Zp], ["INX", M.Imp], ["SBC", M.Imm], ["NOP", M.Imp], ["SBC", M.Imm], ["CPX", M.Abs], ["SBC", M.Abs], ["INC", M.Abs], ["INS", M.Abs]],
|
|
57
|
+
// 0xF0-0xFF
|
|
58
|
+
[["BEQ", M.Rel], ["SBC", M.IndY], ["HLT", M.Imp], ["INS", M.IndY], ["NOP", M.ZpS], ["SBC", M.ZpX], ["INC", M.ZpX], ["INS", M.ZpX], ["SED", M.Imp], ["SBC", M.AbsY], ["NOP", M.Imp], ["INS", M.AbsY], ["NOP", M.AbsX], ["SBC", M.AbsX], ["INC", M.AbsX], ["INS", M.AbsX]],
|
|
59
|
+
];
|
|
60
|
+
const ILLEGAL_MAP = [
|
|
61
|
+
0x989C, 0x9C9C, 0x888C, 0x9C9C,
|
|
62
|
+
0x889C, 0x9C9C, 0x889C, 0x9C9C,
|
|
63
|
+
0x8A8D, 0xD88C, 0x8888, 0x888C,
|
|
64
|
+
0x888C, 0x9C9C, 0x888C, 0x9C9C,
|
|
65
|
+
];
|
|
66
|
+
function isIllegal(opcode) {
|
|
67
|
+
const row = ILLEGAL_MAP[opcode >> 4];
|
|
68
|
+
return ((row >> (opcode & 0x0F)) & 1) !== 0;
|
|
69
|
+
}
|
|
70
|
+
function canonical(mnemonic) {
|
|
71
|
+
switch (mnemonic) {
|
|
72
|
+
case "HLT": return "JAM";
|
|
73
|
+
case "ASO": return "SLO";
|
|
74
|
+
case "LSE": return "SRE";
|
|
75
|
+
case "DCM": return "DCP";
|
|
76
|
+
case "INS": return "ISC";
|
|
77
|
+
default: return mnemonic;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function buildOpcodeInventory() {
|
|
81
|
+
const entries = OPCODE_ROWS.flatMap((row, rowIndex) => row.map(([mnemonic, mode], columnIndex) => {
|
|
82
|
+
const opcode = (rowIndex << 4) | columnIndex;
|
|
83
|
+
return {
|
|
84
|
+
opcode,
|
|
85
|
+
mnemonic: canonical(mnemonic),
|
|
86
|
+
mode,
|
|
87
|
+
illegal: isIllegal(opcode),
|
|
88
|
+
};
|
|
89
|
+
}));
|
|
90
|
+
validateOpcodeInventory(entries);
|
|
91
|
+
return entries;
|
|
92
|
+
}
|
|
93
|
+
function validateOpcodeInventory(entries) {
|
|
94
|
+
if (entries.length !== 256) {
|
|
95
|
+
throw new Error(`Opcode inventory must contain 256 entries, found ${entries.length}`);
|
|
96
|
+
}
|
|
97
|
+
for (let opcode = 0; opcode < 256; opcode += 1) {
|
|
98
|
+
const entry = entries[opcode];
|
|
99
|
+
if (!entry || entry.opcode !== opcode) {
|
|
100
|
+
throw new Error(`Opcode inventory is not sorted at $${opcode.toString(16).padStart(2, "0")}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export function opcodeSize(mode) {
|
|
105
|
+
switch (mode) {
|
|
106
|
+
case M.Imp:
|
|
107
|
+
case M.Acc:
|
|
108
|
+
return 1;
|
|
109
|
+
case M.Imm:
|
|
110
|
+
case M.Zp:
|
|
111
|
+
case M.ZpX:
|
|
112
|
+
case M.ZpY:
|
|
113
|
+
case M.ZpS:
|
|
114
|
+
case M.Rel:
|
|
115
|
+
case M.IndX:
|
|
116
|
+
case M.IndY:
|
|
117
|
+
return 2;
|
|
118
|
+
case M.Abs:
|
|
119
|
+
case M.AbsX:
|
|
120
|
+
case M.AbsY:
|
|
121
|
+
case M.Ind:
|
|
122
|
+
return 3;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export const OPCODE_INVENTORY = Object.freeze(buildOpcodeInventory());
|
|
126
|
+
export const OPCODES_BY_BYTE = OPCODE_INVENTORY;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-process symbol registry for VICE debug sessions.
|
|
3
|
+
* Maps addresses to label names so the disassembler can annotate output.
|
|
4
|
+
* Cleared and repopulated each time a new program is loaded.
|
|
5
|
+
*/
|
|
6
|
+
const viceAddressToLabel = new Map();
|
|
7
|
+
export function setViceSymbols(nameToAddress) {
|
|
8
|
+
viceAddressToLabel.clear();
|
|
9
|
+
for (const [name, address] of nameToAddress) {
|
|
10
|
+
if (address >= 0 && address <= 0xffff) {
|
|
11
|
+
viceAddressToLabel.set(address, name);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function getViceSymbol(address) {
|
|
16
|
+
return viceAddressToLabel.get(address);
|
|
17
|
+
}
|
|
18
|
+
export function clearViceSymbols() {
|
|
19
|
+
viceAddressToLabel.clear();
|
|
20
|
+
}
|
|
21
|
+
export function getViceSymbols() {
|
|
22
|
+
return viceAddressToLabel;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Parse a VICE symbol file (.vs) into a name->address map.
|
|
26
|
+
*
|
|
27
|
+
* Supported line formats:
|
|
28
|
+
* add_label 0x0810 .main
|
|
29
|
+
* add_label C:0810 .main
|
|
30
|
+
* al C:0810 .main
|
|
31
|
+
* al 0810 .main
|
|
32
|
+
*/
|
|
33
|
+
export function parseViceSymbolFile(content) {
|
|
34
|
+
const symbols = new Map();
|
|
35
|
+
for (const rawLine of content.split("\n")) {
|
|
36
|
+
const line = rawLine.trim();
|
|
37
|
+
if (!line || line.startsWith(";") || line.startsWith("#"))
|
|
38
|
+
continue;
|
|
39
|
+
const match = /^(?:add_label|al)\s+(?:[A-Za-z]+:)?(?:0x|\$)?([0-9A-Fa-f]+)\s+\.?(\S+)/i.exec(line);
|
|
40
|
+
if (match) {
|
|
41
|
+
const addr = parseInt(match[1], 16);
|
|
42
|
+
const name = match[2];
|
|
43
|
+
if (!isNaN(addr) && addr >= 0 && addr <= 0xffff && name) {
|
|
44
|
+
symbols.set(name, addr);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return symbols;
|
|
49
|
+
}
|
package/dist/tools/types.js
CHANGED
|
@@ -36,16 +36,30 @@ export function discriminatedUnionSchema(options) {
|
|
|
36
36
|
const opValues = [];
|
|
37
37
|
const mergedProperties = {};
|
|
38
38
|
const opDescriptions = [];
|
|
39
|
+
const operationMetadata = [];
|
|
39
40
|
for (const variant of variants) {
|
|
40
41
|
const variantProps = variant.properties ?? {};
|
|
41
42
|
const discProp = variantProps[discriminator];
|
|
42
43
|
const opValue = discProp?.const;
|
|
44
|
+
const variantDesc = variant.description ?? discProp?.description ?? "";
|
|
45
|
+
const requiredInputs = (variant.required ?? []).filter((name) => name !== discriminator);
|
|
46
|
+
const optionalInputs = Object.keys(variantProps).filter((name) => name !== discriminator && !requiredInputs.includes(name));
|
|
43
47
|
if (opValue) {
|
|
44
48
|
opValues.push(opValue);
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
const requiredSummary = requiredInputs.length > 0
|
|
50
|
+
? ` Required inputs: ${requiredInputs.join(", ")}.`
|
|
51
|
+
: " Required inputs: none.";
|
|
52
|
+
const optionalSummary = optionalInputs.length > 0
|
|
53
|
+
? ` Optional inputs: ${optionalInputs.join(", ")}.`
|
|
54
|
+
: "";
|
|
55
|
+
opDescriptions.push(`${opValue}: ${variantDesc || `Operation ${opValue}`}${requiredSummary}${optionalSummary}`);
|
|
56
|
+
operationMetadata.push({
|
|
57
|
+
op: opValue,
|
|
58
|
+
...(variantDesc ? { description: variantDesc } : {}),
|
|
59
|
+
required: requiredInputs,
|
|
60
|
+
optional: optionalInputs,
|
|
61
|
+
inputSchema: variant,
|
|
62
|
+
});
|
|
49
63
|
}
|
|
50
64
|
for (const [key, propSchema] of Object.entries(variantProps)) {
|
|
51
65
|
if (key === discriminator)
|
|
@@ -74,6 +88,7 @@ export function discriminatedUnionSchema(options) {
|
|
|
74
88
|
},
|
|
75
89
|
required: [discriminator],
|
|
76
90
|
additionalProperties: false,
|
|
91
|
+
...(operationMetadata.length > 0 ? { "x-c64bridge-operations": operationMetadata } : {}),
|
|
77
92
|
};
|
|
78
93
|
return schema;
|
|
79
94
|
}
|
package/dist/vice/process.js
CHANGED
|
@@ -187,8 +187,19 @@ export async function terminateProcess(child, signal = "SIGTERM", timeoutMs = 10
|
|
|
187
187
|
}
|
|
188
188
|
export async function startViceProcess(options) {
|
|
189
189
|
const debugEnabled = process.env.VICE_DEVICE_TEST_DEBUG === "1";
|
|
190
|
-
|
|
191
|
-
const
|
|
190
|
+
// When headless=true, skip Xvfb entirely and rely on VICE's own -headless flag (VICE 3.5+).
|
|
191
|
+
const useHeadless = options.headless === true || process.env.VICE_HEADLESS === "1";
|
|
192
|
+
let useXvfb;
|
|
193
|
+
let display;
|
|
194
|
+
if (useHeadless) {
|
|
195
|
+
useXvfb = false;
|
|
196
|
+
display = "";
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
const probe = shouldUseXvfb(options.visible);
|
|
200
|
+
useXvfb = probe.useXvfb;
|
|
201
|
+
display = useXvfb ? resolveXvfbDisplayForLaunch(probe.display) : probe.display;
|
|
202
|
+
}
|
|
192
203
|
const viceEnv = { ...process.env };
|
|
193
204
|
let xvfb = null;
|
|
194
205
|
const xvfbOutput = createOutputTailCapture("xvfb");
|
|
@@ -198,6 +209,7 @@ export async function startViceProcess(options) {
|
|
|
198
209
|
directory: options.directory,
|
|
199
210
|
display,
|
|
200
211
|
extraArgs: options.extraArgs ?? [],
|
|
212
|
+
headless: useHeadless,
|
|
201
213
|
host: options.host,
|
|
202
214
|
port: options.port,
|
|
203
215
|
useXvfb,
|
|
@@ -239,6 +251,7 @@ export async function startViceProcess(options) {
|
|
|
239
251
|
"-binarymonitoraddress", `${options.host}:${options.port}`,
|
|
240
252
|
"-sounddev", "dummy",
|
|
241
253
|
"-config", "/dev/null",
|
|
254
|
+
...(useHeadless ? ["-headless"] : []),
|
|
242
255
|
...(options.directory ? ["-directory", options.directory] : []),
|
|
243
256
|
...(options.extraArgs ?? []),
|
|
244
257
|
];
|
|
@@ -370,15 +383,43 @@ export async function startViceProcess(options) {
|
|
|
370
383
|
});
|
|
371
384
|
return { host: options.host, port: options.port, process: child, stop };
|
|
372
385
|
}
|
|
386
|
+
function isIgnorableSocketDirError(error) {
|
|
387
|
+
if (!error || typeof error !== "object" || !("code" in error)) {
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
const code = String(error.code ?? "");
|
|
391
|
+
return code === "EACCES" || code === "EPERM" || code === "EROFS" || code === "EEXIST";
|
|
392
|
+
}
|
|
373
393
|
export function ensureXvfbSocketDir(debugEnabled) {
|
|
374
394
|
const socketDir = "/tmp/.X11-unix";
|
|
375
395
|
try {
|
|
376
396
|
if (!fs.existsSync(socketDir)) {
|
|
377
397
|
fs.mkdirSync(socketDir, { mode: 0o1777 });
|
|
378
398
|
}
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
if (isIgnorableSocketDirError(error)) {
|
|
402
|
+
if (debugEnabled) {
|
|
403
|
+
console.error("[vice-process] unable to create Xvfb socket dir; continuing", error);
|
|
404
|
+
}
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
writeDiagnosticEvent("xvfb_socket_dir_failed", { socketDir, error });
|
|
408
|
+
if (debugEnabled) {
|
|
409
|
+
console.error("[vice-process] failed to prepare Xvfb socket dir", error);
|
|
410
|
+
}
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
379
414
|
fs.chmodSync(socketDir, 0o1777);
|
|
380
415
|
}
|
|
381
416
|
catch (error) {
|
|
417
|
+
if (isIgnorableSocketDirError(error)) {
|
|
418
|
+
if (debugEnabled) {
|
|
419
|
+
console.error("[vice-process] unable to chmod Xvfb socket dir; continuing", error);
|
|
420
|
+
}
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
382
423
|
writeDiagnosticEvent("xvfb_socket_dir_failed", { socketDir, error });
|
|
383
424
|
if (debugEnabled) {
|
|
384
425
|
console.error("[vice-process] failed to prepare Xvfb socket dir", error);
|
package/dist/vice/viceClient.js
CHANGED
|
@@ -9,6 +9,9 @@ export class ViceClient {
|
|
|
9
9
|
this.pending = new Map();
|
|
10
10
|
this.closing = false;
|
|
11
11
|
}
|
|
12
|
+
setUnsolicitedHandler(handler) {
|
|
13
|
+
this.onUnsolicitedHandler = handler;
|
|
14
|
+
}
|
|
12
15
|
async connect(port, host = "127.0.0.1") {
|
|
13
16
|
this.socket = net.connect({ host, port });
|
|
14
17
|
this.closing = false;
|
|
@@ -77,7 +80,8 @@ export class ViceClient {
|
|
|
77
80
|
const err = frame[7];
|
|
78
81
|
const reqId = frame.readUInt32LE(8);
|
|
79
82
|
if (reqId === 0xffffffff) {
|
|
80
|
-
|
|
83
|
+
this.onUnsolicitedHandler?.(responseType, frame);
|
|
84
|
+
continue;
|
|
81
85
|
}
|
|
82
86
|
const pending = this.pending.get(reqId);
|
|
83
87
|
if (!pending) {
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# C64 Bridge MCP Server Audit
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
|
|
5
|
+
This audit reviews the C64 Bridge repository as an MCP server for C64 Ultimate/C64U REST devices and VICE emulator control. It covers server initialization, tool/resource/prompt registration, tool schemas and results, device-control safety, VICE integration, C64 Ultimate integration, documentation consistency, and test/coverage architecture.
|
|
6
|
+
|
|
7
|
+
## Assumptions
|
|
8
|
+
|
|
9
|
+
- The local repository is the source of truth for supported behavior.
|
|
10
|
+
- The current branch is `fix/readme`.
|
|
11
|
+
- Pre-existing modified `data/**` files are user-owned unless a finding specifically requires editing them.
|
|
12
|
+
- Real hardware is not required for fixes where mock or emulator tests cover the behavior.
|
|
13
|
+
- Compatibility aliases for removed legacy resource URIs are not required; tests already assert legacy URI rejection.
|
|
14
|
+
|
|
15
|
+
## Methodology
|
|
16
|
+
|
|
17
|
+
1. Inspected repository layout, package scripts, README, AGENTS instructions, skill routing, and MCP generated artifacts.
|
|
18
|
+
2. Inspected `src/mcp-server.ts`, tool registries, schema utilities, knowledge resource registry, VICE backend, C64U backend, and client device-control helpers.
|
|
19
|
+
3. Ran baseline install/build/test/coverage commands.
|
|
20
|
+
4. Scanned for top-level tool schema combinators, stale resource URIs, broad validation gaps, and hidden-result patterns.
|
|
21
|
+
5. Classified findings with the requested severity model and mapped fixes to tests.
|
|
22
|
+
|
|
23
|
+
## Repository Areas Inspected
|
|
24
|
+
|
|
25
|
+
- `src/mcp-server.ts`
|
|
26
|
+
- `src/tools/**`
|
|
27
|
+
- `src/rag/resourceUris.ts`
|
|
28
|
+
- `src/rag/knowledgeIndex.ts`
|
|
29
|
+
- `src/c64Client.ts`
|
|
30
|
+
- `src/device.ts`
|
|
31
|
+
- `src/vice/**`
|
|
32
|
+
- `src/prompts/registry.ts`
|
|
33
|
+
- `scripts/run-coverage.mjs`
|
|
34
|
+
- `scripts/run-tests.ts`
|
|
35
|
+
- `README.md`
|
|
36
|
+
- `AGENTS.md`
|
|
37
|
+
- `.github/skills/**`
|
|
38
|
+
- `mcp/**`
|
|
39
|
+
- `test/**`
|
|
40
|
+
|
|
41
|
+
## MCP Server Design Review
|
|
42
|
+
|
|
43
|
+
The server uses the official TypeScript MCP SDK and registers `resources/list`, `resources/read`, `tools/list`, `tools/call`, `prompts/list`, and `prompts/get` handlers over stdio. Diagnostics are written to stderr/files, leaving stdout for the MCP protocol. Tool errors are returned as tool results with `isError`, which matches MCP expectations for model-visible tool failures.
|
|
44
|
+
|
|
45
|
+
Confirmed non-issue: optional HTTP CLI parsing exists but current `main()` always connects stdio; the README describes stdio as canonical. No fix is required for this audit because no HTTP server path is advertised as the normal agent transport.
|
|
46
|
+
|
|
47
|
+
## Tool Design Review
|
|
48
|
+
|
|
49
|
+
Most public tools are grouped under names such as `c64_program`, `c64_memory`, `c64_config`, and `c64_sound` with an `op` discriminator. The shared `discriminatedUnionSchema` intentionally flattens operations to avoid top-level `oneOf`, which is correct for common MCP/LLM client compatibility.
|
|
50
|
+
|
|
51
|
+
Confirmed issue: `c64_input` is an outlier and still exposes top-level `oneOf` plus `discriminator`. Its metadata also references `c64://specs/assembly` and `c64://specs/memory-map`, which are not registered resources.
|
|
52
|
+
|
|
53
|
+
## Resource URI Design Review
|
|
54
|
+
|
|
55
|
+
Canonical resources use the `c64://` scheme with concise namespaces such as `guide`, `basic`, `assembly`, `graphics`, `sound`, `memory`, `io`, `printer`, `vice`, and `platform`. The registered resource list is stable, discoverable, and documented in README-generated sections. Tests assert old legacy URI forms are rejected.
|
|
56
|
+
|
|
57
|
+
Confirmed non-issue: legacy resource aliases do not need to be preserved because repository tests already reject legacy direct reads.
|
|
58
|
+
|
|
59
|
+
Confirmed issue: tool metadata in `c64_input` points to stale `c64://specs/*` URIs outside the canonical resource set.
|
|
60
|
+
|
|
61
|
+
## Device-Control Safety Review
|
|
62
|
+
|
|
63
|
+
The tool layer generally bounds address lengths, file image parameters, stream names, joystick values, and grouped operations. Memory mutation paths support verification and C64U pause/resume where applicable.
|
|
64
|
+
|
|
65
|
+
Confirmed issue: lower-level client parsing accepts partial numeric literals through `parseInt`, so values such as `$04ZZ` can resolve to `$0004`. Hex byte parsing can also silently truncate through `Buffer.from(value, "hex")`, so malformed data such as `$AAZZ` can become `$AA`. Raw memory helpers do not consistently reject address ranges that cross `$FFFF` before calling a backend.
|
|
66
|
+
|
|
67
|
+
## VICE Integration Review
|
|
68
|
+
|
|
69
|
+
VICE support is separated through `ViceBackend`, `ViceClient`, readiness helpers, and Binary Monitor guidance resources. Tests cover monitor framing, single-client behavior, process launch, readiness, resource get/set, mock and device smoke paths. Joystick simulation is correctly marked VICE-only at operation-platform level.
|
|
70
|
+
|
|
71
|
+
Confirmed issue: `c64_input` schema compatibility affects VICE joystick discoverability in some clients.
|
|
72
|
+
|
|
73
|
+
## C64 Ultimate REST Integration Review
|
|
74
|
+
|
|
75
|
+
C64U REST base URLs are normalized through config and environment handling. Network passwords are sent as `X-Password`. HTTP clients use timeouts. Tests cover mock REST endpoints, password behavior, memory, runners, drives, config, streams, and files.
|
|
76
|
+
|
|
77
|
+
Possible issue requiring external confirmation: local option-list validation for arbitrary C64U configuration values is limited because firmware-provided option inventories are not fully represented in repository data. The current safe approach is to list/read config first and rely on firmware rejection for unknown values. This was not fixed without a reliable local option source.
|
|
78
|
+
|
|
79
|
+
## Error Handling Review
|
|
80
|
+
|
|
81
|
+
Tool errors are normalized through `ToolValidationError`, `ToolExecutionError`, `toolErrorResult`, and `unknownErrorResult`. Tool-level failures are model-visible rather than protocol-level except for unknown tool lookup through registry invocation.
|
|
82
|
+
|
|
83
|
+
Confirmed issue: memory parse failures for partial literals are not reliably raised today, reducing the value of this error-handling layer for device safety.
|
|
84
|
+
|
|
85
|
+
## Schema/Input Validation Review
|
|
86
|
+
|
|
87
|
+
Most grouped tools use flattened schemas with enum-constrained `op`. String, number, boolean, array, object, and optional schema helpers enforce runtime validation.
|
|
88
|
+
|
|
89
|
+
Confirmed issue: `c64_input` still has a top-level `oneOf`.
|
|
90
|
+
|
|
91
|
+
Confirmed issue: C64Client numeric and hex parsing needs stricter validation below the tool layer so public methods and internal raw paths fail safely.
|
|
92
|
+
|
|
93
|
+
## Documentation Review
|
|
94
|
+
|
|
95
|
+
README, AGENTS, skills, and generated `mcp/**` files are broadly consistent with current grouped tool names and canonical resource URIs. README resource lists are generated.
|
|
96
|
+
|
|
97
|
+
Confirmed issue: generated `mcp/tools.json` and schema output inherit the stale `c64_input` resources and top-level `oneOf` until source is fixed and generation reruns.
|
|
98
|
+
|
|
99
|
+
## Test Architecture and Coverage Review
|
|
100
|
+
|
|
101
|
+
The repository has broad unit, integration, MCP harness, VICE mock, and VICE device tests. `npm test` passes. Coverage is intended to be enforced by `scripts/run-coverage.mjs` and `.c8rc.json` with 90% thresholds.
|
|
102
|
+
|
|
103
|
+
Confirmed issue: baseline `npm run coverage` failed after passing shards because the wrapper attempted to copy a missing `coverage/lcov.info` for the VICE device shard.
|
|
104
|
+
|
|
105
|
+
## Findings
|
|
106
|
+
|
|
107
|
+
| ID | Severity | Area | Finding | Evidence | Impact | Recommended fix | Status |
|
|
108
|
+
| --- | --- | --- | --- | --- | --- | --- | --- |
|
|
109
|
+
| MCP-AUDIT-001 | High | Device-control safety | Numeric and hex parsing can accept partial malformed input, and raw memory access lacks consistent 64 KB range guards. | `src/c64Client.ts` uses `parseInt`; `hexStringToBuffer` and `src/tools/memory.ts` parse hex through `Buffer.from(..., "hex")`; raw reads/writes do not consistently validate range overflow. | Malformed addresses or bytes can target the wrong memory location or write truncated data before a device call fails. | Enforce full-literal parsing, strict hex validation, and range checks before memory reads/writes. Add regression tests. | Planned |
|
|
110
|
+
| MCP-AUDIT-002 | High | MCP schema/resource metadata | `c64_input` exposes top-level `oneOf`/`discriminator` and stale `c64://specs/*` resources. | `src/tools/input.ts`; generated `mcp/tools.json` and `mcp/schemas/c64_input.schema.json`. | Some MCP/LLM clients may fail to call the tool, and metadata points agents to resources that cannot be read. | Use flattened `discriminatedUnionSchema` and canonical resources. Regenerate MCP artifacts and tests. | Planned |
|
|
111
|
+
| MCP-AUDIT-003 | Medium | Coverage architecture | Coverage command fails with `ENOENT` copying `coverage/lcov.info` after VICE device shard. | Baseline `npm run coverage` failed in `scripts/run-coverage.mjs:181`. | Required coverage verification cannot complete reliably even when tests pass. | Make the wrapper robust to Bun temporary LCOV output and add script regression tests. | Planned |
|
|
112
|
+
| MCP-AUDIT-004 | Low | Dependency hygiene | `npm install` reports 1 moderate and 1 high vulnerability. | Baseline install output. | Potential dependency risk, but not enough local evidence to safely upgrade during this audit. | Defer to a dependency/security review with package upgrade testing. | Deferred |
|
|
113
|
+
| MCP-AUDIT-005 | Trivial | Formatting | Some inspected source has indentation inconsistencies. | `src/rag/knowledgeIndex.ts`, `src/tools/memory.ts`. | Cosmetic only. | Defer unless adjacent to required edits. | Deferred |
|
|
114
|
+
|
|
115
|
+
## Fix Plan Mapped To Findings
|
|
116
|
+
|
|
117
|
+
- MCP-AUDIT-001: update strict parsers and range guards in `src/c64Client.ts` and `src/tools/memory.ts`; update `test/c64ClientInvalidInputs.test.mjs` and `test/memory.test.mjs`.
|
|
118
|
+
- MCP-AUDIT-002: update `src/tools/input.ts`; update input/schema tests; regenerate `README.md` and `mcp/**` via `npm run build`.
|
|
119
|
+
- MCP-AUDIT-003: update `scripts/run-coverage.mjs`; update `test/scripts/run-coverage.test.mjs`.
|
|
120
|
+
|
|
121
|
+
## Confirmed Issues
|
|
122
|
+
|
|
123
|
+
- MCP-AUDIT-001
|
|
124
|
+
- MCP-AUDIT-002
|
|
125
|
+
- MCP-AUDIT-003
|
|
126
|
+
|
|
127
|
+
## Possible Issues Requiring External Confirmation
|
|
128
|
+
|
|
129
|
+
- Full local option-list validation for C64U firmware configuration values depends on firmware-provided category/item/value metadata not present in repository evidence.
|
|
130
|
+
|
|
131
|
+
## Non-Issues
|
|
132
|
+
|
|
133
|
+
- Legacy resource URI aliases are intentionally absent.
|
|
134
|
+
- Stdio-first transport is consistent with repository instructions.
|
|
135
|
+
- VICE Binary Monitor reference is present and routed through canonical resource and skills.
|
|
136
|
+
|
|
137
|
+
## Trivialities Intentionally Deferred
|
|
138
|
+
|
|
139
|
+
- Formatting-only indentation cleanup not adjacent to required edits.
|
|
140
|
+
|
|
141
|
+
## Final Status After Implementation
|
|
142
|
+
|
|
143
|
+
Implementation has not yet started. This section will be updated after fixes and final verification.
|