c64bridge 0.8.1 → 0.9.1

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.
Files changed (92) hide show
  1. package/.github/prompts/steer.prompt.md +55 -0
  2. package/AGENTS.md +9 -7
  3. package/README.md +192 -154
  4. package/data/embeddings_asm.json +1 -1
  5. package/data/embeddings_basic.json +1 -1
  6. package/data/embeddings_hardware.json +1 -1
  7. package/data/embeddings_mixed.json +1 -1
  8. package/data/embeddings_other.json +1 -1
  9. package/data/graphics/petscii-style-guide.md +4 -4
  10. package/data/graphics/sprite-charset-best-practices.md +9 -9
  11. package/data/vice/vice-binary-monitor-spec.md +938 -0
  12. package/dist/.tsbuildinfo +1 -1
  13. package/dist/c64Client.js +76 -2
  14. package/dist/device.js +129 -13
  15. package/dist/mcp-server.js +1 -1
  16. package/dist/prompts/registry.js +27 -25
  17. package/dist/rag/indexer.js +18 -8
  18. package/dist/rag/knowledgeIndex.js +112 -79
  19. package/dist/rag/resourceUris.js +93 -0
  20. package/dist/sidwaveCompiler.js +1 -1
  21. package/dist/{assemblyConverter.js → tools/assember.js} +10 -1
  22. package/dist/tools/audio.js +11 -11
  23. package/dist/{basicConverter.js → tools/basicTokenizer.js} +1 -1
  24. package/dist/tools/debug.js +159 -6
  25. package/dist/tools/developer.js +13 -13
  26. package/dist/tools/disassembler.js +478 -0
  27. package/dist/tools/graphics.js +10 -10
  28. package/dist/tools/input.js +338 -0
  29. package/dist/tools/machineControl.js +5 -5
  30. package/dist/tools/memory.js +336 -12
  31. package/dist/tools/meta/index.js +1 -1
  32. package/dist/tools/printer.js +11 -12
  33. package/dist/tools/programRunners.js +61 -22
  34. package/dist/tools/rag.js +9 -9
  35. package/dist/tools/registry/batch.js +167 -0
  36. package/dist/tools/registry/config.js +1 -1
  37. package/dist/tools/registry/disk.js +1 -1
  38. package/dist/tools/registry/drive.js +1 -1
  39. package/dist/tools/registry/extract.js +1 -1
  40. package/dist/tools/registry/graphics.js +102 -2
  41. package/dist/tools/registry/index.js +4 -0
  42. package/dist/tools/registry/input.js +2 -0
  43. package/dist/tools/registry/memory.js +62 -2
  44. package/dist/tools/registry/platform.js +1 -1
  45. package/dist/tools/registry/printer.js +1 -1
  46. package/dist/tools/registry/program.js +1 -1
  47. package/dist/tools/registry/rag.js +1 -1
  48. package/dist/tools/registry/sound.js +1 -1
  49. package/dist/tools/registry/stream.js +1 -1
  50. package/dist/tools/registry/system.js +1 -1
  51. package/dist/tools/schema.js +4 -5
  52. package/dist/tools/storage.js +15 -15
  53. package/dist/tools/streaming.js +3 -3
  54. package/dist/tools/symbolRegistry.js +49 -0
  55. package/dist/tools/types.js +56 -4
  56. package/dist/tools/vice.js +2 -5
  57. package/dist/vice/process.js +120 -2
  58. package/dist/vice/readiness.js +35 -0
  59. package/dist/vice/smokeOptions.js +44 -0
  60. package/dist/vice/viceClient.js +5 -1
  61. package/dist/viceRunner.js +2 -0
  62. package/doc/plans/prompts/README.md +7 -7
  63. package/mcp/prompts.json +42 -59
  64. package/mcp/protocol-examples.json +2 -2
  65. package/mcp/resources.json +179 -166
  66. package/mcp/schemas/c64_batch.schema.json +53 -0
  67. package/mcp/schemas/c64_config.schema.json +525 -283
  68. package/mcp/schemas/c64_debug.schema.json +763 -304
  69. package/mcp/schemas/c64_disk.schema.json +499 -243
  70. package/mcp/schemas/c64_drive.schema.json +165 -90
  71. package/mcp/schemas/c64_extract.schema.json +533 -274
  72. package/mcp/schemas/c64_graphics.schema.json +607 -295
  73. package/mcp/schemas/c64_input.schema.json +136 -0
  74. package/mcp/schemas/c64_memory.schema.json +637 -128
  75. package/mcp/schemas/c64_printer.schema.json +393 -196
  76. package/mcp/schemas/c64_program.schema.json +699 -340
  77. package/mcp/schemas/c64_rag.schema.json +100 -54
  78. package/mcp/schemas/c64_select_backend.schema.json +49 -23
  79. package/mcp/schemas/c64_sound.schema.json +1092 -577
  80. package/mcp/schemas/c64_stream.schema.json +89 -47
  81. package/mcp/schemas/c64_system.schema.json +351 -185
  82. package/mcp/schemas/c64_vice.schema.json +85 -47
  83. package/mcp/server.json +1 -1
  84. package/mcp/tools.json +7299 -3346
  85. package/mcp.json +3 -3
  86. package/package.json +17 -14
  87. package/scripts/fetch-external-sources.mjs +1 -2
  88. package/scripts/run-coverage.mjs +33 -1
  89. package/scripts/run-tests.mjs +2 -2
  90. package/scripts/run-tests.ts +275 -52
  91. package/scripts/update-readme.ts +132 -3
  92. package/server.json +2 -2
