@henols/c64-re-tools 2.0.0-rc.9 → 2.0.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.
- package/README.md +97 -116
- package/package.json +1 -1
- package/skills/c64-assembler/SKILL.md +22 -6
- package/skills/c64-assembler/scripts/assemble.ts +42 -14
- package/skills/c64-assembler/scripts/lib/compare.ts +48 -0
- package/skills/c64-basic/SKILL.md +10 -1
- package/skills/c64-basic/scripts/basic.ts +3 -3
- package/skills/c64-disk/SKILL.md +13 -4
- package/skills/c64-disk/scripts/disk.ts +7 -7
- package/skills/c64-disk/scripts/{containers.ts → lib/containers.ts} +2 -2
- package/skills/c64-docs/SKILL.md +138 -0
- package/skills/c64-docs/references/page-types.md +141 -0
- package/skills/c64-docs/references/style.md +43 -0
- package/skills/c64-docs/scripts/generate.ts +109 -0
- package/skills/c64-docs/scripts/lib/links.ts +167 -0
- package/skills/c64-docs/scripts/lib/markdown.ts +79 -0
- package/skills/c64-docs/scripts/lib/reference.ts +406 -0
- package/skills/c64-docs/scripts/lib/source-map.ts +81 -0
- package/skills/c64-docs/scripts/link.ts +239 -0
- package/skills/c64-emulator/SKILL.md +16 -5
- package/skills/c64-knowledge/SKILL.md +140 -18
- package/skills/c64-knowledge/scripts/knowledge.ts +737 -112
- package/skills/c64-knowledge/scripts/lib/listing.ts +162 -0
- package/skills/c64-memory-map/SKILL.md +11 -2
- package/skills/c64-memory-map/scripts/memmap.ts +2 -2
- package/skills/c64-provenance/SKILL.md +66 -11
- package/skills/c64-provenance/scripts/{compare.ts → lib/compare.ts} +3 -3
- package/skills/c64-provenance/scripts/lib/library.ts +584 -0
- package/skills/c64-provenance/scripts/{runtime.ts → lib/runtime.ts} +2 -2
- package/skills/c64-provenance/scripts/{separate.ts → lib/separate.ts} +3 -3
- package/skills/c64-provenance/scripts/provenance.ts +242 -40
- package/skills/c64-reverse-engineering/SKILL.md +51 -26
- package/skills/c64-source/SKILL.md +76 -28
- package/skills/c64-source/scripts/generate.ts +425 -127
- package/skills/c64-source/scripts/{loader.ts → lib/loader.ts} +84 -25
- package/skills/c64-source/scripts/lib/source.ts +1121 -0
- package/skills/c64-static-analysis/SKILL.md +35 -18
- package/skills/c64-static-analysis/scripts/analyze.ts +184 -55
- package/skills/c64-static-analysis/scripts/{coverage.ts → lib/coverage.ts} +19 -25
- package/skills/c64-static-analysis/scripts/{findings.ts → lib/findings.ts} +6 -2
- package/skills/c64-static-analysis/scripts/{idioms.ts → lib/idioms.ts} +21 -12
- package/skills/c64-static-analysis/scripts/{instructions.ts → lib/instructions.ts} +1 -1
- package/skills/c64-static-analysis/scripts/lib/macros.ts +233 -0
- package/skills/c64-static-analysis/scripts/lib/pitfalls.ts +68 -0
- package/skills/c64-static-analysis/scripts/lib/pointers.ts +108 -0
- package/skills/c64-testing/SKILL.md +36 -7
- package/skills/c64-testing/references/scenario-format.md +12 -8
- package/skills/c64-testing/scripts/checklist.ts +3 -3
- package/skills/c64-testing/scripts/diff.ts +167 -0
- package/skills/c64-testing/scripts/lib/diff.ts +70 -0
- package/skills/c64-testing/scripts/{scenario.ts → lib/scenario.ts} +45 -34
- package/skills/c64-testing/scripts/run.ts +73 -21
- package/skills/c64-testing/scripts/test.ts +5 -5
- package/skills/c64-unpacker/SKILL.md +23 -9
- package/skills/c64-unpacker/scripts/{evidence.ts → lib/evidence.ts} +1 -1
- package/skills/c64-unpacker/scripts/{loads.ts → lib/loads.ts} +4 -4
- package/skills/c64-unpacker/scripts/{machine.ts → lib/machine.ts} +4 -4
- package/skills/c64-unpacker/scripts/{stages.ts → lib/stages.ts} +17 -5
- package/skills/c64-unpacker/scripts/unpack.ts +41 -24
- package/src/c64.ts +103 -5
- package/src/cli/cleanup.ts +1 -1
- package/src/cli/main.ts +14 -5
- package/src/cli/status.ts +70 -3
- package/src/graphics.ts +158 -0
- package/src/host/main.ts +25 -5
- package/src/host/server.ts +10 -3
- package/src/host/tools/petcat.ts +1 -1
- package/src/host/tools/status.ts +1 -1
- package/src/host/vice/adapter.ts +4 -3
- package/src/host/vice/process.ts +2 -2
- package/src/host/vice/screen.ts +1 -42
- package/src/host/vice/session.ts +196 -23
- package/src/host-client/autostart.ts +1 -1
- package/src/host-client/connect.ts +1 -1
- package/src/host-client/coverage.ts +2 -29
- package/src/host-client/image.ts +66 -0
- package/src/host-client/steps.ts +9 -4
- package/src/host-client/tools.ts +1 -0
- package/src/host-client/transfer.ts +39 -11
- package/src/host-client/vice-session.ts +33 -16
- package/src/knowledge/conflicts.ts +161 -0
- package/src/knowledge/database.ts +150 -38
- package/src/knowledge/expression.ts +209 -0
- package/src/knowledge/graph.ts +104 -0
- package/src/knowledge/history.ts +70 -22
- package/src/knowledge/import.ts +64 -55
- package/src/knowledge/join.ts +211 -0
- package/src/knowledge/program.ts +266 -0
- package/src/knowledge/read.ts +258 -27
- package/src/knowledge/schema.ts +199 -75
- package/src/knowledge/text.ts +415 -0
- package/src/knowledge/write.ts +740 -57
- package/src/mcp/main.ts +8 -2
- package/src/mcp/output.ts +15 -8
- package/src/mcp/server.ts +3 -1
- package/src/mcp/tools/debug.ts +97 -16
- package/src/mcp/tools/execution.ts +38 -5
- package/src/mcp/tools/machine.ts +11 -2
- package/src/mcp/tools/media.ts +18 -13
- package/src/mcp/tools/memory.ts +17 -8
- package/src/mcp/tools/video.ts +9 -9
- package/src/native/discover.ts +2 -2
- package/src/native/ghidra/analyze.ts +2 -2
- package/src/native/ghidra/index.ts +1 -1
- package/src/native/ghidra/language/c64rt_6510.slaspec +4 -1
- package/src/native/ghidra/scripts/C64Export.java +1 -1
- package/src/native/ghidra/scripts/C64Prepare.java +7 -1
- package/src/native/types.ts +14 -8
- package/{skills/c64-source/scripts → src}/opcodes.ts +75 -3
- package/src/package.ts +13 -3
- package/src/platform.ts +70 -7
- package/src/project.ts +180 -3
- package/src/protocol/messages.ts +1 -1
- package/src/protocol/tools.ts +2 -16
- package/src/protocol/vice.ts +124 -15
- package/src/script.ts +46 -7
- package/skills/c64-assembler/scripts/compare.ts +0 -84
- package/skills/c64-source/scripts/source.ts +0 -595
|
@@ -1,29 +1,54 @@
|
|
|
1
1
|
// The c64-provenance script: compares two or more releases byte by byte at
|
|
2
2
|
// their addresses and reports where they differ and which releases agree. A
|
|
3
|
-
// release is a PRG or an unpacker image.json
|
|
3
|
+
// release is a PRG or an unpacker image.json. With --run, each release
|
|
4
4
|
// runs in its own emulator, and each listed difference gets its first access
|
|
5
5
|
// and whether the run changed it. The separate command gives evidence from
|
|
6
|
-
//
|
|
6
|
+
// the program in the knowledge. The routine and match commands keep
|
|
7
|
+
// routines that several programs share in a library folder and find them in the
|
|
8
|
+
// program (D71).
|
|
7
9
|
|
|
8
|
-
import { posix } from "node:path";
|
|
10
|
+
import { basename, posix, relative } from "node:path";
|
|
9
11
|
import { parseArgs } from "node:util";
|
|
10
12
|
|
|
11
13
|
import { formatC64Address, parseC64Address } from "#src/c64.ts";
|
|
12
|
-
import { readCoverageFiles } from "#src/host-client/coverage.ts";
|
|
13
14
|
import { checkActionSteps, type ActionStep } from "#src/host-client/steps.ts";
|
|
14
15
|
import { MAX_EXECUTION_COUNT, WireFailure } from "#src/host-client/tools.ts";
|
|
15
16
|
import { readProjectFile, writeProjectFile } from "#src/host-client/transfer.ts";
|
|
16
17
|
import { ViceSessionClient } from "#src/host-client/vice-session.ts";
|
|
17
18
|
import { KnowledgeError, openForRead } from "#src/knowledge/database.ts";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
19
|
+
import { programCoverage, requireProgram } from "#src/knowledge/program.ts";
|
|
20
|
+
import {
|
|
21
|
+
commentsAt,
|
|
22
|
+
currentRevision,
|
|
23
|
+
listComments,
|
|
24
|
+
listModules,
|
|
25
|
+
listOperands,
|
|
26
|
+
listReferences,
|
|
27
|
+
listRegions,
|
|
28
|
+
listSymbols,
|
|
29
|
+
operandAt,
|
|
30
|
+
referencesFrom,
|
|
31
|
+
referenceSuppressed,
|
|
32
|
+
regionsOverlapping,
|
|
33
|
+
suppressionsOverlapping,
|
|
34
|
+
symbolAt,
|
|
35
|
+
symbolNamed,
|
|
36
|
+
symbolSuppressed,
|
|
37
|
+
} from "#src/knowledge/read.ts";
|
|
38
|
+
import { CODE_REGION_TYPES, type Origin } from "#src/knowledge/schema.ts";
|
|
39
|
+
import { platformNames } from "#src/platform.ts";
|
|
40
|
+
import { projectRoot } from "#src/project.ts";
|
|
20
41
|
import { runScript, UsageError } from "#src/script.ts";
|
|
21
|
-
import { compareReleases, shiftSuggestion, suggestShifts, type Range, type Release } from "./compare.ts";
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
42
|
+
import { compareReleases, shiftSuggestion, suggestShifts, type Range, type Release } from "./lib/compare.ts";
|
|
43
|
+
import { findRoutine, libraryFolder, LibraryError, readLibrary, routineFrom, saveRoutine, suggest } from "./lib/library.ts";
|
|
44
|
+
import { watchRelease, type ImageState, type Start } from "./lib/runtime.ts";
|
|
45
|
+
import { separate } from "./lib/separate.ts";
|
|
24
46
|
|
|
25
47
|
const USAGE = `provenance.ts <release> <release> [<release> ...] [options]
|
|
26
|
-
provenance.ts separate
|
|
48
|
+
provenance.ts separate [--out <file>] [--max-listed <n>]
|
|
49
|
+
provenance.ts routine --library <folder> (--module <address> | --start <address> --end <address>)
|
|
50
|
+
[--name <name>] [--title <text>] [--replace]
|
|
51
|
+
provenance.ts match --library <folder> [--out <file>] [--max-listed <n>]
|
|
27
52
|
|
|
28
53
|
--shift <n>:<bytes> move release n (1 = the first) by a number of bytes, for example 2:-256
|
|
29
54
|
--from <address> compare from this address
|
|
@@ -32,15 +57,28 @@ provenance.ts separate <release> [--coverage <file> ...] [--out <file>] [--max-l
|
|
|
32
57
|
give each listed difference its first access and whether it changed
|
|
33
58
|
--steps <file> with --run: action steps (JSON) to run before the frames
|
|
34
59
|
|
|
35
|
-
separate gives evidence from
|
|
36
|
-
its access in the
|
|
37
|
-
jump references in knowledge reach each code region from the entry, and what
|
|
38
|
-
program reads or executes outside
|
|
60
|
+
separate gives evidence from the program in the knowledge: the unpack stage
|
|
61
|
+
that wrote each range, its access in the coverage of the knowledge, whether call
|
|
62
|
+
and jump references in knowledge reach each code region from the entry, and what
|
|
63
|
+
the program reads or executes outside its segments. --out writes all ranges to a file;
|
|
39
64
|
the result lists at most --max-listed of each list (default 50) and its count.
|
|
40
65
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
project
|
|
66
|
+
routine saves a routine of the program in the knowledge to a library: a plain folder of routine
|
|
67
|
+
files, beside the projects (--library, relative to the working directory, as
|
|
68
|
+
--project). It takes a module of the knowledge, or --start and --end. Its
|
|
69
|
+
code regions give the instructions; the user and LLM names, regions, comments,
|
|
70
|
+
references and operand expressions go into the file at offsets. --name defaults
|
|
71
|
+
to the user or LLM name at the start, --title to the module title.
|
|
72
|
+
|
|
73
|
+
match finds each routine of the library in the program of the knowledge: the opcodes and other
|
|
74
|
+
fixed bytes equal, and the addresses into the routine moved by one shift.
|
|
75
|
+
Operands that address something outside the routine may differ; the result
|
|
76
|
+
lists them. --out writes the names of the routines at the found addresses as a
|
|
77
|
+
batch for knowledge.ts apply, and the result lists what it left out.
|
|
78
|
+
|
|
79
|
+
A release to compare is a PRG, placed at its load address, or an image.json of
|
|
80
|
+
the c64-unpacker skill, compared inside its segments. Paths are relative to the
|
|
81
|
+
project folder. The result is one JSON object.`;
|
|
44
82
|
|
|
45
83
|
function address(text: string, option: string): number {
|
|
46
84
|
try {
|
|
@@ -155,20 +193,40 @@ async function run(argv: string[]): Promise<unknown> {
|
|
|
155
193
|
to: { type: "string" },
|
|
156
194
|
run: { type: "string" },
|
|
157
195
|
steps: { type: "string" },
|
|
158
|
-
coverage: { type: "string", multiple: true },
|
|
159
196
|
out: { type: "string" },
|
|
160
197
|
"max-listed": { type: "string" },
|
|
198
|
+
library: { type: "string" },
|
|
199
|
+
module: { type: "string" },
|
|
200
|
+
start: { type: "string" },
|
|
201
|
+
end: { type: "string" },
|
|
202
|
+
name: { type: "string" },
|
|
203
|
+
title: { type: "string" },
|
|
204
|
+
replace: { type: "boolean" },
|
|
161
205
|
help: { type: "boolean" },
|
|
162
206
|
},
|
|
163
207
|
});
|
|
164
208
|
if (values.help) throw new UsageError("");
|
|
209
|
+
/** Refuses the options that `command` does not take. */
|
|
210
|
+
const only = (command: string, allowed: readonly string[]) => {
|
|
211
|
+
const used = Object.keys(values).filter((option) => option !== "help" && !allowed.includes(option));
|
|
212
|
+
if (used.length > 0) throw new UsageError(`${command} does not take --${used.join(", --")}`);
|
|
213
|
+
};
|
|
165
214
|
if (positionals[0] === "separate") {
|
|
166
|
-
|
|
167
|
-
if (
|
|
168
|
-
|
|
169
|
-
|
|
215
|
+
only("separate", ["out", "max-listed"]);
|
|
216
|
+
if (positionals.length !== 1) throw new UsageError("separate takes no release: it reads the program from the knowledge");
|
|
217
|
+
return separateCommand(values.out, values["max-listed"]);
|
|
218
|
+
}
|
|
219
|
+
if (positionals[0] === "routine") {
|
|
220
|
+
only("routine", ["library", "module", "start", "end", "name", "title", "replace"]);
|
|
221
|
+
if (positionals.length !== 1) throw new UsageError("routine takes no image: it reads the program from the knowledge");
|
|
222
|
+
return routineCommand(values);
|
|
223
|
+
}
|
|
224
|
+
if (positionals[0] === "match") {
|
|
225
|
+
only("match", ["library", "out", "max-listed"]);
|
|
226
|
+
if (positionals.length !== 1) throw new UsageError("match takes no image: it reads the program from the knowledge");
|
|
227
|
+
return matchCommand(values.library, values.out, values["max-listed"]);
|
|
170
228
|
}
|
|
171
|
-
|
|
229
|
+
only("a comparison", ["shift", "from", "to", "run", "steps"]);
|
|
172
230
|
if (positionals.length < 2) throw new UsageError("give two or more releases");
|
|
173
231
|
const shifts = new Map<number, number>();
|
|
174
232
|
for (const text of values.shift ?? []) {
|
|
@@ -204,7 +262,7 @@ async function run(argv: string[]): Promise<unknown> {
|
|
|
204
262
|
const compared = comparison.identicalBytes + comparison.differingBytes;
|
|
205
263
|
const identicalShare = compared === 0 ? 0 : comparison.identicalBytes / compared;
|
|
206
264
|
// A poor match at the load addresses can mean that the releases are placed differently.
|
|
207
|
-
// Each release gets its best shifts: data that stayed can outvote code that moved, so one is not enough
|
|
265
|
+
// Each release gets its best shifts: data that stayed can outvote code that moved, so one is not enough.
|
|
208
266
|
const applied = loaded.map((item) => item.shift);
|
|
209
267
|
const suggestions =
|
|
210
268
|
identicalShare < 0.5
|
|
@@ -255,7 +313,7 @@ async function run(argv: string[]): Promise<unknown> {
|
|
|
255
313
|
end: formatC64Address(difference.end),
|
|
256
314
|
values: Object.fromEntries(Object.entries(difference.values).map(([name, bytes]) => [name, hexBytes(bytes.slice(0, 32)) + (bytes.length > 32 ? " ..." : "")])),
|
|
257
315
|
groups: difference.groups,
|
|
258
|
-
// runtime gives the release's own addresses: with a shift, they are not the addresses of the difference
|
|
316
|
+
// runtime gives the release's own addresses: with a shift, they are not the addresses of the difference.
|
|
259
317
|
...(runtime.size === 0
|
|
260
318
|
? {}
|
|
261
319
|
: {
|
|
@@ -276,28 +334,43 @@ async function run(argv: string[]): Promise<unknown> {
|
|
|
276
334
|
};
|
|
277
335
|
}
|
|
278
336
|
|
|
279
|
-
/**
|
|
280
|
-
function
|
|
337
|
+
/** The stages of the capture in the knowledge as ranges; a stage that is not a capture stage is refused. */
|
|
338
|
+
function stagesOf(stages: Array<Record<string, unknown>> | undefined): Array<{ entry: number; wrote: Range[] }> | undefined {
|
|
339
|
+
if (stages === undefined) return undefined;
|
|
340
|
+
const parse = (value: unknown) => {
|
|
341
|
+
if (typeof value === "string" && /^\$[0-9a-f]{4}$/i.test(value)) return Number.parseInt(value.slice(1), 16);
|
|
342
|
+
throw new UsageError("the stages of the program in the knowledge are not the stages of a c64-unpacker capture; put the capture in again with knowledge.ts image");
|
|
343
|
+
};
|
|
344
|
+
return stages.map((stage) => ({
|
|
345
|
+
entry: parse(stage.entry),
|
|
346
|
+
wrote: (Array.isArray(stage.wrote) ? (stage.wrote as Array<Record<string, unknown>>) : []).map((range) => ({ start: parse(range.start), end: parse(range.end) })),
|
|
347
|
+
}));
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** separate: evidence from the program in the knowledge, with all ranges in --out and the first ones in the result. */
|
|
351
|
+
function separateCommand(out: string | undefined, maxListed: string | undefined): unknown {
|
|
281
352
|
const max = maxListed ?? "50";
|
|
282
353
|
if (!/^\d{1,5}$/.test(max)) throw new UsageError(`--max-listed must be a whole number, not ${max}`);
|
|
283
|
-
const loaded = readRelease(path, 0);
|
|
284
|
-
const coverage = readCoverageFiles(coveragePaths, (message) => {
|
|
285
|
-
throw new UsageError(message);
|
|
286
|
-
});
|
|
287
354
|
const db = openForRead();
|
|
355
|
+
let program: ReturnType<typeof requireProgram>;
|
|
356
|
+
let coverage: Uint8Array | undefined;
|
|
288
357
|
let regions: ReturnType<typeof listRegions>;
|
|
289
358
|
let references: ReturnType<typeof listReferences>;
|
|
290
359
|
try {
|
|
360
|
+
program = requireProgram(db);
|
|
361
|
+
coverage = programCoverage(db)?.map;
|
|
291
362
|
regions = listRegions(db).filter((region) => CODE_REGION_TYPES.includes(region.type));
|
|
292
363
|
references = listReferences(db);
|
|
293
364
|
} finally {
|
|
294
365
|
db?.close();
|
|
295
366
|
}
|
|
367
|
+
const stages = stagesOf(program.stages);
|
|
368
|
+
const loaded = { entry: program.entry, stages };
|
|
296
369
|
const found = separate({
|
|
297
|
-
segments:
|
|
298
|
-
memory:
|
|
299
|
-
...(
|
|
300
|
-
...(
|
|
370
|
+
segments: program.segments,
|
|
371
|
+
memory: program.memory,
|
|
372
|
+
...(program.entry === undefined ? {} : { entry: program.entry }),
|
|
373
|
+
...(stages === undefined ? {} : { stages }),
|
|
301
374
|
...(coverage === undefined ? {} : { coverage }),
|
|
302
375
|
codeRegions: regions,
|
|
303
376
|
references,
|
|
@@ -307,21 +380,20 @@ function separateCommand(path: string, coveragePaths: string[], out: string | un
|
|
|
307
380
|
const codeRegions = found.codeRegions.map((region) => ({ ...show(region), type: region.type, ...(region.reachable === undefined ? {} : { reachable: region.reachable }), ...(region.executed === undefined ? {} : { executed: region.executed }) }));
|
|
308
381
|
const outside = { read: found.outside.read.map(show), executed: found.outside.executed.map(show), generated: found.outside.generated.map(show) };
|
|
309
382
|
const listed = <T>(items: T[]) => ({ count: items.length, listed: items.slice(0, Number(max)) });
|
|
310
|
-
if (out !== undefined) writeProjectFile(out, Buffer.from(`${JSON.stringify({
|
|
383
|
+
if (out !== undefined) writeProjectFile(out, Buffer.from(`${JSON.stringify({ ranges, codeRegions, outside }, null, 1)}\n`), { what: "--out" });
|
|
311
384
|
const bytes = (filter: (range: (typeof found.ranges)[number]) => boolean) => found.ranges.filter(filter).reduce((sum, range) => sum + range.end - range.start + 1, 0);
|
|
312
385
|
const lastStage = loaded.stages === undefined ? undefined : loaded.stages.length - 1;
|
|
313
386
|
return {
|
|
314
|
-
release: path,
|
|
315
387
|
...(loaded.entry === undefined ? {} : { entry: formatC64Address(loaded.entry) }),
|
|
316
388
|
...(lastStage === undefined
|
|
317
|
-
? { noStages: "
|
|
389
|
+
? { noStages: "The program in the knowledge has no stage log. Only a capture of c64-unpacker gives one. A capture of loads holds one part of a program and has none." }
|
|
318
390
|
: {
|
|
319
391
|
stages: lastStage + 1,
|
|
320
392
|
bytesByStage: Object.fromEntries([...[...Array(lastStage + 1).keys()].map((stage): [string, number] => [String(stage), bytes((range) => range.stage === stage)]), ["none", bytes((range) => range.stage === undefined)]]),
|
|
321
393
|
}),
|
|
322
394
|
...(coverage === undefined ? {} : { bytesByAccess: Object.fromEntries((["executed", "read", "written", "read-written", "untouched"] as const).map((access) => [access, bytes((range) => range.access === access)])) }),
|
|
323
395
|
ranges: listed(ranges),
|
|
324
|
-
// total counts every code region; unreachable is a list like the others
|
|
396
|
+
// total counts every code region; unreachable is a list like the others.
|
|
325
397
|
codeRegions: {
|
|
326
398
|
total: codeRegions.length,
|
|
327
399
|
...(loaded.entry === undefined ? {} : { unreachable: listed(codeRegions.filter((region) => region.reachable === false)) }),
|
|
@@ -331,4 +403,134 @@ function separateCommand(path: string, coveragePaths: string[], out: string | un
|
|
|
331
403
|
};
|
|
332
404
|
}
|
|
333
405
|
|
|
334
|
-
|
|
406
|
+
const SEMANTIC: readonly Origin[] = ["user", "llm"];
|
|
407
|
+
|
|
408
|
+
const platform = () => new Map(platformNames().map((entry) => [entry.name, entry.address]));
|
|
409
|
+
|
|
410
|
+
/** routine: saves a module or a range of the program in the knowledge, with its knowledge, as a routine of the library (D71). */
|
|
411
|
+
function routineCommand(values: { library?: string; module?: string; start?: string; end?: string; name?: string; title?: string; replace?: boolean }): unknown {
|
|
412
|
+
if (values.library === undefined) throw new UsageError("routine needs --library <folder>");
|
|
413
|
+
if (values.module !== undefined && (values.start !== undefined || values.end !== undefined)) throw new UsageError("routine takes --module, or --start and --end, not both");
|
|
414
|
+
if (values.module === undefined && values.start === undefined && values.end === undefined) throw new UsageError("routine needs --module <address>, or --start and --end");
|
|
415
|
+
if (values.module === undefined && (values.start === undefined || values.end === undefined)) throw new UsageError("routine needs both --start and --end");
|
|
416
|
+
const db = openForRead();
|
|
417
|
+
try {
|
|
418
|
+
const image = requireProgram(db);
|
|
419
|
+
const segmentOf = (at: number) => image.segments.find((segment) => at >= segment.start && at <= segment.end);
|
|
420
|
+
let start: number;
|
|
421
|
+
let end: number;
|
|
422
|
+
let title = values.title;
|
|
423
|
+
if (values.module !== undefined) {
|
|
424
|
+
start = address(values.module, "--module");
|
|
425
|
+
const modules = listModules(db);
|
|
426
|
+
const module = modules.find((item) => item.start === start);
|
|
427
|
+
if (module === undefined) throw new LibraryError("not-found", `No module starts at ${formatC64Address(start)}. List the modules with knowledge.ts modules, or give --start and --end.`);
|
|
428
|
+
const segment = segmentOf(start);
|
|
429
|
+
if (segment === undefined) throw new UsageError(`The module at ${formatC64Address(start)} is outside the segments of the program.`);
|
|
430
|
+
end = (modules.find((item) => item.start > start && item.start <= segment.end)?.start ?? segment.end + 1) - 1;
|
|
431
|
+
title ??= module.title;
|
|
432
|
+
} else {
|
|
433
|
+
start = address(values.start!, "--start");
|
|
434
|
+
end = address(values.end!, "--end");
|
|
435
|
+
if (end < start) throw new UsageError("--end is before --start");
|
|
436
|
+
if (segmentOf(start) === undefined || segmentOf(start) !== segmentOf(end)) throw new UsageError(`${formatC64Address(start)}-${formatC64Address(end)} is not inside one segment of the program.`);
|
|
437
|
+
}
|
|
438
|
+
const range = { start, end };
|
|
439
|
+
const name = values.name ?? listSymbols(db, { start, end: start }, undefined, SEMANTIC)[0]?.name;
|
|
440
|
+
if (name === undefined) throw new UsageError(`The knowledge has no user or LLM name at ${formatC64Address(start)}: give --name.`);
|
|
441
|
+
const { routine, leftOut } = routineFrom({
|
|
442
|
+
name,
|
|
443
|
+
...(title === undefined ? {} : { title }),
|
|
444
|
+
program: basename(projectRoot()),
|
|
445
|
+
image: `knowledge revision ${currentRevision(db)}`,
|
|
446
|
+
start,
|
|
447
|
+
end,
|
|
448
|
+
byteAt: image.byteAt,
|
|
449
|
+
analyzed: listRegions(db, range),
|
|
450
|
+
symbols: listSymbols(db, range, undefined, SEMANTIC),
|
|
451
|
+
regions: listRegions(db, range, undefined, SEMANTIC),
|
|
452
|
+
comments: listComments(db, range, SEMANTIC),
|
|
453
|
+
references: listReferences(db, range, undefined, SEMANTIC),
|
|
454
|
+
operands: listOperands(db, range, SEMANTIC),
|
|
455
|
+
platform: platform(),
|
|
456
|
+
});
|
|
457
|
+
const file = saveRoutine(libraryFolder(values.library, { create: true }), routine, values.replace === true);
|
|
458
|
+
return {
|
|
459
|
+
routine: routine.name,
|
|
460
|
+
file: relative(process.cwd(), file),
|
|
461
|
+
start: formatC64Address(start),
|
|
462
|
+
end: formatC64Address(end),
|
|
463
|
+
length: routine.bytes.length,
|
|
464
|
+
instructions: routine.instructions.length,
|
|
465
|
+
relocations: routine.relocations.length,
|
|
466
|
+
variable: routine.variable.length,
|
|
467
|
+
counts: { symbols: routine.symbols.length, regions: routine.regions.length, comments: routine.comments.length, references: routine.references.length, operands: routine.operands.length },
|
|
468
|
+
...(leftOut.length === 0 ? {} : { leftOut }),
|
|
469
|
+
};
|
|
470
|
+
} finally {
|
|
471
|
+
db?.close();
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/** match: finds the routines of the library in the program of the knowledge, and with --out writes their names there as an apply batch (D71). */
|
|
476
|
+
function matchCommand(library: string | undefined, out: string | undefined, maxListed: string | undefined): unknown {
|
|
477
|
+
if (library === undefined) throw new UsageError("match needs --library <folder>");
|
|
478
|
+
const max = maxListed ?? "50";
|
|
479
|
+
if (!/^\d{1,5}$/.test(max)) throw new UsageError(`--max-listed must be a whole number, not ${max}`);
|
|
480
|
+
const listed = <T>(items: T[]) => ({ count: items.length, listed: items.slice(0, Number(max)) });
|
|
481
|
+
const routines = readLibrary(libraryFolder(library, { create: false }));
|
|
482
|
+
if (routines.length === 0) throw new LibraryError("not-found", `The library ${library} has no routine files. Save a routine with routine first.`);
|
|
483
|
+
const programDb = openForRead();
|
|
484
|
+
let image: ReturnType<typeof requireProgram>;
|
|
485
|
+
try {
|
|
486
|
+
image = requireProgram(programDb);
|
|
487
|
+
} finally {
|
|
488
|
+
programDb?.close();
|
|
489
|
+
}
|
|
490
|
+
const found = routines.map((routine) => ({ routine, matches: findRoutine(routine, image) }));
|
|
491
|
+
const show = (value: number, size: 1 | 2) => `$${value.toString(16).padStart(2 * size, "0")}`;
|
|
492
|
+
const matches = found
|
|
493
|
+
.flatMap(({ routine, matches: each }) =>
|
|
494
|
+
each.map((match) => ({
|
|
495
|
+
routine: match.routine,
|
|
496
|
+
start: formatC64Address(match.start),
|
|
497
|
+
end: formatC64Address(match.start + routine.bytes.length - 1),
|
|
498
|
+
shift: match.shift,
|
|
499
|
+
differing: match.differing.map((operand) => ({ address: formatC64Address(operand.address), library: show(operand.library, operand.size), program: show(operand.program, operand.size) })),
|
|
500
|
+
})),
|
|
501
|
+
)
|
|
502
|
+
.sort((a, b) => a.start.localeCompare(b.start));
|
|
503
|
+
const result: Record<string, unknown> = { library, routines: routines.length, matches: listed(matches) };
|
|
504
|
+
if (out === undefined) return result;
|
|
505
|
+
const db = openForRead();
|
|
506
|
+
let suggestions: ReturnType<typeof suggest>;
|
|
507
|
+
try {
|
|
508
|
+
suggestions = suggest(
|
|
509
|
+
found,
|
|
510
|
+
{
|
|
511
|
+
symbolAt: (at) => symbolAt(db, at),
|
|
512
|
+
symbolNamed: (name) => symbolNamed(db, name),
|
|
513
|
+
regionsOverlapping: (start, end) => regionsOverlapping(db, start, end),
|
|
514
|
+
commentsAt: (at) => commentsAt(db, at),
|
|
515
|
+
referencesFrom: (at) => referencesFrom(db, at),
|
|
516
|
+
operandAt: (at) => operandAt(db, at),
|
|
517
|
+
symbolSuppressed: (at) => symbolSuppressed(db, at),
|
|
518
|
+
regionSuppressed: (start, end) => suppressionsOverlapping(db, "region", start, end).length > 0,
|
|
519
|
+
referenceSuppressed: (reference) => referenceSuppressed(db, reference),
|
|
520
|
+
},
|
|
521
|
+
image,
|
|
522
|
+
platform(),
|
|
523
|
+
);
|
|
524
|
+
} finally {
|
|
525
|
+
db?.close();
|
|
526
|
+
}
|
|
527
|
+
const { batch, known, leftOut } = suggestions;
|
|
528
|
+
writeProjectFile(out, Buffer.from(batch.length === 0 ? "[]\n" : `[\n${batch.map((write) => ` ${JSON.stringify(write)}`).join(",\n")}\n]\n`), { what: "--out" });
|
|
529
|
+
return {
|
|
530
|
+
...result,
|
|
531
|
+
batch: { out, writes: batch.length, known, leftOut: listed(leftOut) },
|
|
532
|
+
...(batch.length === 0 ? {} : { apply: `knowledge.ts apply ${out} --reason "names of library routines"` }),
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
await runScript((argv) => run(argv), { usage: USAGE, refusals: [KnowledgeError, LibraryError] });
|
|
@@ -26,6 +26,21 @@ This skill does not do the specialist work itself.
|
|
|
26
26
|
|
|
27
27
|
This skill uses the other skills. It does not use the host runtime or the emulator directly.
|
|
28
28
|
|
|
29
|
+
### Project folder
|
|
30
|
+
|
|
31
|
+
One repository can hold several programs, with one project folder for each program. A project folder holds `.c64-re-tools/`. Work on one project at a time:
|
|
32
|
+
|
|
33
|
+
- From the root of the repository, give `--project <folder>` to each skill script, and `project` to each emulator tool with a path.
|
|
34
|
+
- Write all files in the project folder. Do not write files at the root of the repository.
|
|
35
|
+
- Write each file that you make for your work in `.c64-re-tools/local/` of the project. Git ignores that folder. Examples are results, batches, step files, runs, snapshots and memory dumps.
|
|
36
|
+
- Move a file out of `.c64-re-tools/local/` only when it is the evidence for a finding. Move it before you give it to `--evidence` of c64-knowledge. `--evidence` refuses a file in `.c64-re-tools/local/`.
|
|
37
|
+
- Put a run that is evidence in `runs/<name>/`, with its run file, its results and the snapshot files that it uses. Change the paths in the run file to the new paths. Then git keeps them, and another person can do the run again.
|
|
38
|
+
- A subagent can have no emulator tools. Give it its experiments as run files of the c64-testing skill. Each run starts its own emulator, so many runs can go at the same time. The emulator of the emulator tools is one machine, for the main session.
|
|
39
|
+
- To name a large program with more agents at the same time, give each agent one range of addresses. Each agent writes a batch with its owner range. Then join the batches with `join` of the c64-knowledge skill, and apply the result one time.
|
|
40
|
+
- Do not write a build script or a merge script. c64-source writes and assembles the loader with `--loader`, and `join` of c64-knowledge joins the batches.
|
|
41
|
+
- Do not write a script that makes a deliverable again, for example a capture, copy or replay script. Commit the deliverable.
|
|
42
|
+
- Write the knowledge only with the c64-knowledge script. Do not write or edit the files in `.c64-re-tools/knowledge/` yourself.
|
|
43
|
+
|
|
29
44
|
## Workflow
|
|
30
45
|
|
|
31
46
|
Select the path for the scope:
|
|
@@ -48,6 +63,7 @@ Select the path for the scope:
|
|
|
48
63
|
| The routines, code, data and references of the program | c64-static-analysis |
|
|
49
64
|
| The platform meaning of an address or a register | c64-memory-map |
|
|
50
65
|
| Differences between releases of the program | c64-provenance |
|
|
66
|
+
| A routine that another program of the workspace has, for example a music driver | c64-provenance, with the library |
|
|
51
67
|
| Source code from the knowledge | c64-source |
|
|
52
68
|
| Source code to build | c64-assembler |
|
|
53
69
|
| Proof that a rebuild behaves as the original | c64-testing |
|
|
@@ -58,61 +74,69 @@ Select the path for the scope:
|
|
|
58
74
|
|
|
59
75
|
### The stages of a full rebuild
|
|
60
76
|
|
|
61
|
-
Do the stages in this sequence. Go to the next stage only when its exit check is true, or when the user accepts an exception.
|
|
77
|
+
Do the stages in this sequence. Go to the next stage only when its exit check is true, or when the user accepts an exception. Remember each exception for the answer of stage 11.
|
|
62
78
|
|
|
63
79
|
| Stage | Work | Exit check |
|
|
64
80
|
|---|---|---|
|
|
65
|
-
| 1. Acceptance | Ask the user for the criteria of the rebuild. Offer these: identical source, a verified loader, passed c64-testing scenarios, a list of the code that no run executed.
|
|
81
|
+
| 1. Acceptance | Ask the user for the criteria of the rebuild. Offer these: identical source, a verified loader, passed c64-testing scenarios, and a list of the code that no run executed. Also offer documentation of the flows, tables, formats and graphics. | The user gave the criteria. |
|
|
66
82
|
| 2. Get | c64-disk: list the disk. Extract the start file into `original/`. | The start file is in the project. |
|
|
67
83
|
| 3. Start | c64-basic: find the SYS address of the BASIC start. | You have the entry address. |
|
|
68
|
-
| 4. Unpack | c64-unpacker: examine the program. Capture a packed program into
|
|
69
|
-
| 5. Run | c64-emulator and the run of c64-testing: run each part that the scope names. Write a coverage
|
|
70
|
-
| 6. Analyze | c64-static-analysis
|
|
71
|
-
| 7. Name | c64-knowledge and c64-memory-map: name each routine in the scope and give it a comment. Record the modules. | Each routine
|
|
72
|
-
| 8. Generate | c64-source
|
|
84
|
+
| 4. Unpack | c64-unpacker: examine the program. Capture a packed program into `.c64-re-tools/local/`. Then c64-knowledge: put the capture, or the PRG of a program without a packer, into the knowledge with `image`. | `program` of c64-knowledge shows the segments and the entry. |
|
|
85
|
+
| 5. Run | c64-emulator and the run of c64-testing: run each part that the scope names. Write a coverage map for each part into `.c64-re-tools/local/`. Then c64-knowledge: add the maps with `coverage`. | `program` of c64-knowledge lists a coverage map for each part that the scope names. |
|
|
86
|
+
| 6. Analyze | c64-static-analysis. It reads the program and the coverage from the knowledge. | No coverage conflict is open. |
|
|
87
|
+
| 7. Name | c64-provenance: if the workspace has a library, find its routines with `match` and apply the batch. Then c64-knowledge and c64-memory-map: name each routine in the scope and give it a comment. Record the modules. When the scope has hundreds of routines, ask the user which routines to name. The source gives the other routines an `l_xxxx` label. | Each routine that the user selected has a name and a comment. |
|
|
88
|
+
| 8. Generate | c64-source into `src/`. For an unpacked program, add `--loader`. | `check.identical` is true. For an unpacked program, `loader.verified` is true. |
|
|
73
89
|
| 9. Build and test | c64-assembler with `--compare` into `build/`. c64-testing scenarios in `tests/`. | The criteria of stage 1 are true. |
|
|
74
|
-
| 10.
|
|
90
|
+
| 10. Document | When the criteria ask for documentation: c64-docs into `README.md` and `docs/`. | `link.ts --check` of c64-docs gives no problem. |
|
|
91
|
+
| 11. Hand over | Find each file that you made outside `.c64-re-tools/local/` and that is not a deliverable. Move it into `.c64-re-tools/local/`. Then give the answer (below). | Each file that you made outside `.c64-re-tools/local/` is a deliverable. |
|
|
75
92
|
|
|
76
93
|
In stage 5, if you cannot get to a part of the program, ask the user how to get to it. Do stages 7 and 8 again until the source is clear. Each run of c64-source writes the source again.
|
|
77
94
|
|
|
95
|
+
After stage 7, save each routine that other programs can have to the library with c64-provenance `routine`. Examples are a music driver, a decruncher and a loader. Ask the user before you make a library.
|
|
96
|
+
|
|
78
97
|
### Deliverables of a full rebuild
|
|
79
98
|
|
|
80
|
-
- `.c64-re-tools/knowledge
|
|
81
|
-
- the image and the entry state
|
|
82
|
-
- the coverage files
|
|
99
|
+
- `.c64-re-tools/knowledge/` with the program, its entry state, the coverage, the names, the regions, the comments and the modules
|
|
83
100
|
- the generated source
|
|
84
101
|
- the built program and the loader
|
|
85
|
-
- the c64-testing scenarios
|
|
86
|
-
- `
|
|
102
|
+
- the c64-testing scenarios
|
|
103
|
+
- the documentation in `README.md` and `docs/`, when the criteria ask for it
|
|
104
|
+
- the runs in `runs/` that the knowledge gives as evidence
|
|
105
|
+
- the scripts in `tools/` for the formats of this program, when you wrote them.
|
|
87
106
|
|
|
88
|
-
|
|
107
|
+
All other files that you made are not deliverables. Keep them in `.c64-re-tools/local/`. Do not delete a file that the user made.
|
|
89
108
|
|
|
90
|
-
|
|
109
|
+
### The answer
|
|
91
110
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
111
|
+
Give the user a short answer at the end. Do not write a report file. Write a report file only when the user asks for it. The knowledge, `docs/` and the scenarios keep the durable results. The answer has:
|
|
112
|
+
|
|
113
|
+
1. Each criterion of stage 1 and its result, and each exception that the user accepted.
|
|
114
|
+
2. The count of the code ranges that no run executed. The ranges are in the `not_executed.json` file of c64-source.
|
|
115
|
+
3. Open questions: what the evidence does not explain.
|
|
97
116
|
|
|
98
117
|
Keep observed facts apart from your conclusions.
|
|
99
118
|
|
|
100
119
|
### Project layout
|
|
101
120
|
|
|
102
|
-
If the project has no layout, use this one.
|
|
121
|
+
If the project has no layout, use this one. No script checks it. Each script takes the paths that you give.
|
|
103
122
|
|
|
104
123
|
```
|
|
105
124
|
original/ the disk image or the program as you got it
|
|
106
|
-
unpacked/ image.json, image.bin and entry_state.json from c64-unpacker
|
|
107
|
-
coverage/ coverage files from c64_memmap
|
|
108
125
|
src/ the generated source from c64-source
|
|
109
126
|
build/ programs from c64-assembler
|
|
110
|
-
tests/ c64-testing scenarios
|
|
111
|
-
|
|
127
|
+
tests/ c64-testing scenarios
|
|
128
|
+
runs/ the runs that the knowledge gives as evidence
|
|
129
|
+
docs/ the documentation from c64-docs, with the reference in docs/reference/
|
|
130
|
+
tools/ scripts for the formats of this program only
|
|
131
|
+
README.md the entry page of the documentation from c64-docs
|
|
112
132
|
```
|
|
113
133
|
|
|
134
|
+
Commit each folder of the layout and `.c64-re-tools/knowledge/`. The knowledge holds the program, the entry state and the coverage, so the project needs no other folder for them. Put all other files that you make in `.c64-re-tools/local/`, also the captures and the coverage maps after you put them into the knowledge. Do not let git ignore a folder of the layout. Do not change the `.gitignore` files of the project.
|
|
135
|
+
|
|
114
136
|
If the project has a layout, use that layout.
|
|
115
137
|
|
|
138
|
+
A workspace with several projects keeps its library of routines in `library/` beside the projects. The library is not a project folder.
|
|
139
|
+
|
|
116
140
|
## Knowledge
|
|
117
141
|
|
|
118
142
|
Read:
|
|
@@ -129,11 +153,12 @@ Record durable conclusions with the c64-knowledge skill. Do not store:
|
|
|
129
153
|
|
|
130
154
|
## Result
|
|
131
155
|
|
|
132
|
-
A clear answer for the scope, with the durable conclusions in the project knowledge. For a full rebuild, the deliverables and the
|
|
156
|
+
A clear answer for the scope, with the durable conclusions in the project knowledge. For a full rebuild, the deliverables and the answer of stage 11, with the criteria of the user met or each exception in the answer.
|
|
133
157
|
|
|
134
158
|
## Failure and conflicts
|
|
135
159
|
|
|
136
160
|
- A skill reports `installation-incomplete`, or `machine-unavailable` with a message that the host runtime does not run: stop the whole task. Give the message to the user and wait for the user. Do not continue with other skills. Do not make a replacement for the tool or the host runtime, for example your own disk parser, disassembler or emulator script.
|
|
161
|
+
- A tool gives a wrong result, and the exit check of a stage cannot be true. Tell the user the defect and the evidence, and wait for the user. Do not make a workaround, for example a change to a generated file. When the user accepts an exception, give the defect and the exception in the answer of stage 11.
|
|
137
162
|
- If the evidence does not support a conclusion, keep the unknown and get more evidence. Do not make up a meaning.
|
|
138
163
|
- Before you replace current knowledge from the user or the LLM, read its history and the evidence for it.
|
|
139
164
|
- A conflict from the static analysis: find the correct answer at run time with the c64-emulator skill.
|