@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.
- package/package.json +1 -1
- package/skills/c64-assembler/SKILL.md +6 -3
- package/skills/c64-assembler/scripts/assembler.ts +19 -11
- package/skills/c64-assembler/scripts/lib/compare.ts +4 -3
- package/skills/c64-basic/SKILL.md +1 -0
- package/skills/c64-disk/SKILL.md +1 -0
- package/skills/c64-disk/scripts/disk.ts +2 -2
- package/skills/c64-disk/scripts/lib/containers.ts +3 -4
- package/skills/c64-docs/SKILL.md +2 -1
- package/skills/c64-docs/scripts/docs.ts +1 -1
- package/skills/c64-docs/scripts/lib/links.ts +2 -0
- package/skills/c64-emulator/SKILL.md +2 -1
- package/skills/c64-knowledge/SKILL.md +6 -4
- package/skills/c64-knowledge/scripts/knowledge.ts +35 -13
- package/skills/c64-knowledge/scripts/lib/listing.ts +8 -30
- package/skills/c64-memory-map/SKILL.md +1 -0
- package/skills/c64-memory-map/scripts/memory-map.ts +1 -1
- package/skills/c64-provenance/SKILL.md +1 -0
- package/skills/c64-provenance/scripts/provenance.ts +9 -6
- package/skills/c64-source/SKILL.md +1 -0
- package/skills/c64-source/scripts/lib/source.ts +7 -29
- package/skills/c64-source/scripts/source.ts +4 -4
- package/skills/c64-static-analysis/SKILL.md +2 -0
- package/skills/c64-static-analysis/scripts/static-analysis.ts +14 -5
- package/skills/c64-testing/SKILL.md +3 -2
- package/skills/c64-testing/scripts/lib/diff-files.ts +14 -2
- package/skills/c64-testing/scripts/lib/recording.ts +3 -3
- package/skills/c64-testing/scripts/lib/replay.ts +1 -1
- package/skills/c64-testing/scripts/lib/runs.ts +27 -7
- package/skills/c64-testing/scripts/testing.ts +5 -5
- package/skills/c64-unpacker/SKILL.md +8 -5
- package/skills/c64-unpacker/scripts/lib/loads.ts +12 -6
- package/skills/c64-unpacker/scripts/lib/machine.ts +30 -3
- package/skills/c64-unpacker/scripts/lib/stages.ts +15 -7
- package/skills/c64-unpacker/scripts/unpacker.ts +18 -17
- package/src/host/vice/adapter.ts +4 -1
- package/src/host/vice/session.ts +49 -3
- package/src/host-client/image.ts +10 -10
- package/src/host-client/steps.ts +39 -3
- package/src/host-client/transfer.ts +14 -26
- package/src/knowledge/history.ts +55 -2
- package/src/knowledge/import.ts +79 -7
- package/src/knowledge/program.ts +31 -2
- package/src/knowledge/read.ts +7 -4
- package/src/knowledge/schema.ts +3 -2
- package/src/knowledge/text.ts +1 -0
- package/src/knowledge/write.ts +22 -14
- package/src/mcp/inputs.ts +6 -3
- package/src/mcp/server.ts +2 -1
- package/src/mcp/tools/debug.ts +29 -3
- package/src/mcp/tools/machine.ts +3 -1
- package/src/mcp/tools/memory.ts +4 -3
- package/src/media.ts +42 -0
- package/src/opcodes.ts +51 -18
- package/src/project.ts +110 -2
- package/src/protocol/vice.ts +4 -1
- package/src/script.ts +55 -3
package/src/protocol/vice.ts
CHANGED
|
@@ -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")
|
|
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")
|
|
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);
|