@henols/c64-re-tools 2.0.0-rc.9 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +97 -116
  2. package/package.json +1 -1
  3. package/skills/c64-assembler/SKILL.md +22 -6
  4. package/skills/c64-assembler/scripts/assemble.ts +42 -14
  5. package/skills/c64-assembler/scripts/lib/compare.ts +48 -0
  6. package/skills/c64-basic/SKILL.md +10 -1
  7. package/skills/c64-basic/scripts/basic.ts +3 -3
  8. package/skills/c64-disk/SKILL.md +13 -4
  9. package/skills/c64-disk/scripts/disk.ts +7 -7
  10. package/skills/c64-disk/scripts/{containers.ts → lib/containers.ts} +2 -2
  11. package/skills/c64-docs/SKILL.md +138 -0
  12. package/skills/c64-docs/references/page-types.md +141 -0
  13. package/skills/c64-docs/references/style.md +43 -0
  14. package/skills/c64-docs/scripts/generate.ts +109 -0
  15. package/skills/c64-docs/scripts/lib/links.ts +167 -0
  16. package/skills/c64-docs/scripts/lib/markdown.ts +79 -0
  17. package/skills/c64-docs/scripts/lib/reference.ts +406 -0
  18. package/skills/c64-docs/scripts/lib/source-map.ts +81 -0
  19. package/skills/c64-docs/scripts/link.ts +239 -0
  20. package/skills/c64-emulator/SKILL.md +16 -5
  21. package/skills/c64-knowledge/SKILL.md +140 -18
  22. package/skills/c64-knowledge/scripts/knowledge.ts +737 -112
  23. package/skills/c64-knowledge/scripts/lib/listing.ts +162 -0
  24. package/skills/c64-memory-map/SKILL.md +11 -2
  25. package/skills/c64-memory-map/scripts/memmap.ts +2 -2
  26. package/skills/c64-provenance/SKILL.md +66 -11
  27. package/skills/c64-provenance/scripts/{compare.ts → lib/compare.ts} +3 -3
  28. package/skills/c64-provenance/scripts/lib/library.ts +584 -0
  29. package/skills/c64-provenance/scripts/{runtime.ts → lib/runtime.ts} +2 -2
  30. package/skills/c64-provenance/scripts/{separate.ts → lib/separate.ts} +3 -3
  31. package/skills/c64-provenance/scripts/provenance.ts +242 -40
  32. package/skills/c64-reverse-engineering/SKILL.md +51 -26
  33. package/skills/c64-source/SKILL.md +76 -28
  34. package/skills/c64-source/scripts/generate.ts +425 -127
  35. package/skills/c64-source/scripts/{loader.ts → lib/loader.ts} +84 -25
  36. package/skills/c64-source/scripts/lib/source.ts +1121 -0
  37. package/skills/c64-static-analysis/SKILL.md +35 -18
  38. package/skills/c64-static-analysis/scripts/analyze.ts +184 -55
  39. package/skills/c64-static-analysis/scripts/{coverage.ts → lib/coverage.ts} +19 -25
  40. package/skills/c64-static-analysis/scripts/{findings.ts → lib/findings.ts} +6 -2
  41. package/skills/c64-static-analysis/scripts/{idioms.ts → lib/idioms.ts} +21 -12
  42. package/skills/c64-static-analysis/scripts/{instructions.ts → lib/instructions.ts} +1 -1
  43. package/skills/c64-static-analysis/scripts/lib/macros.ts +233 -0
  44. package/skills/c64-static-analysis/scripts/lib/pitfalls.ts +68 -0
  45. package/skills/c64-static-analysis/scripts/lib/pointers.ts +108 -0
  46. package/skills/c64-testing/SKILL.md +36 -7
  47. package/skills/c64-testing/references/scenario-format.md +12 -8
  48. package/skills/c64-testing/scripts/checklist.ts +3 -3
  49. package/skills/c64-testing/scripts/diff.ts +167 -0
  50. package/skills/c64-testing/scripts/lib/diff.ts +70 -0
  51. package/skills/c64-testing/scripts/{scenario.ts → lib/scenario.ts} +45 -34
  52. package/skills/c64-testing/scripts/run.ts +73 -21
  53. package/skills/c64-testing/scripts/test.ts +5 -5
  54. package/skills/c64-unpacker/SKILL.md +23 -9
  55. package/skills/c64-unpacker/scripts/{evidence.ts → lib/evidence.ts} +1 -1
  56. package/skills/c64-unpacker/scripts/{loads.ts → lib/loads.ts} +4 -4
  57. package/skills/c64-unpacker/scripts/{machine.ts → lib/machine.ts} +4 -4
  58. package/skills/c64-unpacker/scripts/{stages.ts → lib/stages.ts} +17 -5
  59. package/skills/c64-unpacker/scripts/unpack.ts +41 -24
  60. package/src/c64.ts +103 -5
  61. package/src/cli/cleanup.ts +1 -1
  62. package/src/cli/main.ts +7 -4
  63. package/src/cli/status.ts +70 -3
  64. package/src/graphics.ts +158 -0
  65. package/src/host/main.ts +18 -4
  66. package/src/host/server.ts +10 -3
  67. package/src/host/tools/petcat.ts +1 -1
  68. package/src/host/tools/status.ts +1 -1
  69. package/src/host/vice/adapter.ts +4 -3
  70. package/src/host/vice/process.ts +2 -2
  71. package/src/host/vice/screen.ts +1 -42
  72. package/src/host/vice/session.ts +196 -23
  73. package/src/host-client/autostart.ts +1 -1
  74. package/src/host-client/connect.ts +1 -1
  75. package/src/host-client/coverage.ts +2 -29
  76. package/src/host-client/image.ts +66 -0
  77. package/src/host-client/steps.ts +9 -4
  78. package/src/host-client/tools.ts +1 -0
  79. package/src/host-client/transfer.ts +39 -11
  80. package/src/host-client/vice-session.ts +33 -16
  81. package/src/knowledge/conflicts.ts +161 -0
  82. package/src/knowledge/database.ts +150 -38
  83. package/src/knowledge/expression.ts +209 -0
  84. package/src/knowledge/graph.ts +104 -0
  85. package/src/knowledge/history.ts +70 -22
  86. package/src/knowledge/import.ts +64 -55
  87. package/src/knowledge/join.ts +211 -0
  88. package/src/knowledge/program.ts +266 -0
  89. package/src/knowledge/read.ts +258 -27
  90. package/src/knowledge/schema.ts +199 -75
  91. package/src/knowledge/text.ts +415 -0
  92. package/src/knowledge/write.ts +740 -57
  93. package/src/mcp/main.ts +1 -1
  94. package/src/mcp/output.ts +15 -8
  95. package/src/mcp/server.ts +3 -1
  96. package/src/mcp/tools/debug.ts +97 -16
  97. package/src/mcp/tools/execution.ts +38 -5
  98. package/src/mcp/tools/machine.ts +11 -2
  99. package/src/mcp/tools/media.ts +18 -13
  100. package/src/mcp/tools/memory.ts +17 -8
  101. package/src/mcp/tools/video.ts +9 -9
  102. package/src/native/discover.ts +2 -2
  103. package/src/native/ghidra/analyze.ts +2 -2
  104. package/src/native/ghidra/index.ts +1 -1
  105. package/src/native/ghidra/language/c64rt_6510.slaspec +4 -1
  106. package/src/native/ghidra/scripts/C64Export.java +1 -1
  107. package/src/native/ghidra/scripts/C64Prepare.java +7 -1
  108. package/src/native/types.ts +14 -8
  109. package/{skills/c64-source/scripts → src}/opcodes.ts +75 -3
  110. package/src/platform.ts +70 -7
  111. package/src/project.ts +180 -3
  112. package/src/protocol/messages.ts +1 -1
  113. package/src/protocol/tools.ts +2 -16
  114. package/src/protocol/vice.ts +124 -15
  115. package/src/script.ts +46 -7
  116. package/skills/c64-assembler/scripts/compare.ts +0 -84
  117. 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 (D50). With --run, each release
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
- // one release (D51).
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 { listReferences, listRegions } from "#src/knowledge/read.ts";
19
- import { CODE_REGION_TYPES } from "#src/knowledge/schema.ts";
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 { watchRelease, type ImageState, type Start } from "./runtime.ts";
23
- import { separate } from "./separate.ts";
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 <release> [--coverage <file> ...] [--out <file>] [--max-listed <n>]
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 one release: the unpack stage that wrote each range,
36
- its access in the --coverage files (64 KiB each, repeatable), whether call and
37
- jump references in knowledge reach each code region from the entry, and what the
38
- program reads or executes outside the image. --out writes all ranges to a file;
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
- A release is a PRG, placed at its load address, or an image.json of the
42
- c64-unpacker skill, compared inside its segments. Paths are relative to the
43
- project directory. The result is one JSON object.`;
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
- const used = ["shift", "from", "to", "run", "steps"].filter((option) => values[option as keyof typeof values] !== undefined);
167
- if (used.length > 0) throw new UsageError(`separate does not take --${used.join(", --")}`);
168
- if (positionals.length !== 2) throw new UsageError("separate needs one release");
169
- return separateCommand(positionals[1]!, values.coverage ?? [], values.out, values["max-listed"]);
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
- if (values.coverage !== undefined || values.out !== undefined || values["max-listed"] !== undefined) throw new UsageError("--coverage, --out and --max-listed work only with separate");
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 (b2/04).
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 (F-24).
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
- /** separate: evidence from one release (D51), with all ranges in --out and the first ones in the result. */
280
- function separateCommand(path: string, coveragePaths: string[], out: string | undefined, maxListed: string | undefined): unknown {
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: loaded.release.segments,
298
- memory: loaded.memory,
299
- ...(loaded.entry === undefined ? {} : { entry: loaded.entry }),
300
- ...(loaded.stages === undefined ? {} : { stages: loaded.stages }),
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({ release: path, ranges, codeRegions, outside }, null, 1)}\n`), { what: "--out" });
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: "image.json has no stage log. Only a capture of c64-unpacker writes one. A capture of loads holds one part of a program and has none." }
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 (b2/08).
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
- await runScript(() => run(process.argv.slice(2)), { usage: USAGE, refusals: [KnowledgeError] });
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. Write each exception in the report.
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. Write the criteria in `report.md`. | The user gave the criteria. |
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 `unpacked/`. | `image.json` and `entry_state.json` exist, or the evidence shows no packer. |
69
- | 5. Run | c64-emulator and the run of c64-testing: run each part that the scope names. Write a coverage file for each part into `coverage/`. | Each part that the scope names has a coverage file. |
70
- | 6. Analyze | c64-static-analysis with `--coverage` for each coverage file. | No coverage conflict is open. |
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 in the scope has a name and a comment. |
72
- | 8. Generate | c64-source with `--coverage` into `src/`. For an unpacked program, add `--loader`. | `check.identical` is true. For an unpacked program, `loader.verified` is true. |
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. Report | Write `report.md`. | The report has all its sections. |
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.db` with the names, regions, comments and modules
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 and their results
86
- - `report.md`.
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
- ### The report
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
- `report.md` is short. It has these sections:
109
+ ### The answer
91
110
 
92
- 1. Acceptance: each criterion of stage 1 and its result, and each exception that the user accepted.
93
- 2. The program: what the program does, in a few sentences.
94
- 3. Modules: the file, the address range and the title of each module.
95
- 4. Untested code: all ranges in the `not_executed.json` file of c64-source. Tell why no run executed them, if you know.
96
- 5. Open questions: what the evidence does not explain.
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. It is a proposal. No script checks it. Each script takes the paths that you give.
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 and their results
111
- report.md the report
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 report, with the criteria of the user met or each exception in the report.
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.