@henols/c64-re-tools 2.1.1 → 2.1.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.
Files changed (57) hide show
  1. package/package.json +1 -1
  2. package/skills/c64-assembler/SKILL.md +6 -3
  3. package/skills/c64-assembler/scripts/assembler.ts +19 -11
  4. package/skills/c64-assembler/scripts/lib/compare.ts +4 -3
  5. package/skills/c64-basic/SKILL.md +1 -0
  6. package/skills/c64-disk/SKILL.md +1 -0
  7. package/skills/c64-disk/scripts/disk.ts +2 -2
  8. package/skills/c64-disk/scripts/lib/containers.ts +3 -4
  9. package/skills/c64-docs/SKILL.md +2 -1
  10. package/skills/c64-docs/scripts/docs.ts +1 -1
  11. package/skills/c64-docs/scripts/lib/links.ts +2 -0
  12. package/skills/c64-emulator/SKILL.md +2 -1
  13. package/skills/c64-knowledge/SKILL.md +6 -4
  14. package/skills/c64-knowledge/scripts/knowledge.ts +35 -13
  15. package/skills/c64-knowledge/scripts/lib/listing.ts +8 -30
  16. package/skills/c64-memory-map/SKILL.md +1 -0
  17. package/skills/c64-memory-map/scripts/memory-map.ts +1 -1
  18. package/skills/c64-provenance/SKILL.md +1 -0
  19. package/skills/c64-provenance/scripts/provenance.ts +9 -6
  20. package/skills/c64-source/SKILL.md +1 -0
  21. package/skills/c64-source/scripts/lib/source.ts +7 -29
  22. package/skills/c64-source/scripts/source.ts +4 -4
  23. package/skills/c64-static-analysis/SKILL.md +2 -0
  24. package/skills/c64-static-analysis/scripts/static-analysis.ts +14 -5
  25. package/skills/c64-testing/SKILL.md +3 -2
  26. package/skills/c64-testing/scripts/lib/diff-files.ts +14 -2
  27. package/skills/c64-testing/scripts/lib/recording.ts +3 -3
  28. package/skills/c64-testing/scripts/lib/replay.ts +1 -1
  29. package/skills/c64-testing/scripts/lib/runs.ts +27 -7
  30. package/skills/c64-testing/scripts/testing.ts +5 -5
  31. package/skills/c64-unpacker/SKILL.md +8 -5
  32. package/skills/c64-unpacker/scripts/lib/loads.ts +12 -6
  33. package/skills/c64-unpacker/scripts/lib/machine.ts +30 -3
  34. package/skills/c64-unpacker/scripts/lib/stages.ts +15 -7
  35. package/skills/c64-unpacker/scripts/unpacker.ts +18 -17
  36. package/src/host/vice/adapter.ts +4 -1
  37. package/src/host/vice/session.ts +49 -3
  38. package/src/host-client/image.ts +10 -10
  39. package/src/host-client/steps.ts +39 -3
  40. package/src/host-client/transfer.ts +14 -26
  41. package/src/knowledge/history.ts +55 -2
  42. package/src/knowledge/import.ts +79 -7
  43. package/src/knowledge/program.ts +31 -2
  44. package/src/knowledge/read.ts +7 -4
  45. package/src/knowledge/schema.ts +3 -2
  46. package/src/knowledge/text.ts +1 -0
  47. package/src/knowledge/write.ts +22 -14
  48. package/src/mcp/inputs.ts +6 -3
  49. package/src/mcp/server.ts +2 -1
  50. package/src/mcp/tools/debug.ts +29 -3
  51. package/src/mcp/tools/machine.ts +3 -1
  52. package/src/mcp/tools/memory.ts +4 -3
  53. package/src/media.ts +42 -0
  54. package/src/opcodes.ts +51 -18
  55. package/src/project.ts +110 -2
  56. package/src/protocol/vice.ts +4 -1
  57. package/src/script.ts +55 -3
@@ -385,6 +385,8 @@ export interface MachineStatus {
385
385
  stopReason?: PointStopReason;
386
386
  /** The id of the breakpoint or watchpoint that stopped the machine. */
387
387
  pointId?: number;
388
+ /** With pc: the CPU whose address pc is, drive8 when a point in the drive stopped the machine. */
389
+ space?: "c64" | "drive8";
388
390
  }