package/dist/c64Client.js CHANGED
@@ -8,8 +8,8 @@ See <https://www.gnu.org/licenses/> for details.
8
8
  import { Buffer } from "node:buffer";
9
9
  import { createSocket } from "node:dgram";
10
10
  import axios from "axios";
11
- import { basicToPrg } from "./basicConverter.js";
12
- import { assemblyToPrg } from "./assemblyConverter.js";
11
+ import { basicToPrg } from "./tools/basicTokenizer.js";
12
+ import { assemblyToPrg } from "./tools/assember.js";
13
13
  import { screenCodesToAscii } from "./petscii.js";
14
14
  import { resolveAddressSymbol } from "./knowledge.js";
15
15
  import { createAllFacades, createFacade } from "./device.js";
@@ -1492,6 +1492,80 @@ export class C64Client {
1492
1492
  }
1493
1493
  return body instanceof ArrayBuffer ? new Uint8Array(body) : this.extractBytes(body);
1494
1494
  }
1495
+ /**
1496
+ * Low-level memory write that delegates to the active backend's facade.
1497
+ * Public so tools that need to drive cross-platform machine state (e.g.
1498
+ * keyboard-buffer injection) can avoid backend-specific paths.
1499
+ */
1500
+ async writeMemoryRaw(address, bytes) {
1501
+ const facade = await this.facadePromise;
1502
+ const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
1503
+ await facade.writeMemory(address, buf);
1504
+ }
1505
+ /**
1506
+ * Inject a sequence of PETSCII byte values into the KERNAL keyboard buffer
1507
+ * using shared memory writes. Works on both C64U and VICE because both
1508
+ * backends expose `writeMemory`/`readMemory` against the C64 zero-page and
1509
+ * the keyboard queue at $0277.
1510
+ *
1511
+ * The KERNAL buffer ($0277-$0280) holds 10 bytes; $00C6 (NDX) tracks the
1512
+ * number of pending characters. We chunk the input, then poll NDX until
1513
+ * the kernel has consumed each chunk before queueing the next one.
1514
+ *
1515
+ * @param bytes Raw PETSCII bytes (already token-expanded by caller)
1516
+ * @param options Optional knobs for chunk size, drain timing, and total
1517
+ * timeout. The defaults mirror KERNAL behavior.
1518
+ */
1519
+ async injectKeyboardQueue(bytes, options) {
1520
+ const data = Buffer.isBuffer(bytes)
1521
+ ? bytes
1522
+ : Buffer.from(bytes instanceof Uint8Array ? bytes : Uint8Array.from(bytes));
1523
+ if (data.length === 0) {
1524
+ return;
1525
+ }
1526
+ const chunkSize = Math.max(1, Math.min(10, options?.chunkSize ?? 10));
1527
+ const pollMs = Math.max(1, options?.drainPollMs ?? 8);
1528
+ const totalTimeoutMs = Math.max(50, options?.drainTimeoutMs ?? 2000);
1529
+ const facade = await this.facadePromise;
1530
+ const KEYD = 0x0277; // KERNAL keyboard buffer base
1531
+ const NDX = 0x00C6; // pending byte count
1532
+ for (let offset = 0; offset < data.length; offset += chunkSize) {
1533
+ const slice = data.subarray(offset, Math.min(data.length, offset + chunkSize));
1534
+ // Write the bytes into the queue first so KERNAL never reads a stale
1535
+ // length+old-byte combination.
1536
+ await facade.writeMemory(KEYD, Buffer.from(slice));
1537
+ await facade.writeMemory(NDX, Buffer.from([slice.length]));
1538
+ // Drain: wait for KERNAL to consume the queue. If the machine is paused
1539
+ // or otherwise not running, give up after the timeout and continue —
1540
+ // higher layers can decide whether that is a failure for their context.
1541
+ const drainStart = Date.now();
1542
+ while (Date.now() - drainStart < totalTimeoutMs) {
1543
+ const ndx = await facade.readMemory(NDX, 1);
1544
+ if ((ndx[0] ?? 0) === 0) {
1545
+ break;
1546
+ }
1547
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
1548
+ }
1549
+ }
1550
+ }
1551
+ async viceExitMonitor() {
1552
+ await this.withViceMonitor((client) => client.exitMonitor());
1553
+ }
1554
+ async viceKeyboardFeed(text) {
1555
+ await this.withViceMonitor((client) => client.keyboardFeed(text));
1556
+ }
1557
+ async viceMemSet(address, bytes) {
1558
+ const buf = Buffer.isBuffer(bytes) ? bytes : Buffer.from(bytes);
1559
+ await this.withViceMonitor((client) => client.memSet(address, buf));
1560
+ }
1561
+ async viceMemGet(address, length) {
1562
+ const end = (address + length - 1) & 0xffff;
1563
+ return this.withViceMonitor((client) => client.memGet(address, end));
1564
+ }
1565
+ async viceNuclearReset() {
1566
+ const backend = await this.requireViceBackend();
1567
+ await backend.nuclearReset();
1568
+ }
1495
1569
  async viceCheckpointList() {
1496
1570
  return this.withViceMonitor((client) => client.checkpointList());
1497
1571
  }
