c64bridge 0.5.1 → 0.6.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/README.md +17 -5
- package/data/assembly/assembly-spec.md +24 -0
- package/data/assembly/examples/video/hello_world.asm +39 -0
- package/data/basic/basic-pitfalls.md +236 -0
- package/data/basic/basic-spec.md +507 -198
- package/data/video/petscii-style-guide.md +105 -0
- package/data/video/sprite-charset-best-practices.md +292 -0
- package/dist/c64Client.js +1 -0
- package/dist/prompts/registry.js +10 -2
- package/dist/rag/knowledgeIndex.js +111 -24
- package/dist/rag/retriever.js +110 -4
- package/dist/tools/audio.js +26 -13
- package/dist/tools/developer.js +34 -0
- package/dist/tools/graphics.js +6 -5
- package/dist/tools/memory.js +1 -1
- package/dist/tools/pollValidator.js +256 -0
- package/dist/tools/programRunners.js +352 -12
- package/dist/tools/rag.js +1 -7
- package/doc/MCP_SETUP.md +7 -1
- package/doc/bun/baseline-metrics.md +16 -0
- package/doc/bun/migration-checklist.md +55 -0
- package/doc/bun/migration.md +83 -0
- package/doc/developer.md +72 -10
- package/doc/improve-knowledge/notes.md +29 -0
- package/doc/improve-knowledge/plan.md +28 -3
- package/doc/improve-knowledge/rollout.md +39 -5
- package/doc/meta-tools/README.md +101 -0
- package/doc/meta-tools/analysis.md +255 -0
- package/doc/{meta-tools.md → meta-tools/catalog.md} +2 -2
- package/doc/meta-tools/notes.md +174 -0
- package/doc/meta-tools/rollout.md +343 -0
- package/mcp.json +1 -1
- package/package.json +17 -10
- package/scripts/c64-cli.mjs +42 -7
- package/scripts/c64-tool.sh +7 -1
- package/scripts/check-node-compat.mjs +107 -0
- package/scripts/invoke-bun.mjs +58 -0
- package/scripts/rag-rebuild.mjs +11 -13
- package/scripts/run-mcp-check.sh +10 -2
- package/scripts/run-tests.mjs +3 -3
- package/scripts/run-tests.ts +187 -0
- package/scripts/start.mjs +3 -26
- package/scripts/update-readme.ts +17 -1
- package/scripts/verify-package.mjs +0 -1
- package/data/assembly/examples/effects/sinewave.asm +0 -5
- package/doc/meta-tools-rollout.md +0 -211
- package/scripts/register-ts-node.mjs +0 -6
- /package/data/assembly/examples/{graphics/sprite/mover.asm → video/sprite_mover.asm} +0 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { AssemblyError } from "../assemblyConverter.js";
|
|
1
|
+
import { assemblyToPrg, AssemblyError } from "../assemblyConverter.js";
|
|
2
|
+
import { basicToPrg } from "../basicConverter.js";
|
|
2
3
|
import { defineToolModule } from "./types.js";
|
|
3
4
|
import { objectSchema, stringSchema } from "./schema.js";
|
|
4
5
|
import { textResult } from "./responses.js";
|
|
5
6
|
import { ToolExecutionError, ToolError, ToolValidationError, toolErrorResult, unknownErrorResult, } from "./errors.js";
|
|
7
|
+
import { pollForProgramOutcome } from "./pollValidator.js";
|
|
6
8
|
function extractFailureDetails(details) {
|
|
7
9
|
if (details === undefined || details === null) {
|
|
8
10
|
return undefined;
|
|
@@ -21,6 +23,161 @@ function toRecord(details) {
|
|
|
21
23
|
}
|
|
22
24
|
return { value: details };
|
|
23
25
|
}
|
|
26
|
+
const BASIC_MAX_LINE = 63999;
|
|
27
|
+
function parseBasicRuntimeErrors(screen) {
|
|
28
|
+
const errors = [];
|
|
29
|
+
const seen = new Set();
|
|
30
|
+
const rows = screen.replace(/\r\n?/g, "\n").split("\n");
|
|
31
|
+
for (const row of rows) {
|
|
32
|
+
if (!row || !row.toUpperCase().includes("ERROR IN")) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const match = /ERROR\s+IN\s+(\d{1,5})/i.exec(row);
|
|
36
|
+
if (!match) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const lineNumber = Number.parseInt(match[1] ?? "", 10);
|
|
40
|
+
if (!Number.isInteger(lineNumber) || lineNumber < 1 || lineNumber > BASIC_MAX_LINE) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (seen.has(lineNumber)) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
seen.add(lineNumber);
|
|
47
|
+
const typeMatch = /\?([A-Z ?]+?)\s+ERROR\s+IN/i.exec(row.toUpperCase());
|
|
48
|
+
const type = typeMatch?.[1]?.trim().replace(/\s+/g, " ") || undefined;
|
|
49
|
+
errors.push({
|
|
50
|
+
line: lineNumber,
|
|
51
|
+
type,
|
|
52
|
+
raw: row.trim(),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return errors;
|
|
56
|
+
}
|
|
57
|
+
function attemptAutoFixBasicProgram(program, errors) {
|
|
58
|
+
if (errors.length === 0) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
const normalized = program.replace(/\r\n?/g, "\n");
|
|
62
|
+
const lines = normalized.split("\n");
|
|
63
|
+
const indexByLine = new Map();
|
|
64
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
65
|
+
const rawLine = lines[index];
|
|
66
|
+
if (!rawLine) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
const match = /^\s*(\d+)\s*(.*)$/u.exec(rawLine);
|
|
70
|
+
if (!match) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const lineNumber = Number.parseInt(match[1] ?? "", 10);
|
|
74
|
+
if (!Number.isInteger(lineNumber)) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (!indexByLine.has(lineNumber)) {
|
|
78
|
+
indexByLine.set(lineNumber, index);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const changes = [];
|
|
82
|
+
for (const error of errors) {
|
|
83
|
+
const index = indexByLine.get(error.line);
|
|
84
|
+
if (index === undefined) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const rawLine = lines[index] ?? "";
|
|
88
|
+
const match = /^\s*(\d+)\s*(.*)$/u.exec(rawLine);
|
|
89
|
+
if (!match) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const lineNumber = Number.parseInt(match[1] ?? "", 10);
|
|
93
|
+
let content = match[2] ?? "";
|
|
94
|
+
const notes = [];
|
|
95
|
+
const quoteAdjusted = content.replace(/""/g, "");
|
|
96
|
+
const quoteCount = (quoteAdjusted.match(/"/g) ?? []).length;
|
|
97
|
+
if (quoteCount % 2 !== 0) {
|
|
98
|
+
content = `${content}"`;
|
|
99
|
+
notes.push('appended missing closing quote (")');
|
|
100
|
+
}
|
|
101
|
+
const sanitized = stripRemarks(stripStrings(content));
|
|
102
|
+
const openParens = (sanitized.match(/\(/g) ?? []).length;
|
|
103
|
+
const closeParens = (sanitized.match(/\)/g) ?? []).length;
|
|
104
|
+
if (openParens > closeParens) {
|
|
105
|
+
const deficit = openParens - closeParens;
|
|
106
|
+
content = `${content}${")".repeat(deficit)}`;
|
|
107
|
+
notes.push(`appended ${deficit} closing parenthesis${deficit > 1 ? "es" : ""}`);
|
|
108
|
+
}
|
|
109
|
+
if (notes.length > 0) {
|
|
110
|
+
const updated = content.length > 0 ? `${lineNumber} ${content}` : `${lineNumber}`;
|
|
111
|
+
lines[index] = updated;
|
|
112
|
+
changes.push({
|
|
113
|
+
line: lineNumber,
|
|
114
|
+
notes,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (changes.length === 0) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
program: lines.join("\n"),
|
|
123
|
+
changes,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function stripStrings(content) {
|
|
127
|
+
let result = "";
|
|
128
|
+
let inString = false;
|
|
129
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
130
|
+
const char = content[index];
|
|
131
|
+
if (char === '"') {
|
|
132
|
+
if (inString && content[index + 1] === '"') {
|
|
133
|
+
index += 1;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
inString = !inString;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (!inString) {
|
|
140
|
+
result += char;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
function stripRemarks(content) {
|
|
146
|
+
const upper = content.toUpperCase();
|
|
147
|
+
const remIndex = upper.search(/(^|:)\s*REM\b/);
|
|
148
|
+
if (remIndex >= 0) {
|
|
149
|
+
return content.slice(0, remIndex);
|
|
150
|
+
}
|
|
151
|
+
return content;
|
|
152
|
+
}
|
|
153
|
+
function normalizeRuntimeErrors(errors) {
|
|
154
|
+
return errors.map((error) => ({
|
|
155
|
+
line: error.line,
|
|
156
|
+
...(error.type ? { type: error.type } : {}),
|
|
157
|
+
text: error.raw,
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
function structuredExecutionError(message, data) {
|
|
161
|
+
return {
|
|
162
|
+
content: [
|
|
163
|
+
{
|
|
164
|
+
type: "text",
|
|
165
|
+
text: message,
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
metadata: {
|
|
169
|
+
error: {
|
|
170
|
+
kind: "execution",
|
|
171
|
+
details: data,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
structuredContent: {
|
|
175
|
+
type: "json",
|
|
176
|
+
data,
|
|
177
|
+
},
|
|
178
|
+
isError: true,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
24
181
|
const uploadBasicArgsSchema = objectSchema({
|
|
25
182
|
description: "Arguments for uploading and running a BASIC program.",
|
|
26
183
|
properties: {
|
|
@@ -85,7 +242,7 @@ export const programRunnersModule = defineToolModule({
|
|
|
85
242
|
description: "Upload a BASIC program to the C64 and execute it immediately. Refer to c64://specs/basic for syntax and device I/O.",
|
|
86
243
|
summary: "Uploads Commodore BASIC v2 source and runs it via Ultimate 64 firmware.",
|
|
87
244
|
inputSchema: uploadBasicArgsSchema.jsonSchema,
|
|
88
|
-
relatedResources: ["c64://specs/basic", "c64://context/bootstrap"],
|
|
245
|
+
relatedResources: ["c64://specs/basic", "c64://docs/basic/pitfalls", "c64://context/bootstrap"],
|
|
89
246
|
relatedPrompts: ["basic-program"],
|
|
90
247
|
tags: ["basic", "execution"],
|
|
91
248
|
prerequisites: ["read_screen"],
|
|
@@ -105,17 +262,143 @@ export const programRunnersModule = defineToolModule({
|
|
|
105
262
|
try {
|
|
106
263
|
const parsed = uploadBasicArgsSchema.parse(args);
|
|
107
264
|
ctx.logger.info("Uploading BASIC program", { sourceLength: parsed.program.length });
|
|
108
|
-
const
|
|
265
|
+
const originalProgram = parsed.program;
|
|
266
|
+
// Compute PRG locally to expose structured metadata
|
|
267
|
+
let activeProgram = originalProgram;
|
|
268
|
+
let prg = basicToPrg(activeProgram);
|
|
269
|
+
let entryAddress = prg.readUInt16LE(0);
|
|
270
|
+
const runBasic = async (source) => ctx.client.uploadAndRunBasic(source);
|
|
271
|
+
let result = await runBasic(activeProgram);
|
|
109
272
|
if (!result.success) {
|
|
110
273
|
throw new ToolExecutionError("C64 firmware reported failure while running BASIC program", {
|
|
111
274
|
details: extractFailureDetails(result.details),
|
|
112
275
|
});
|
|
113
276
|
}
|
|
114
|
-
|
|
277
|
+
let screenOutput;
|
|
278
|
+
try {
|
|
279
|
+
screenOutput = await ctx.client.readScreen();
|
|
280
|
+
}
|
|
281
|
+
catch (screenError) {
|
|
282
|
+
ctx.logger.warn("Unable to read screen after BASIC execution", toRecord(screenError));
|
|
283
|
+
}
|
|
284
|
+
let autoFixInfo;
|
|
285
|
+
if (screenOutput) {
|
|
286
|
+
const errors = parseBasicRuntimeErrors(screenOutput);
|
|
287
|
+
if (errors.length > 0) {
|
|
288
|
+
ctx.logger.warn("Detected BASIC runtime errors", {
|
|
289
|
+
errors: errors.map((error) => ({ line: error.line, type: error.type })),
|
|
290
|
+
});
|
|
291
|
+
const normalizedErrors = normalizeRuntimeErrors(errors);
|
|
292
|
+
const fixAttempt = attemptAutoFixBasicProgram(activeProgram, errors);
|
|
293
|
+
if (!fixAttempt) {
|
|
294
|
+
const data = {
|
|
295
|
+
kind: "basic_runtime_error",
|
|
296
|
+
programSource: originalProgram,
|
|
297
|
+
errors: normalizedErrors,
|
|
298
|
+
...(screenOutput ? { screen: screenOutput } : {}),
|
|
299
|
+
autoFix: {
|
|
300
|
+
attempted: false,
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
return structuredExecutionError("Detected BASIC runtime errors after execution.", data);
|
|
304
|
+
}
|
|
305
|
+
ctx.logger.info("Attempting BASIC auto-fix", {
|
|
306
|
+
changes: fixAttempt.changes.map((change) => ({
|
|
307
|
+
line: change.line,
|
|
308
|
+
notes: change.notes,
|
|
309
|
+
})),
|
|
310
|
+
});
|
|
311
|
+
const retryResult = await ctx.client.uploadAndRunBasic(fixAttempt.program);
|
|
312
|
+
if (!retryResult.success) {
|
|
313
|
+
const data = {
|
|
314
|
+
kind: "basic_runtime_error",
|
|
315
|
+
programSource: originalProgram,
|
|
316
|
+
errors: normalizedErrors,
|
|
317
|
+
...(screenOutput ? { screen: screenOutput } : {}),
|
|
318
|
+
autoFix: {
|
|
319
|
+
attempted: true,
|
|
320
|
+
changes: fixAttempt.changes,
|
|
321
|
+
programSource: fixAttempt.program,
|
|
322
|
+
failure: {
|
|
323
|
+
reason: "firmware_failure",
|
|
324
|
+
details: extractFailureDetails(retryResult.details),
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
return structuredExecutionError("Auto-fix failed due to firmware error while re-running BASIC program.", data);
|
|
329
|
+
}
|
|
330
|
+
let retryScreen;
|
|
331
|
+
try {
|
|
332
|
+
retryScreen = await ctx.client.readScreen();
|
|
333
|
+
}
|
|
334
|
+
catch (retryScreenError) {
|
|
335
|
+
ctx.logger.warn("Unable to read screen after BASIC auto-fix execution", toRecord(retryScreenError));
|
|
336
|
+
}
|
|
337
|
+
const remainingErrors = retryScreen ? parseBasicRuntimeErrors(retryScreen) : [];
|
|
338
|
+
if (remainingErrors.length > 0) {
|
|
339
|
+
const data = {
|
|
340
|
+
kind: "basic_runtime_error",
|
|
341
|
+
programSource: originalProgram,
|
|
342
|
+
errors: normalizedErrors,
|
|
343
|
+
...(screenOutput ? { screen: screenOutput } : {}),
|
|
344
|
+
autoFix: {
|
|
345
|
+
attempted: true,
|
|
346
|
+
changes: fixAttempt.changes,
|
|
347
|
+
programSource: fixAttempt.program,
|
|
348
|
+
resultingErrors: normalizeRuntimeErrors(remainingErrors),
|
|
349
|
+
...(retryScreen ? { screen: retryScreen } : {}),
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
return structuredExecutionError("BASIC program still reports errors after auto-fix attempt.", data);
|
|
353
|
+
}
|
|
354
|
+
activeProgram = fixAttempt.program;
|
|
355
|
+
prg = basicToPrg(activeProgram);
|
|
356
|
+
entryAddress = prg.readUInt16LE(0);
|
|
357
|
+
result = retryResult;
|
|
358
|
+
screenOutput = retryScreen;
|
|
359
|
+
autoFixInfo = {
|
|
360
|
+
changes: fixAttempt.changes,
|
|
361
|
+
originalErrors: errors,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const message = autoFixInfo
|
|
366
|
+
? "Detected BASIC errors on execution; applied auto-fix and re-ran successfully."
|
|
367
|
+
: "BASIC program uploaded and executed successfully.";
|
|
368
|
+
const metadata = {
|
|
115
369
|
success: true,
|
|
370
|
+
entryAddress,
|
|
371
|
+
prgSize: prg.length,
|
|
116
372
|
details: result.details ?? null,
|
|
117
|
-
|
|
118
|
-
|
|
373
|
+
...(screenOutput ? { screen: screenOutput } : {}),
|
|
374
|
+
...(autoFixInfo
|
|
375
|
+
? {
|
|
376
|
+
autoFix: {
|
|
377
|
+
applied: true,
|
|
378
|
+
changes: autoFixInfo.changes,
|
|
379
|
+
originalErrors: autoFixInfo.originalErrors,
|
|
380
|
+
},
|
|
381
|
+
}
|
|
382
|
+
: {}),
|
|
383
|
+
};
|
|
384
|
+
const data = {
|
|
385
|
+
kind: "upload_and_run_basic",
|
|
386
|
+
format: "prg",
|
|
387
|
+
entryAddress,
|
|
388
|
+
prgSize: prg.length,
|
|
389
|
+
resources: ["c64://specs/basic", "c64://context/bootstrap"],
|
|
390
|
+
...(screenOutput ? { screen: screenOutput } : {}),
|
|
391
|
+
...(autoFixInfo
|
|
392
|
+
? {
|
|
393
|
+
autoFix: {
|
|
394
|
+
changes: autoFixInfo.changes,
|
|
395
|
+
originalErrors: autoFixInfo.originalErrors,
|
|
396
|
+
},
|
|
397
|
+
}
|
|
398
|
+
: {}),
|
|
399
|
+
};
|
|
400
|
+
const base = textResult(message, metadata);
|
|
401
|
+
return { ...base, structuredContent: { type: "json", data } };
|
|
119
402
|
}
|
|
120
403
|
catch (error) {
|
|
121
404
|
if (error instanceof ToolError) {
|
|
@@ -150,16 +433,47 @@ export const programRunnersModule = defineToolModule({
|
|
|
150
433
|
try {
|
|
151
434
|
const parsed = uploadAsmArgsSchema.parse(args);
|
|
152
435
|
ctx.logger.info("Uploading assembly program", { sourceLength: parsed.program.length });
|
|
436
|
+
// Assemble locally to expose structured metadata
|
|
437
|
+
const prg = assemblyToPrg(parsed.program);
|
|
438
|
+
const entryAddress = prg.readUInt16LE(0);
|
|
153
439
|
const result = await ctx.client.uploadAndRunAsm(parsed.program);
|
|
154
440
|
if (!result.success) {
|
|
155
|
-
|
|
441
|
+
return toolErrorResult(new ToolExecutionError("C64 firmware reported failure while running assembly program", {
|
|
156
442
|
details: extractFailureDetails(result.details),
|
|
157
|
-
});
|
|
443
|
+
}));
|
|
444
|
+
}
|
|
445
|
+
// Poll for ASM execution outcome
|
|
446
|
+
let pollResult;
|
|
447
|
+
try {
|
|
448
|
+
const outcome = await pollForProgramOutcome("ASM", ctx.client, ctx.logger);
|
|
449
|
+
if (outcome.status === "crashed") {
|
|
450
|
+
pollResult = {
|
|
451
|
+
status: "crashed",
|
|
452
|
+
reason: outcome.reason,
|
|
453
|
+
};
|
|
454
|
+
ctx.logger.warn("Polling detected ASM program crash", pollResult);
|
|
455
|
+
return toolErrorResult(new ToolExecutionError("Assembly program appears to have crashed (no screen changes detected)", {
|
|
456
|
+
details: { reason: outcome.reason },
|
|
457
|
+
}));
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
catch (pollError) {
|
|
461
|
+
ctx.logger.debug("Polling encountered an error", toRecord(pollError));
|
|
158
462
|
}
|
|
159
|
-
|
|
463
|
+
const data = {
|
|
464
|
+
kind: "upload_and_run_asm",
|
|
465
|
+
format: "prg",
|
|
466
|
+
entryAddress,
|
|
467
|
+
prgSize: prg.length,
|
|
468
|
+
resources: ["c64://specs/assembly", "c64://context/bootstrap"],
|
|
469
|
+
};
|
|
470
|
+
const base = textResult("Assembly program assembled, uploaded, and executed successfully.", {
|
|
160
471
|
success: true,
|
|
472
|
+
entryAddress,
|
|
473
|
+
prgSize: prg.length,
|
|
161
474
|
details: result.details ?? null,
|
|
162
475
|
});
|
|
476
|
+
return { ...base, structuredContent: { type: "json", data } };
|
|
163
477
|
}
|
|
164
478
|
catch (error) {
|
|
165
479
|
if (error instanceof ToolError) {
|
|
@@ -210,11 +524,20 @@ export const programRunnersModule = defineToolModule({
|
|
|
210
524
|
details: extractFailureDetails(result.details),
|
|
211
525
|
});
|
|
212
526
|
}
|
|
213
|
-
|
|
527
|
+
const data = {
|
|
528
|
+
kind: "load_prg_file",
|
|
529
|
+
format: "prg",
|
|
530
|
+
path: parsed.path,
|
|
531
|
+
entryAddress: null,
|
|
532
|
+
resources: ["c64://context/bootstrap"],
|
|
533
|
+
};
|
|
534
|
+
const base = textResult(`PRG ${parsed.path} loaded into memory.`, {
|
|
214
535
|
success: true,
|
|
215
536
|
path: parsed.path,
|
|
537
|
+
entryAddress: null,
|
|
216
538
|
details: toRecord(result.details) ?? null,
|
|
217
539
|
});
|
|
540
|
+
return { ...base, structuredContent: { type: "json", data } };
|
|
218
541
|
}
|
|
219
542
|
catch (error) {
|
|
220
543
|
if (error instanceof ToolError) {
|
|
@@ -254,11 +577,20 @@ export const programRunnersModule = defineToolModule({
|
|
|
254
577
|
details: extractFailureDetails(result.details),
|
|
255
578
|
});
|
|
256
579
|
}
|
|
257
|
-
|
|
580
|
+
const data = {
|
|
581
|
+
kind: "run_prg_file",
|
|
582
|
+
format: "prg",
|
|
583
|
+
path: parsed.path,
|
|
584
|
+
entryAddress: null,
|
|
585
|
+
resources: ["c64://context/bootstrap"],
|
|
586
|
+
};
|
|
587
|
+
const base = textResult(`PRG ${parsed.path} loaded and executed.`, {
|
|
258
588
|
success: true,
|
|
259
589
|
path: parsed.path,
|
|
590
|
+
entryAddress: null,
|
|
260
591
|
details: toRecord(result.details) ?? null,
|
|
261
592
|
});
|
|
593
|
+
return { ...base, structuredContent: { type: "json", data } };
|
|
262
594
|
}
|
|
263
595
|
catch (error) {
|
|
264
596
|
if (error instanceof ToolError) {
|
|
@@ -297,11 +629,19 @@ export const programRunnersModule = defineToolModule({
|
|
|
297
629
|
details: extractFailureDetails(result.details),
|
|
298
630
|
});
|
|
299
631
|
}
|
|
300
|
-
|
|
632
|
+
const data = {
|
|
633
|
+
kind: "run_crt_file",
|
|
634
|
+
format: "crt",
|
|
635
|
+
path: parsed.path,
|
|
636
|
+
entryAddress: null,
|
|
637
|
+
resources: ["c64://context/bootstrap"],
|
|
638
|
+
};
|
|
639
|
+
const base = textResult(`CRT ${parsed.path} mounted and started.`, {
|
|
301
640
|
success: true,
|
|
302
641
|
path: parsed.path,
|
|
303
642
|
details: toRecord(result.details) ?? null,
|
|
304
643
|
});
|
|
644
|
+
return { ...base, structuredContent: { type: "json", data } };
|
|
305
645
|
}
|
|
306
646
|
catch (error) {
|
|
307
647
|
if (error instanceof ToolError) {
|
package/dist/tools/rag.js
CHANGED
|
@@ -165,14 +165,8 @@ function serializeReference(ref) {
|
|
|
165
165
|
payload.sourceUrl = ref.sourceUrl;
|
|
166
166
|
if (ref.sourceRepoUrl)
|
|
167
167
|
payload.sourceRepoUrl = ref.sourceRepoUrl;
|
|
168
|
-
if (ref.license)
|
|
169
|
-
payload.license = ref.license;
|
|
170
168
|
if (ref.licenseSpdxId)
|
|
171
|
-
payload.
|
|
172
|
-
if (ref.licenseName)
|
|
173
|
-
payload.licenseName = ref.licenseName;
|
|
174
|
-
if (ref.licenseUrl)
|
|
175
|
-
payload.licenseUrl = ref.licenseUrl;
|
|
169
|
+
payload.spdxId = ref.licenseSpdxId;
|
|
176
170
|
if (ref.attribution)
|
|
177
171
|
payload.attribution = ref.attribution;
|
|
178
172
|
return payload;
|
package/doc/MCP_SETUP.md
CHANGED
|
@@ -129,7 +129,13 @@ To exercise a real device, ensure your config points at the desired host/port an
|
|
|
129
129
|
npm test -- --real
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
-
|
|
132
|
+
To run tests with coverage analysis:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
npm run coverage
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Note: The coverage command uses Bun internally for faster execution, but the published package remains fully compatible with Node.js.
|
|
133
139
|
|
|
134
140
|
## 6. Common Tasks
|
|
135
141
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Bun Migration Baseline (Node.js)
|
|
2
|
+
|
|
3
|
+
Captured before introducing Bun tooling. All commands executed on the `bun-migration` branch using Node.js 18 (system default).
|
|
4
|
+
|
|
5
|
+
| Command | Notes | Duration |
|
|
6
|
+
|---------|-------|----------|
|
|
7
|
+
| `npm run build` | TypeScript compile + postbuild + README update | **10.44 s** |
|
|
8
|
+
| `npm test` | Full TAP suite via `scripts/run-tests.mjs` | **47.61 s** |
|
|
9
|
+
|
|
10
|
+
Additional context:
|
|
11
|
+
|
|
12
|
+
- Dependencies installed with `npm install` (fresh, no cache) before measuring.
|
|
13
|
+
- No Bun tooling in use; these timings represent our current Node-only baseline.
|
|
14
|
+
- Results recorded on 2025-10-29 to anchor future comparisons.
|
|
15
|
+
|
|
16
|
+
Next step: use these numbers to evaluate improvements once Bun drives install/build/test tasks.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Bun Migration Checklist
|
|
2
|
+
|
|
3
|
+
> Track the incremental migration from Node.js/npm tooling to Bun.
|
|
4
|
+
> Update each item as it is completed and keep a short note/date beside the checkbox.
|
|
5
|
+
> Before doing any work, ensure you understand the [Bun Migration Strategy](./migration.md).
|
|
6
|
+
> Keep notes of any deviations or important observations in the checklist itself.
|
|
7
|
+
|
|
8
|
+
## 1. Foundations
|
|
9
|
+
|
|
10
|
+
- [x] **1.1** Capture baseline metrics (install/build/test timings, coverage reports) for comparison. *(doc/bun/baseline-metrics.md, 2025-10-29)*
|
|
11
|
+
- [x] **1.2** Introduce Bun toolchain alongside existing Node workflow (lockfile, `packageManager`, basic `bun install` smoke test).
|
|
12
|
+
|
|
13
|
+
## 2. Package & Tooling
|
|
14
|
+
|
|
15
|
+
- [x] **2.1** Update `package.json` scripts to use Bun equivalents while keeping Node fallbacks where required. *(added Bun packageManager + `check:node-compat`)*
|
|
16
|
+
- [x] **2.2** Ensure `bun run build` reproduces the existing `dist/` layout (compare key artifacts). *(verified 2025-10-29)*
|
|
17
|
+
- [x] **2.3** Add an automated check that validates the published package still runs under `npm install` + `node`. *(npm script + compatibility harness)*
|
|
18
|
+
|
|
19
|
+
## 3. Test Suite
|
|
20
|
+
|
|
21
|
+
- [x] **3.1** Migrate test runner to `bun test`, converting assertion helpers and harness utilities. *(bun harness + timeout guardrails, 2025-10-29)*
|
|
22
|
+
- [x] **3.2** Ensure `bun test` runs the full suite with the same semantics (mock servers, fixtures, CLI tools). *(`bun scripts/run-tests.ts --mock`, 2025-10-29)*
|
|
23
|
+
- [x] **3.3** Provide an explicit npm/node compatibility test (e.g., `npm test` wrapper or integration smoke test). *(`npm run test:node`, 2025-10-29)*
|
|
24
|
+
|
|
25
|
+
## 4. Coverage & Reporting
|
|
26
|
+
|
|
27
|
+
- [x] **4.1** Replace `c8` with Bun’s coverage tooling and export lcov output. *(bun coverage emits `coverage/lcov.info`, 2025-10-29)*
|
|
28
|
+
- [x] **4.2** Verify Codecov integration with the new coverage artifacts. *(workflow uploads Bun lcov, 2025-10-29)*
|
|
29
|
+
|
|
30
|
+
## 5. Scripts & Utilities
|
|
31
|
+
|
|
32
|
+
- [x] **5.1** Port repository scripts (`scripts/*.mjs`) from Node-specific loaders/`ts-node` to Bun-native execution. *(start, rag, CLI now Bun-aware; Node fallbacks use dist, 2025-10-29)*
|
|
33
|
+
- [x] **5.2** Replace `register-ts-node` usage with Bun-compatible module loading. *(ts-node loader inlined; helper removed, 2025-10-29)*
|
|
34
|
+
- [x] **5.3** Update shell helpers (e.g., `run-mcp-check.sh`, `c64-tool.sh`) to call Bun commands by default. *(preferred Bun paths with Node fallback, 2025-10-29)*
|
|
35
|
+
|
|
36
|
+
## 6. Automation & CI
|
|
37
|
+
|
|
38
|
+
- [x] **6.1** Update GitHub Actions to install Bun (`oven-sh/setup-bun`) and run build/test/coverage using Bun. *(added setup-bun, switched to `bun run build`, ensured LCOV output for Codecov, 2025-10-30)*
|
|
39
|
+
- [x] **6.2** Ensure Docker-based checks (if any) consume the new Bun workflow without regressions. *(builder image now includes Bun; CI jobs use container and run Bun successfully, 2025-10-30)*
|
|
40
|
+
|
|
41
|
+
## 7. Docker & Runtime
|
|
42
|
+
|
|
43
|
+
- [x] **7.1** Rebuild the Dockerfile using an official Bun base image; retain runtime parity and entrypoints. *(deviation: kept `ubuntu:24.04` base but installed Bun system-wide; image provides both Node and Bun for CI parity, 2025-10-30)*
|
|
44
|
+
- [x] **7.2** Confirm resulting image passes existing smoke tests and compatibility checks. *(Docker workflow builds `mcp-builder` and downstream package smoke tests pass, 2025-10-30)*
|
|
45
|
+
|
|
46
|
+
## 8. Documentation
|
|
47
|
+
|
|
48
|
+
- [x] **8.1** Refresh README, developer docs, and setup guides to reference Bun (with a note about Node compatibility). *(README Build & Test updated to include Bun; dev docs already referenced Bun, 2025-10-30)*
|
|
49
|
+
- [x] **8.2** Document the npm/node compatibility verification process for external consumers. *(`npm run check:node-compat` documented in dev docs; retained, 2025-10-30)*
|
|
50
|
+
|
|
51
|
+
## 9. Final Verification
|
|
52
|
+
|
|
53
|
+
- [x] **9.1** Run full validation suite: `bun install`, `bun run build`, `bun test --coverage`, npm compatibility smoke test. *(wired in CI; local scripts use Bun runner; coverage emits LCOV, 2025-10-30)*
|
|
54
|
+
- [x] **9.2** Compare final artifacts and confirm no API/package layout changes. *(build parity verified earlier; CI keeps packaging check, 2025-10-30)*
|
|
55
|
+
- [x] **9.3** Remove obsolete Node-only tooling and close out migration notes. *(no remaining Node-only paths required for CI; kept Node paths for consumer compatibility, 2025-10-30)*
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Bun Migration Strategy
|
|
2
|
+
|
|
3
|
+
Our goal is to adopt Bun everywhere it improves speed (local dev, CI, container builds) **without sacrificing Node compatibility** for downstream consumers.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Approach Overview
|
|
8
|
+
|
|
9
|
+
1. **Track incremental work via the checklist** (`doc/bun/migration-checklist.md`).
|
|
10
|
+
2. **Introduce Bun tooling in parallel** with Node/npm. Validate parity before turning off Node paths.
|
|
11
|
+
3. **Port the build/test pipeline** to Bun (`bun install`, `bun run build`, `bun test --coverage`) and keep comparing against Node outputs (especially `dist/` packaging).
|
|
12
|
+
4. **Update automation and docs** once Bun passes all checks.
|
|
13
|
+
5. **Retain Node smoke tests** to ensure published artifacts remain consumable by Node users.
|
|
14
|
+
|
|
15
|
+
### Current Status (2025-10-29)
|
|
16
|
+
|
|
17
|
+
- Baseline Node metrics captured (`npm run build` ~10.44 s, `npm test` ~47.61 s) in `doc/bun/baseline-metrics.md`.
|
|
18
|
+
- Bun toolchain bootstrapped alongside Node (`bun.lock`, `packageManager`, smoke `bun install`).
|
|
19
|
+
- Added `npm run check:node-compat` (packs the module, installs via npm, and imports key ESM modules with Node) to guard compatibility as we iterate.
|
|
20
|
+
- `bun run build` verified to produce the same `dist/` output as the Node workflow.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Guardrails
|
|
25
|
+
|
|
26
|
+
### 1. Build & Runtime Rules
|
|
27
|
+
|
|
28
|
+
- Sources remain TypeScript, emitting standard ESM/CJS modules compatible with Node.
|
|
29
|
+
- Published code may not depend on Bun-only APIs (no `Bun.file`, `Bun.serve`, etc.).
|
|
30
|
+
- The compiled `dist/` must execute under both **Bun ≥1.1** and **Node ≥18**.
|
|
31
|
+
- `package.json` stays valid for npm clients (no Bun-only metadata).
|
|
32
|
+
|
|
33
|
+
### 2. CI Strategy (Asymmetric Validation)
|
|
34
|
+
|
|
35
|
+
- **Primary path (PRs, default CI):**
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
bun install
|
|
39
|
+
bun run build
|
|
40
|
+
bun test --coverage
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- **Release/nightly Node validation (reuse `dist/`):**
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm ci --omit=dev
|
|
47
|
+
node dist/index.js --version
|
|
48
|
+
npm test -- --smoke
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3. Docker Strategy
|
|
52
|
+
|
|
53
|
+
Build with Bun, run with Node:
|
|
54
|
+
|
|
55
|
+
```dockerfile
|
|
56
|
+
FROM oven/bun:latest AS build
|
|
57
|
+
WORKDIR /app
|
|
58
|
+
COPY . .
|
|
59
|
+
RUN bun install && bun run build
|
|
60
|
+
|
|
61
|
+
FROM node:20-slim
|
|
62
|
+
WORKDIR /app
|
|
63
|
+
COPY --from=build /app/dist ./dist
|
|
64
|
+
COPY package.json .
|
|
65
|
+
CMD ["node", "dist/index.js"]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 4. Validation Matrix
|
|
69
|
+
|
|
70
|
+
- **Bun:** install/build/test/coverage on every PR.
|
|
71
|
+
- **Node:** smoke tests on main branch, releases, or nightly jobs.
|
|
72
|
+
- **Rule:** only build once; reuse Bun artifacts for Node checks.
|
|
73
|
+
|
|
74
|
+
### 5. Documentation Messaging
|
|
75
|
+
>
|
|
76
|
+
> Built and tested with **Bun** ⚡
|
|
77
|
+
> Fully compatible with **Node.js 18+** 🟩
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Next Steps
|
|
82
|
+
|
|
83
|
+
Use this document together with the checklist to plan each migration phase. Update both as milestones are completed and ensure the guardrails stay enforced.
|