389
391
 
390
392
  /** What stopped the machine outside a request that stops it. */
@@ -1092,7 +1094,8 @@ export function validateViceResult<O extends ViceOperation>(op: O, value: unknow
1092
1094
  (value.pc === undefined || isInteger(value.pc, 0, 0xffff)) &&
1093
1095
  typeof value.cartridge === "boolean" &&
1094
1096
  (value.stopReason === undefined || isOneOf(POINT_STOP_REASONS, value.stopReason)) &&
1095
- (value.pointId === undefined || isInteger(value.pointId, 1, Number.MAX_SAFE_INTEGER));
1097
+ (value.pointId === undefined || isInteger(value.pointId, 1, Number.MAX_SAFE_INTEGER)) &&
1098
+ (value.space === undefined || isOneOf(SPACES, value.space));
1096
1099
  if (!ok) throw new ProtocolError("status result is malformed");
1097
1100
  break;
1098
1101
  }
package/src/script.ts CHANGED
@@ -13,7 +13,7 @@ import { parseArgs } from "node:util";
13
13
 
14
14
  import { c64AddressOf, formatC64Address, SPACE_PATTERN, SYMBOL_NAME, type AddressRange } from "./c64.ts";
15
15
  import { isRefusal, Refusal, type ErrorCode } from "./errors.ts";
16
- import { projectRoot, resolveProjectPath, useProject } from "./project.ts";
16
+ import { checkOutputPath, projectRoot, resolveProjectPath, useProject, type OutputKind } from "./project.ts";
17
17
  import { shownMessage } from "./protocol/messages.ts";
18
18
  import { elapsedMs, startTrace, trace, type TraceFields } from "./trace.ts";
19
19
 
@@ -59,6 +59,12 @@ export interface OptionSpec {
59
59
  max?: number;
60
60
  /** The value when the option is not given. */
61
61
  default?: number | string;
62
+ /**
63
+ * path only: the command writes there, a file or files in a folder. The
64
+ * path is checked before the command runs (checkOutputPath). Every option
65
+ * --out and --out-<name> has it.
66
+ */
67
+ output?: OutputKind;
62
68
  }
63
69
 
64
70
  /** The options that have one meaning in every script. A command can change the help, the value and whether it is required, never the kind. */
@@ -157,7 +163,7 @@ export class Call {
157
163
 
158
164
  /** The knowledge space of the command: --space, else c64. */
159
165
  get space(): string {
160
- return "space" in this.#options ? this.string("space")! : "c64";
166
+ return ("space" in this.#options ? this.string("space") : undefined) ?? "c64";
161
167
  }
162
168
 
163
169
  /** Subject `index` as text. */
@@ -339,6 +345,8 @@ function commandOptions(command: CommandSpec): Record<string, OptionSpec> {
339
345
  const shared = (OPTIONS as Record<string, OptionSpec>)[name];
340
346
  if (shared !== undefined && shared.kind !== spec.kind) throw fault(`--${name} of ${command.name} is a ${spec.kind}, but OPTIONS has it as a ${shared.kind}`);
341
347
  if (name === "help" || name === "project") throw fault(`--${name} of ${command.name}: the contract owns this option`);
348
+ if (spec.kind === "path" && (name === "out" || name.startsWith("out-")) && spec.output === undefined) throw fault(`--${name} of ${command.name} has no output kind (file or folder)`);
349
+ if (spec.output !== undefined && spec.kind !== "path") throw fault(`--${name} of ${command.name} has an output kind, but it is a ${spec.kind}, not a path`);
342
350
  }
343
351
  return options;
344
352
  }
@@ -391,9 +399,49 @@ function checkPath(text: string, what: string): void {
391
399
  }
392
400
  }
393
401
 