package/dist/device.js CHANGED
@@ -18,6 +18,18 @@ const DEFAULT_VICE_PORT = 6502;
18
18
  function delay(ms) {
19
19
  return new Promise((resolve) => setTimeout(resolve, ms));
20
20
  }
21
+ async function resumeViceMonitor(client, timeoutMs = 250) {
22
+ try {
23
+ const exitMonitor = client.exitMonitor().catch(() => { });
24
+ await Promise.race([
25
+ exitMonitor,
26
+ delay(timeoutMs),
27
+ ]);
28
+ }
29
+ catch {
30
+ // Ignore resume failures while tearing down a monitor session.
31
+ }
32
+ }
21
33
  function coalesceMemoryWriteBlocks(blocks) {
22
34
  const merged = [];
23
35
  for (const block of blocks) {
@@ -215,12 +227,18 @@ export class ViceBackend {
215
227
  const configBinary = config.exe !== undefined
216
228
  ? configuredString(config.exe) ?? (typeof config.exe === "string" ? config.exe : String(config.exe))
217
229
  : undefined;
218
- this.exe = resolveViceBinary({ envBinary, configBinary });
230
+ const resolvedLaunch = resolveViceLaunch({
231
+ envBinary,
232
+ configBinary,
233
+ envDirectory: configuredString(process.env.VICE_DIRECTORY),
234
+ configDirectory: configuredString(config.directory),
235
+ });
236
+ this.exe = resolvedLaunch.binary;
219
237
  const envHost = normaliseViceHost(process.env.VICE_HOST);
220
238
  const envPort = normaliseVicePort(process.env.VICE_PORT);
221
239
  this.host = firstDefined(envHost, normaliseViceHost(config.host)) ?? DEFAULT_VICE_HOST;
222
240
  this.port = firstDefined(envPort, normaliseVicePort(config.port)) ?? DEFAULT_VICE_PORT;
223
- this.directory = firstDefined(resolveViceDirectory(configuredString(process.env.VICE_DIRECTORY)), resolveViceDirectory(configuredString(config.directory)), findViceResourceDirectory(this.exe));
241
+ this.directory = resolvedLaunch.directory;
224
242
  this.mockMode = (process.env.VICE_TEST_TARGET || "").toLowerCase() === "mock";
225
243
  const hostLower = this.host.toLowerCase();
226
244
  const isLocal = hostLower === "127.0.0.1" || hostLower === "localhost";
@@ -238,6 +256,10 @@ export class ViceBackend {
238
256
  console.error("[vice-backend] probing existing VICE", this.host, this.port);
239
257
  await client.connect(this.port, this.host);
240
258
  await client.info();
259
+ try {
260
+ await client.exitMonitor();
261
+ }
262
+ catch { }
241
263
  if (this.debugEnabled)
242
264
  console.error("[vice-backend] existing VICE is reachable");
243
265
  return true;
@@ -257,6 +279,10 @@ export class ViceBackend {
257
279
  try {
258
280
  await client.connect(this.port, this.host);
259
281
  await client.info();
282
+ try {
283
+ await client.exitMonitor();
284
+ }
285
+ catch { }
260
286
  return;
261
287
  }
262
288
  catch (error) {
@@ -275,6 +301,7 @@ export class ViceBackend {
275
301
  const client = new ViceClient();
276
302
  try {
277
303
  await client.connect(this.port, this.host);
304
+ await client.reset();
278
305
  const readiness = await waitForBasicReady(client, { timeoutMs, ensurePrompt: true });
279
306
  if (!readiness.pointersOk || !readiness.promptOk) {
280
307
  throw new Error(`VICE machine at ${this.host}:${this.port} did not reach the BASIC prompt within ${timeoutMs}ms`);
@@ -312,15 +339,15 @@ export class ViceBackend {
312
339
  extraArgs: this.extraArgs,
313
340
  });
314
341
  }
315
- const handle = await startViceProcess({
342
+ const handle = await startViceProcess(buildManagedViceProcessOptions({
316
343
  binary: this.exe,
317
344
  directory: this.directory,
318
345
  host: this.host,
319
346
  port: this.port,
320
347
  warp: this.warp,
321
348
  visible: this.visible,
322
- extraArgs: this.extraArgs.length > 0 ? this.extraArgs : undefined,
323
- });
349
+ extraArgs: this.extraArgs,
350
+ }));
324
351
  this.lastProcessStart = Date.now();
325
352
  if (this.debugEnabled) {
326
353
  console.error("[vice-backend] VICE process started", { pid: handle.process.pid });
@@ -351,6 +378,7 @@ export class ViceBackend {
351
378
  return;
352
379
  }
353
380
  ViceBackend.cleanupRegistered = true;
381
+ /* c8 ignore next 5 -- global process exit hooks are not practical to exercise in the unit test process */
354
382
  process.once("exit", () => {
355
383
  for (const [, handle] of ViceBackend.supervisors) {
356
384
  handle.stop().catch(() => { });
@@ -358,7 +386,7 @@ export class ViceBackend {
358
386
  ViceBackend.supervisors.clear();
359
387
  });
360
388
  }
361
- async withClient(fn) {
389
+ async withClient(fn, options) {
362
390
  const previous = this.monitorQueue;
363
391
  let releaseQueue = () => { };
364
392
  this.monitorQueue = new Promise((resolve) => {
@@ -385,6 +413,9 @@ export class ViceBackend {
385
413
  return await fn(client);
386
414
  }
387
415
  finally {
416
+ if (options?.resumeOnClose !== false) {
417
+ await resumeViceMonitor(client);
418
+ }
388
419
  if (this.debugEnabled)
389
420
  console.error("[vice-backend] closing VICE monitor connection");
390
421
  client.close();
@@ -532,7 +563,7 @@ export class ViceBackend {
532
563
  catch {
533
564
  // Ignore transport errors during quit; the emulator will terminate regardless.
534
565
  }
535
- });
566
+ }, { resumeOnClose: false });
536
567
  if (managedHandle) {
537
568
  try {
538
569
  await managedHandle.stop();
@@ -549,6 +580,31 @@ export class ViceBackend {
549
580
  return { success: false, details };
550
581
  }
551
582
  }
583
+ async nuclearReset() {
584
+ const poweroffResult = await this.poweroff();
585
+ if (!poweroffResult.success) {
586
+ return poweroffResult;
587
+ }
588
+ if (!this.manageProcess) {
589
+ return {
590
+ success: false,
591
+ details: {
592
+ code: "UNSUPPORTED",
593
+ message: "VICE nuclear reset requires a managed process; unmanaged instances can only be powered off.",
594
+ },
595
+ };
596
+ }
597
+ try {
598
+ await this.ensureProcess();
599
+ return { success: true };
600
+ }
601
+ catch (error) {
602
+ const details = error instanceof Error
603
+ ? { message: error.message }
604
+ : error;
605
+ return { success: false, details };
606
+ }
607
+ }
552
608
  async menuButton() { throw unsupported("menuButton"); }
553
609
  async debugregRead() { throw unsupported("debugregRead"); }
554
610
  async debugregWrite(_v) { throw unsupported("debugregWrite"); }
@@ -775,9 +831,59 @@ function resolveViceBinary(options, findBinary = which) {
775
831
  }
776
832
  return findBinary("x64sc") ?? findBinary("x64") ?? "x64sc";
777
833
  }
834
+ function resolveViceLaunch(options, dependencies = {}) {
835
+ const findBinary = dependencies.findBinary ?? which;
836
+ const isResourceDirectory = dependencies.isResourceDirectory ?? isViceResourceDirectory;
837
+ const explicitBinary = firstDefined(options.envBinary, options.configBinary);
838
+ const explicitDirectory = firstDefined(resolveViceDirectoryWithValidator(options.envDirectory, isResourceDirectory), resolveViceDirectoryWithValidator(options.configDirectory, isResourceDirectory));
839
+ let binary = resolveViceBinary({ envBinary: options.envBinary, configBinary: options.configBinary }, findBinary);
840
+ if (explicitDirectory) {
841
+ return { binary, directory: explicitDirectory };
842
+ }
843
+ const adjacentDirectory = findViceResourceDirectory(binary, {
844
+ allowGlobalFallback: false,
845
+ isResourceDirectory,
846
+ });
847
+ if (adjacentDirectory) {
848
+ return { binary, directory: adjacentDirectory };
849
+ }
850
+ if (explicitBinary) {
851
+ return {
852
+ binary,
853
+ directory: findViceResourceDirectory(binary, {
854
+ allowGlobalFallback: true,
855
+ isResourceDirectory,
856
+ }),
857
+ };
858
+ }
859
+ return {
860
+ binary,
861
+ directory: findViceResourceDirectory(binary, {
862
+ allowGlobalFallback: true,
863
+ isResourceDirectory,
864
+ }),
865
+ };
866
+ }
867
+ function buildManagedViceProcessOptions(input) {
868
+ return {
869
+ binary: input.binary,
870
+ directory: input.directory,
871
+ host: input.host,
872
+ port: input.port,
873
+ warp: input.warp,
874
+ visible: input.visible,
875
+ extraArgs: input.extraArgs.length > 0 ? input.extraArgs : undefined,
876
+ };
877
+ }
778
878
  export function __resolveViceBinaryForTests(options, findBinary) {
779
879
  return resolveViceBinary(options, findBinary ?? which);
780
880
  }
881
+ export function __resolveViceLaunchForTests(options, dependencies) {
882
+ return resolveViceLaunch(options, dependencies);
883
+ }
884
+ export function __buildViceProcessOptionsForTests(input) {
885
+ return buildManagedViceProcessOptions(input);
886
+ }
781
887
  export async function createFacade(logger, options) {
782
888
  // Caller-forced preference: use c64u with provided base URL (used by tests and server wiring)
783
889
  if (options?.preferredC64uBaseUrl) {
@@ -935,7 +1041,15 @@ function resolveViceDirectory(value) {
935
1041
  }
936
1042
  return isViceResourceDirectory(input) ? input : undefined;
937
1043
  }
938
- function findViceResourceDirectory(binaryPath) {
1044
+ function resolveViceDirectoryWithValidator(value, isResourceDirectory) {
1045
+ const input = configuredString(value);
1046
+ if (!input) {
1047
+ return undefined;
1048
+ }
1049
+ return isResourceDirectory(input) ? input : undefined;
1050
+ }
1051
+ function findViceResourceDirectory(binaryPath, options = {}) {
1052
+ const isResourceDirectory = options.isResourceDirectory ?? isViceResourceDirectory;
939
1053
  const candidates = new Set();
940
1054
  const resolvedBinary = configuredString(binaryPath);
941
1055
  if (resolvedBinary) {
@@ -943,12 +1057,14 @@ function findViceResourceDirectory(binaryPath) {
943
1057
  candidates.add(path.resolve(binaryDir, "..", "share", "vice"));
944
1058
  candidates.add(path.resolve(binaryDir, "..", "..", "share", "vice"));
945
1059
  }
946
- candidates.add("/usr/local/share/vice");
947
- candidates.add("/usr/share/vice");
948
- candidates.add("/opt/homebrew/share/vice");
949
- candidates.add("/opt/local/share/vice");
1060
+ if (options.allowGlobalFallback !== false) {
1061
+ candidates.add("/usr/local/share/vice");
1062
+ candidates.add("/usr/share/vice");
1063
+ candidates.add("/opt/homebrew/share/vice");
1064
+ candidates.add("/opt/local/share/vice");
1065
+ }
950
1066
  for (const candidate of candidates) {
951
- if (isViceResourceDirectory(candidate)) {
1067
+ if (isResourceDirectory(candidate)) {
952
1068
  return candidate;
953
1069
  }
954
1070
  }
@@ -8,6 +8,7 @@ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSche
8
8
  import { loadConfig } from "./config.js";
9
9
  import { C64Client } from "./c64Client.js";
10
10
  import { listKnowledgeResources, readKnowledgeResource, } from "./rag/knowledgeIndex.js";
11
+ import { PLATFORM_RESOURCE_URI } from "./rag/resourceUris.js";
11
12
  import { createLazyRagRetriever, initRag } from "./rag/init.js";
12
13
  import { getMcpServerImplementationInfo } from "./mcp/metadata.js";
13
14
  import { toolRegistry } from "./tools/registry/index.js";
@@ -417,7 +418,6 @@ async function main() {
417
418
  diagnosticsFile: diagnostics.filePath,
418
419
  });
419
420
  }
420
- const PLATFORM_RESOURCE_URI = "c64://platform/status";
421
421
  function createPlatformResourceDescriptor() {
422
422
  return {
423
423
  uri: PLATFORM_RESOURCE_URI,
@@ -1,4 +1,5 @@
1
1
  import { listKnowledgeResources } from "../rag/knowledgeIndex.js";
2
+ import { RESOURCE_URIS } from "../rag/resourceUris.js";
2
3
  import { toolRegistry } from "../tools/registry/index.js";
3
4
  const ASSEMBLY_HARDWARE_NOTES = {
4
5
  sid: [
@@ -34,6 +35,7 @@ const ROUTING_CORE_SEGMENT = {
34
35
  content: [
35
36
  "Prompts in this repository define intent and routing only.",
36
37
  "Execution logic, validation steps, and safety rules live exclusively in `.github/skills/*/SKILL.md`.",
38
+ `When the request targets the \`vice\` backend, read \`${RESOURCE_URIS.vice.binaryMonitorSpec}\` before executing the skill so Binary Monitor single-client behaviour, trap/resume semantics, and monitor-side side effects stay in scope.`,
37
39
  "Use the referenced skill, extract missing inputs from the user request, execute the skill, and summarize the outcome.",
38
40
  ].join("\n"),
39
41
  };
@@ -115,7 +117,7 @@ export function createPromptRegistry() {
115
117
  name: "hello-world",
116
118
  title: "Hello World Workflow",
117
119
  description: "Route ultra-fast hello-world and smoke-test requests to the canonical greeting skill.",
118
- requiredResources: ["c64://context/bootstrap", "c64://context/fast-paths", "c64://docs/index"],
120
+ requiredResources: [RESOURCE_URIS.guide.bootstrap, RESOURCE_URIS.guide.fastPaths, RESOURCE_URIS.guide.index],
119
121
  optionalResources: [],
120
122
  tools: ["c64_program"],
121
123
  tags: ["hello", "demo", "greeting", "smoke-test"],
@@ -134,8 +136,8 @@ export function createPromptRegistry() {
134
136
  name: "basic-program",
135
137
  title: "BASIC Program Workflow",
136
138
  description: "Route bespoke Commodore BASIC v2 requests to the canonical BASIC skill.",
137
- requiredResources: ["c64://specs/basic", "c64://context/bootstrap", "c64://docs/index"],
138
- optionalResources: ["c64://context/fast-paths"],
139
+ requiredResources: [RESOURCE_URIS.basic.spec, RESOURCE_URIS.guide.bootstrap, RESOURCE_URIS.guide.index],
140
+ optionalResources: [RESOURCE_URIS.guide.fastPaths],
139
141
  tools: ["c64_program", "c64_memory"],
140
142
  tags: ["basic", "program"],
141
143
  },
@@ -150,7 +152,7 @@ export function createPromptRegistry() {
150
152
  name: "cross-platform-demo",
151
153
  title: "Cross-Platform Demo Workflow",
152
154
  description: "Route quick visible demo requests to the cross-platform demo skill.",
153
- requiredResources: ["c64://context/bootstrap", "c64://context/fast-paths", "c64://docs/index"],
155
+ requiredResources: [RESOURCE_URIS.guide.bootstrap, RESOURCE_URIS.guide.fastPaths, RESOURCE_URIS.guide.index],
154
156
  optionalResources: [],
155
157
  tools: ["c64_program"],
156
158
  tags: ["demo", "platform", "greeting"],
@@ -166,8 +168,8 @@ export function createPromptRegistry() {
166
168
  name: "preset-music-demo",
167
169
  title: "Preset Music Demo Workflow",
168
170
  description: "Route quick recognizable tune requests to the SID music skill.",
169
- requiredResources: ["c64://specs/sid", "c64://specs/sidwave", "c64://context/fast-paths"],
170
- optionalResources: ["c64://docs/sid/best-practices"],
171
+ requiredResources: [RESOURCE_URIS.sound.sid.spec, RESOURCE_URIS.sound.sidwave.spec, RESOURCE_URIS.guide.fastPaths],
172
+ optionalResources: [RESOURCE_URIS.sound.sid.bestPractices],
171
173
  tools: ["c64_sound"],
172
174
  tags: ["sid", "music", "demo"],
173
175
  },
@@ -181,8 +183,8 @@ export function createPromptRegistry() {
181
183
  name: "assembly-program",
182
184
  title: "Assembly Program Workflow",
183
185
  description: "Route 6502/6510 routine requests to the canonical assembly skill.",
184
- requiredResources: ["c64://specs/assembly", "c64://specs/vic", "c64://specs/sid", "c64://context/bootstrap"],
185
- optionalResources: ["c64://docs/sid/best-practices"],
186
+ requiredResources: [RESOURCE_URIS.assembly.spec6510, RESOURCE_URIS.graphics.vic.spec, RESOURCE_URIS.sound.sid.spec, RESOURCE_URIS.guide.bootstrap],
187
+ optionalResources: [RESOURCE_URIS.sound.sid.bestPractices],
186
188
  tools: ["c64_program", "c64_memory"],
187
189
  tags: ["assembly", "program"],
188
190
  },
@@ -201,7 +203,7 @@ export function createPromptRegistry() {
201
203
  selectOptionalResources: (args) => {
202
204
  const hardware = args.hardware;
203
205
  return hardware === "sid" || hardware === "multi"
204
- ? ["c64://docs/sid/best-practices"]
206
+ ? [RESOURCE_URIS.sound.sid.bestPractices]
205
207
  : [];
206
208
  },
207
209
  renderRoutingNotes: (args) => {
@@ -220,12 +222,12 @@ export function createPromptRegistry() {
220
222
  title: "SID Composition Workflow",
221
223
  description: "Route SID playback and composition work to the canonical SID skill.",
222
224
  requiredResources: [
223
- "c64://specs/sid",
224
- "c64://specs/sidwave",
225
- "c64://docs/sid/file-structure",
226
- "c64://docs/sid/best-practices",
225
+ RESOURCE_URIS.sound.sid.spec,
226
+ RESOURCE_URIS.sound.sidwave.spec,
227
+ RESOURCE_URIS.sound.sid.fileFormat,
228
+ RESOURCE_URIS.sound.sid.bestPractices,
227
229
  ],
228
- optionalResources: ["c64://context/fast-paths"],
230
+ optionalResources: [RESOURCE_URIS.guide.fastPaths],
229
231
  tools: ["c64_sound"],
230
232
  tags: ["sid", "music"],
231
233
  },
@@ -239,8 +241,8 @@ export function createPromptRegistry() {
239
241
  name: "graphics-demo",
240
242
  title: "Graphics Demo Workflow",
241
243
  description: "Route graphics requests to the canonical graphics skill.",
242
- requiredResources: ["c64://specs/vic", "c64://context/bootstrap"],
243
- optionalResources: ["c64://specs/assembly", "c64://specs/charset", "c64://docs/petscii-style"],
244
+ requiredResources: [RESOURCE_URIS.graphics.vic.spec, RESOURCE_URIS.guide.bootstrap],
245
+ optionalResources: [RESOURCE_URIS.assembly.spec6510, RESOURCE_URIS.graphics.characterSet, RESOURCE_URIS.graphics.petscii.styleGuide],
244
246
  tools: ["c64_program", "c64_memory", "c64_graphics"],
245
247
  tags: ["graphics", "vic"],
246
248
  },
@@ -271,12 +273,12 @@ export function createPromptRegistry() {
271
273
  name: "printer-job",
272
274
  title: "Printer Job Workflow",
273
275
  description: "Route printer work to the canonical printer skill.",
274
- requiredResources: ["c64://specs/printer", "c64://docs/printer/guide", "c64://docs/printer/prompts"],
276
+ requiredResources: [RESOURCE_URIS.printer.spec, RESOURCE_URIS.printer.promptGuide],
275
277
  optionalResources: [
276
- "c64://docs/printer/commodore-text",
277
- "c64://docs/printer/commodore-bitmap",
278
- "c64://docs/printer/epson-text",
279
- "c64://docs/printer/epson-bitmap",
278
+ RESOURCE_URIS.printer.commodore.text,
279
+ RESOURCE_URIS.printer.commodore.bitmap,
280
+ RESOURCE_URIS.printer.epson.text,
281
+ RESOURCE_URIS.printer.epson.bitmap,
280
282
  ],
281
283
  tools: ["c64_printer"],
282
284
  tags: ["printer"],
@@ -296,10 +298,10 @@ export function createPromptRegistry() {
296
298
  selectOptionalResources: (args) => {
297
299
  const printerType = args.printerType;
298
300
  if (printerType === "commodore") {
299
- return ["c64://docs/printer/commodore-text", "c64://docs/printer/commodore-bitmap"];
301
+ return [RESOURCE_URIS.printer.commodore.text, RESOURCE_URIS.printer.commodore.bitmap];
300
302
  }
301
303
  if (printerType === "epson") {
302
- return ["c64://docs/printer/epson-text", "c64://docs/printer/epson-bitmap"];
304
+ return [RESOURCE_URIS.printer.epson.text, RESOURCE_URIS.printer.epson.bitmap];
303
305
  }
304
306
  return [];
305
307
  },
@@ -315,7 +317,7 @@ export function createPromptRegistry() {
315
317
  name: "memory-debug",
316
318
  title: "Memory Debug Workflow",
317
319
  description: "Route reversible memory inspection or patching work to the canonical memory skill.",
318
- requiredResources: ["c64://context/bootstrap", "c64://specs/assembly", "c64://docs/index"],
320
+ requiredResources: [RESOURCE_URIS.guide.bootstrap, RESOURCE_URIS.assembly.spec6510, RESOURCE_URIS.guide.index],
319
321
  optionalResources: [],
320
322
  tools: ["c64_memory", "c64_system"],
321
323
  tags: ["memory", "debug"],
@@ -330,7 +332,7 @@ export function createPromptRegistry() {
330
332
  name: "drive-manager",
331
333
  title: "Drive Manager Workflow",
332
334
  description: "Route disk-image and drive-state requests to the canonical drive skill.",
333
- requiredResources: ["c64://context/bootstrap"],
335
+ requiredResources: [RESOURCE_URIS.guide.bootstrap],
334
336
  optionalResources: [],
335
337
  tools: ["c64_disk", "c64_drive"],
336
338
  tags: ["drive", "storage"],
@@ -474,20 +474,30 @@ export async function buildAllIndexes({ model, embeddingsDir: overrideDir, basic
474
474
  repoMetadataCache.clear();
475
475
  const embeddingsDir = resolveEmbeddingsDir(overrideDir);
476
476
  const paths = embeddingIndexPaths(embeddingsDir);
477
- // Collect all markdown files under doc/ recursively, plus any explicitly provided RAG_DOC_FILES
477
+ // Production indexing defaults to the repo docs tree. Tests and other callers can
478
+ // override that set explicitly, including passing an empty array to opt out.
478
479
  const docCandidates = [];
479
- try {
480
- const mdFiles = await collectFiles(DOC_ROOT, [".md"]);
481
- for (const f of mdFiles) {
480
+ if (docFiles !== undefined) {
481
+ for (const f of docFiles) {
482
482
  if (!shouldExcludeDocFile(f)) {
483
483
  docCandidates.push(f);
484
484
  }
485
485
  }
486
486
  }
487
- catch { }
488
- for (const f of ENV_DOC_FILES) {
489
- if (!shouldExcludeDocFile(f)) {
490
- docCandidates.push(f);
487
+ else {
488
+ try {
489
+ const mdFiles = await collectFiles(DOC_ROOT, [".md"]);
490
+ for (const f of mdFiles) {
491
+ if (!shouldExcludeDocFile(f)) {
492
+ docCandidates.push(f);
493
+ }
494
+ }
495
+ }
496
+ catch { }
497
+ for (const f of ENV_DOC_FILES) {
498
+ if (!shouldExcludeDocFile(f)) {
499
+ docCandidates.push(f);
500
+ }
491
501
  }
492
502
  }
493
503
  // De-duplicate