402
+ /**
403
+ * Refuses a range in two parts: two subjects '$1bc2' '$1bd0' where the command
404
+ * takes one <range>, or --start and --end where it takes a range option. The
405
+ * message gives the range as one token.
406
+ */
407
+ function refuseSplitRange(command: CommandSpec, options: Readonly<Record<string, OptionSpec>>, argv: readonly string[], subjects?: readonly string[]): void {
408
+ if (subjects === undefined) {
409
+ const end = argv.indexOf("--");
410
+ const words = end < 0 ? argv : argv.slice(0, end);
411
+ const valueOf = (name: string) => {
412
+ const index = words.findIndex((word) => word === `--${name}` || word.startsWith(`--${name}=`));
413
+ if (index < 0) return undefined;
414
+ const word = words[index]!;
415
+ return word.includes("=") ? word.slice(word.indexOf("=") + 1) : (words[index + 1] ?? "");
416
+ };
417
+ const [start, last] = [valueOf("start"), valueOf("end")];
418
+ const range = Object.entries(options).find(([, spec]) => spec.kind === "range")?.[0];
419
+ if ((start === undefined && last === undefined) || range === undefined || "start" in options || "end" in options) return;
420
+ throw new UsageError(`${command.name} takes no --start or --end. Give the range as one option: --${range} '${start || "<start>"}-${last || "<end>"}'`);
421
+ }
422
+ const specs = command.subjects ?? [];
423
+ for (let index = 0; index + 1 < subjects.length; index++) {
424
+ const spec = specs[Math.min(index, specs.length - 1)];
425
+ const [first, second] = [subjects[index]!, subjects[index + 1]!];
426
+ if (spec === undefined || !spec.name.includes("range") || rangeParts(first) !== undefined || !isAddressText(first) || c64AddressOf(second) === undefined) continue;
427
+ throw new UsageError(`${spec.name} is one token <start>-<end>: give '${first}-${second}', not '${first}' '${second}'`);
428
+ }
429
+ }
430
+
431
+ /**
432
+ * Refuses an output path before the command does any work: a path that
433
+ * leaves the project, a path in the knowledge, a folder where a file must be,
434
+ * a file where a folder must be, or a folder that cannot be written.
435
+ */
436
+ function checkOutput(text: string, what: string, kind: OutputKind): void {
437
+ checkPath(text, what);
438
+ checkOutputPath(text, kind, { root: projectRoot(), what });
439
+ }
440
+
394
441
  /** Parses the command line of one command. Refuses a wrong count of subjects, an unknown option, a missing required option or a value of the wrong form. */
395
442
  export function parseCall(script: ScriptSpec, command: CommandSpec, argv: readonly string[]): Call {
396
443
  const options = commandOptions(command);
444
+ refuseSplitRange(command, options, argv);
397
445
  let parsed: ReturnType<typeof parseArgs>;
398
446
  try {
399
447
  parsed = parseArgs({
@@ -407,6 +455,7 @@ export function parseCall(script: ScriptSpec, command: CommandSpec, argv: readon
407
455
  }
408
456
  const values = parsed.values as Record<string, string | boolean | string[] | undefined>;
409
457
  const subjects = parsed.positionals;
458
+ refuseSplitRange(command, options, argv, subjects);
410
459
  const specs = command.subjects ?? [];
411
460
  const last = specs.at(-1);
412
461
  const least = specs.filter((subject) => !subject.optional).length;
@@ -428,7 +477,10 @@ export function parseCall(script: ScriptSpec, command: CommandSpec, argv: readon
428
477
  if (spec.kind === "range" && rangeParts(text) === undefined) throw new UsageError(`--${name} must be <start>-<end>, each ${ADDRESS_FORM}, for example '$c000-$cfff', not ${JSON.stringify(text)}`);
429
478
  if (spec.kind === "space" && !SPACE_PATTERN.test(text)) throw new UsageError(`--${name} must be c64, drive8, or bank0 to bank255, not ${JSON.stringify(text)}`);
430
479
  if (spec.kind === "choice" && !(spec.choices ?? []).includes(text)) throw new UsageError(`--${name} must be one of ${(spec.choices ?? []).join(", ")}, not ${JSON.stringify(text)}`);
431
- if (spec.kind === "path") checkPath(text, `--${name}`);
480
+ if (spec.kind === "path") {
481
+ if (spec.output === undefined) checkPath(text, `--${name}`);
482
+ else checkOutput(text, `--${name}`, spec.output);
483
+ }
432
484
  }
433
485
  }
434
486
  const call = new Call(script.name, command.name, subjects, options, values, script.names);