@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,10 +1,12 @@
1
- // Current-state knowledge queries. A project without a database
2
- // reads as empty knowledge. Each query reads one address space (D53); the
1
+ // Current-state knowledge queries. A project without knowledge
2
+ // reads as empty knowledge. Each query reads one address space; the
3
3
  // default is the C64.
4
4
 
5
5
  import type { DatabaseSync } from "node:sqlite";
6
6
 
7
- import { DEFAULT_SPACE, type CommentPlacement, type Origin, type ReferenceKind, type RegionType, type Space, type SuppressionEntity, type SymbolKind } from "./schema.ts";
7
+ import { KnowledgeError } from "./database.ts";
8
+ import { DEFAULT_SPACE, type CommentPlacement, type OperandSelect, type Origin, type ReferenceKind, type RegionType, type Space, type SuppressionEntity, type SymbolKind } from "./schema.ts";
9
+ import { NO_REVISION } from "./text.ts";
8
10
 
9
11
  export interface SymbolRow {
10
12
  space: Space;
@@ -12,7 +14,7 @@ export interface SymbolRow {
12
14
  name: string;
13
15
  kind: SymbolKind;
14
16
  origin: Origin;
15
- revision: number;
17
+ revision: string;
16
18
  }
17
19
 
18
20
  export interface RegionRow {
@@ -20,10 +22,10 @@ export interface RegionRow {
20
22
  start: number;
21
23
  end: number;
22
24
  type: RegionType;
23
- /** Bytes per element of a table; the length is a multiple of it (D42). */
25
+ /** Bytes per element of a table; the length is a multiple of it. */
24
26
  element?: number;
25
27
  origin: Origin;
26
- revision: number;
28
+ revision: string;
27
29
  }
28
30
 
29
31
  export interface CommentRow {
@@ -32,7 +34,7 @@ export interface CommentRow {
32
34
  placement: CommentPlacement;
33
35
  text: string;
34
36
  origin: Origin;
35
- revision: number;
37
+ revision: string;
36
38
  }
37
39
 
38
40
  export interface ReferenceRow {
@@ -41,10 +43,10 @@ export interface ReferenceRow {
41
43
  to: number;
42
44
  kind: ReferenceKind;
43
45
  origin: Origin;
44
- revision: number;
46
+ revision: string;
45
47
  }
46
48
 
47
- /** A place that a user/LLM removal blocks for every analyzer (D36). `to` and `kind` are set for a reference only. */
49
+ /** A place that a user/LLM removal blocks for every analyzer. `to` and `kind` are set for a reference only. */
48
50
  export interface SuppressionRow {
49
51
  space: Space;
50
52
  entity: SuppressionEntity;
@@ -53,19 +55,76 @@ export interface SuppressionRow {
53
55
  to?: number;
54
56
  kind?: ReferenceKind;
55
57
  origin: Extract<Origin, "user" | "llm">;
56
- revision: number;
58
+ revision: string;
57
59
  }
58
60
 
59
- /** Where a file of the generated source starts (D48): it runs to the next module or the end of its segment. */
61
+ /** Where a file of the generated source starts: it runs to the next module or the end of its segment. */
60
62
  export interface ModuleRow {
61
63
  space: Space;
62
64
  start: number;
63
65
  file: string;
64
66
  title?: string;
65
67
  origin: Extract<Origin, "user" | "llm">;
66
- revision: number;
68
+ revision: string;
67
69
  }
68
70
 
71
+ /**
72
+ * How the operand of the instruction at `address` is written: a byte
73
+ * (lo, hi) or the value of a symbol plus an offset, or expression text (expr).
74
+ */
75
+ export interface OperandRow {
76
+ space: Space;
77
+ address: number;
78
+ select: OperandSelect;
79
+ symbol?: string;
80
+ /** Added to the symbol before lo or hi takes its byte; 0 for expr. */
81
+ offset: number;
82
+ expression?: string;
83
+ origin: Extract<Origin, "user" | "llm">;
84
+ revision: string;
85
+ }
86
+
87
+ /**
88
+ * Code that replaces [start, end] when the build defines the switch as 1
89
+ *. The code fits the range; NOP fills the rest. Empty code is only NOP.
90
+ */
91
+ export interface PatchRow {
92
+ space: Space;
93
+ start: number;
94
+ end: number;
95
+ switch: string;
96
+ code: string;
97
+ comment?: string;
98
+ origin: Extract<Origin, "user" | "llm">;
99
+ revision: string;
100
+ }
101
+
102
+ /** An ACME macro of the generated source. The body names a parameter with a dot in front: .dst. */
103
+ export interface MacroRow {
104
+ space: Space;
105
+ name: string;
106
+ params: string[];
107
+ body: string;
108
+ comment?: string;
109
+ origin: Extract<Origin, "user" | "llm">;
110
+ revision: string;
111
+ }
112
+
113
+ /** The instructions in [start, end] written as one use of a macro, with one expression for each parameter. */
114
+ export interface MacroUseRow {
115
+ space: Space;
116
+ start: number;
117
+ end: number;
118
+ macro: string;
119
+ args: string[];
120
+ origin: Extract<Origin, "user" | "llm">;
121
+ revision: string;
122
+ }
123
+
124
+ /** A list of params or args as the knowledge stores it: the items with ", " between them. */
125
+ export const listText = (items: readonly string[]) => items.join(", ");
126
+ const items = (text: string) => (text.trim() === "" ? [] : text.split(",").map((item) => item.trim()));
127
+
69
128
  type Raw = Record<string, unknown>;
70
129
 
71
130
  export const SYMBOL_COLUMNS = "space, address, name, kind, origin, valid_from_revision";
@@ -74,9 +133,13 @@ export const COMMENT_COLUMNS = "space, address, placement, text, origin, valid_f
74
133
  export const REFERENCE_COLUMNS = "space, from_address, to_address, kind, origin, valid_from_revision";
75
134
  export const SUPPRESSION_COLUMNS = "space, entity, start_address, end_address, to_address, kind, origin, valid_from_revision";
76
135
  export const MODULE_COLUMNS = "space, start_address, file, title, origin, valid_from_revision";
136
+ export const OPERAND_COLUMNS = "space, address, selector, symbol, offset, expression, origin, valid_from_revision";
137
+ export const PATCH_COLUMNS = "space, start_address, end_address, switch_name, code, comment, origin, valid_from_revision";
138
+ export const MACRO_COLUMNS = "space, name, params, body, comment, origin, valid_from_revision";
139
+ export const MACRO_USE_COLUMNS = "space, start_address, end_address, macro, args, origin, valid_from_revision";
77
140
 
78
141
  export function toSymbol(row: Raw): SymbolRow {
79
- return { space: row.space as Space, address: row.address as number, name: row.name as string, kind: row.kind as SymbolKind, origin: row.origin as Origin, revision: row.valid_from_revision as number };
142
+ return { space: row.space as Space, address: row.address as number, name: row.name as string, kind: row.kind as SymbolKind, origin: row.origin as Origin, revision: row.valid_from_revision as string };
80
143
  }
81
144
 
82
145
  export function toRegion(row: Raw): RegionRow {
@@ -86,7 +149,7 @@ export function toRegion(row: Raw): RegionRow {
86
149
  end: row.end_address as number,
87
150
  type: row.type as RegionType,
88
151
  origin: row.origin as Origin,
89
- revision: row.valid_from_revision as number,
152
+ revision: row.valid_from_revision as string,
90
153
  };
91
154
  if (row.element !== null && row.element !== undefined) region.element = row.element as number;
92
155
  return region;
@@ -99,12 +162,12 @@ export function toComment(row: Raw): CommentRow {
99
162
  placement: row.placement as CommentPlacement,
100
163
  text: row.text as string,
101
164
  origin: row.origin as Origin,
102
- revision: row.valid_from_revision as number,
165
+ revision: row.valid_from_revision as string,
103
166
  };
104
167
  }
105
168
 
106
169
  export function toReference(row: Raw): ReferenceRow {
107
- return { space: row.space as Space, from: row.from_address as number, to: row.to_address as number, kind: row.kind as ReferenceKind, origin: row.origin as Origin, revision: row.valid_from_revision as number };
170
+ return { space: row.space as Space, from: row.from_address as number, to: row.to_address as number, kind: row.kind as ReferenceKind, origin: row.origin as Origin, revision: row.valid_from_revision as string };
108
171
  }
109
172
 
110
173
  export function toSuppression(row: Raw): SuppressionRow {
@@ -114,7 +177,7 @@ export function toSuppression(row: Raw): SuppressionRow {
114
177
  start: row.start_address as number,
115
178
  end: row.end_address as number,
116
179
  origin: row.origin as SuppressionRow["origin"],
117
- revision: row.valid_from_revision as number,
180
+ revision: row.valid_from_revision as string,
118
181
  };
119
182
  if (row.to_address !== null) suppression.to = row.to_address as number;
120
183
  if (row.kind !== null) suppression.kind = row.kind as ReferenceKind;
@@ -122,14 +185,80 @@ export function toSuppression(row: Raw): SuppressionRow {
122
185
  }
123
186
 
124
187
  export function toModule(row: Raw): ModuleRow {
125
- const module: ModuleRow = { space: row.space as Space, start: row.start_address as number, file: row.file as string, origin: row.origin as ModuleRow["origin"], revision: row.valid_from_revision as number };
188
+ const module: ModuleRow = { space: row.space as Space, start: row.start_address as number, file: row.file as string, origin: row.origin as ModuleRow["origin"], revision: row.valid_from_revision as string };
126
189
  if (row.title !== null && row.title !== undefined) module.title = row.title as string;
127
190
  return module;
128
191
  }
129
192
 
130
- export function currentRevision(db: DatabaseSync | undefined): number {
131
- if (db === undefined) return 0;
132
- return Number((db.prepare("SELECT value FROM meta WHERE key = 'current_revision'").get() as { value: string }).value);
193
+ export function toOperand(row: Raw): OperandRow {
194
+ const operand: OperandRow = {
195
+ space: row.space as Space,
196
+ address: row.address as number,
197
+ select: row.selector as OperandSelect,
198
+ offset: row.offset as number,
199
+ origin: row.origin as OperandRow["origin"],
200
+ revision: row.valid_from_revision as string,
201
+ };
202
+ if (row.symbol !== null && row.symbol !== undefined) operand.symbol = row.symbol as string;
203
+ if (row.expression !== null && row.expression !== undefined) operand.expression = row.expression as string;
204
+ return operand;
205
+ }
206
+
207
+ export function toPatch(row: Raw): PatchRow {
208
+ const patch: PatchRow = {
209
+ space: row.space as Space,
210
+ start: row.start_address as number,
211
+ end: row.end_address as number,
212
+ switch: row.switch_name as string,
213
+ code: row.code as string,
214
+ origin: row.origin as PatchRow["origin"],
215
+ revision: row.valid_from_revision as string,
216
+ };
217
+ if (row.comment !== null && row.comment !== undefined) patch.comment = row.comment as string;
218
+ return patch;
219
+ }
220
+
221
+ export function toMacro(row: Raw): MacroRow {
222
+ const macro: MacroRow = { space: row.space as Space, name: row.name as string, params: items(row.params as string), body: row.body as string, origin: row.origin as MacroRow["origin"], revision: row.valid_from_revision as string };
223
+ if (row.comment !== null && row.comment !== undefined) macro.comment = row.comment as string;
224
+ return macro;
225
+ }
226
+
227
+ export function toMacroUse(row: Raw): MacroUseRow {
228
+ return {
229
+ space: row.space as Space,
230
+ start: row.start_address as number,
231
+ end: row.end_address as number,
232
+ macro: row.macro as string,
233
+ args: items(row.args as string),
234
+ origin: row.origin as MacroUseRow["origin"],
235
+ revision: row.valid_from_revision as string,
236
+ };
237
+ }
238
+
239
+ /** The id of the newest revision, or "0" when the knowledge has none. */
240
+ export function currentRevision(db: DatabaseSync | undefined): string {
241
+ if (db === undefined) return "0";
242
+ return (db.prepare("SELECT max(id) AS id FROM revisions").get() as { id: string | null }).id ?? "0";
243
+ }
244
+
245
+ /** At most this many ids are named when a start of an id fits several revisions. */
246
+ const NAMED_REVISIONS = 5;
247
+
248
+ /**
249
+ * The revision that `text` names: "0" (no revision), a full revision id,
250
+ * or a start of an id that only one revision has.
251
+ */
252
+ export function resolveRevision(db: DatabaseSync | undefined, text: string): string {
253
+ if (text === NO_REVISION) return NO_REVISION;
254
+ if (!/^[0-9a-zT.-]+$/.test(text)) throw new KnowledgeError("invalid-input", `${JSON.stringify(text)} is not a revision id. A revision id looks like 20261015T214501.123-k3f9qz; its start is enough when only one revision has it.`);
255
+ const found = (db?.prepare("SELECT id FROM revisions WHERE substr(id, 1, ?) = ? ORDER BY id LIMIT ?").all(text.length, text, NAMED_REVISIONS + 1) ?? []) as Array<{ id: string }>;
256
+ if (found.length === 0) throw new KnowledgeError("not-found", `There is no revision ${text}.`);
257
+ if (found.length > 1) {
258
+ const named = found.slice(0, NAMED_REVISIONS).map((row) => row.id).join(", ");
259
+ throw new KnowledgeError("invalid-input", `${text} is the start of more than one revision: ${named}${found.length > NAMED_REVISIONS ? ", ..." : ""}. Give more of the id.`);
260
+ }
261
+ return found[0]!.id;
133
262
  }
134
263
 
135
264
  export function symbolAt(db: DatabaseSync | undefined, address: number, space: Space = DEFAULT_SPACE): SymbolRow | undefined {
@@ -142,6 +271,31 @@ export function symbolNamed(db: DatabaseSync | undefined, name: string, space: S
142
271
  return row === undefined ? undefined : toSymbol(row);
143
272
  }
144
273
 
274
+ /** The analyzer names at an address: one for each analyzer, in the order heuristic, Ghidra, DXA. */
275
+ export function analyzerNamesAt(db: DatabaseSync | undefined, address: number, space: Space = DEFAULT_SPACE): SymbolRow[] {
276
+ if (db === undefined) return [];
277
+ return (db.prepare(`SELECT ${SYMBOL_COLUMNS} FROM current_analyzer_names WHERE space = ? AND address = ?`).all(space, address) as Raw[]).map(toSymbol).sort(byAnalyzer);
278
+ }
279
+
280
+ /** The analyzer names with this name: more than one when two analyzers gave it to different addresses. */
281
+ export function analyzerNamed(db: DatabaseSync | undefined, name: string, space: Space = DEFAULT_SPACE): SymbolRow[] {
282
+ if (db === undefined) return [];
283
+ return (db.prepare(`SELECT ${SYMBOL_COLUMNS} FROM current_analyzer_names WHERE space = ? AND name = ?`).all(space, name) as Raw[]).map(toSymbol).sort(byAnalyzer);
284
+ }
285
+
286
+ /** The analyzers in the order that their names are used when no other name exists. */
287
+ export const ANALYZER_NAME_ORDER: readonly Origin[] = ["heuristic", "ghidra", "dxa"];
288
+ const byAnalyzer = (a: SymbolRow, b: SymbolRow) => a.address - b.address || ANALYZER_NAME_ORDER.indexOf(a.origin) - ANALYZER_NAME_ORDER.indexOf(b.origin);
289
+
290
+ /** Current analyzer names in a range, in address order and then heuristic, Ghidra, DXA. */
291
+ export function listAnalyzerNames(db: DatabaseSync | undefined, range: Range = ALL, kind?: SymbolKind, origins?: OriginFilter, space: Space = DEFAULT_SPACE): SymbolRow[] {
292
+ if (db === undefined) return [];
293
+ return (db.prepare(`SELECT ${SYMBOL_COLUMNS} FROM current_analyzer_names WHERE space = ? AND address BETWEEN ? AND ?`).all(space, range.start, range.end) as Raw[])
294
+ .map(toSymbol)
295
+ .filter((row) => (kind === undefined || row.kind === kind) && kept(origins)(row))
296
+ .sort(byAnalyzer);
297
+ }
298
+
145
299
  /** Current regions that share at least one address with [start, end], in address order. */
146
300
  export function regionsOverlapping(db: DatabaseSync | undefined, start: number, end: number, space: Space = DEFAULT_SPACE): RegionRow[] {
147
301
  if (db === undefined) return [];
@@ -213,19 +367,55 @@ export function moduleWithFile(db: DatabaseSync | undefined, file: string, space
213
367
  return row === undefined ? undefined : toModule(row);
214
368
  }
215
369
 
370
+ export function operandAt(db: DatabaseSync | undefined, address: number, space: Space = DEFAULT_SPACE): OperandRow | undefined {
371
+ const row = db?.prepare(`SELECT ${OPERAND_COLUMNS} FROM current_operands WHERE space = ? AND address = ?`).get(space, address) as Raw | undefined;
372
+ return row === undefined ? undefined : toOperand(row);
373
+ }
374
+
375
+ /** Current patches that share at least one address with [start, end], in address order. */
376
+ export function patchesOverlapping(db: DatabaseSync | undefined, start: number, end: number, space: Space = DEFAULT_SPACE): PatchRow[] {
377
+ if (db === undefined) return [];
378
+ return db
379
+ .prepare(`SELECT ${PATCH_COLUMNS} FROM current_patches WHERE space = ? AND start_address <= ? AND end_address >= ? ORDER BY start_address`)
380
+ .all(space, end, start)
381
+ .map((row) => toPatch(row as Raw));
382
+ }
383
+
384
+ /** Current macro uses that share at least one address with [start, end], in address order. */
385
+ export function macroUsesOverlapping(db: DatabaseSync | undefined, start: number, end: number, space: Space = DEFAULT_SPACE): MacroUseRow[] {
386
+ if (db === undefined) return [];
387
+ return db
388
+ .prepare(`SELECT ${MACRO_USE_COLUMNS} FROM current_macro_uses WHERE space = ? AND start_address <= ? AND end_address >= ? ORDER BY start_address`)
389
+ .all(space, end, start)
390
+ .map((row) => toMacroUse(row as Raw));
391
+ }
392
+
393
+ export function macroNamed(db: DatabaseSync | undefined, name: string, space: Space = DEFAULT_SPACE): MacroRow | undefined {
394
+ const row = db?.prepare(`SELECT ${MACRO_COLUMNS} FROM current_macros WHERE space = ? AND name = ?`).get(space, name) as Raw | undefined;
395
+ return row === undefined ? undefined : toMacro(row);
396
+ }
397
+
216
398
  /** Everything currently known at one address: the main lookup for skills. */
217
399
  export interface AddressKnowledge {
218
400
  space: Space;
219
401
  address: number;
220
402
  symbol?: SymbolRow;
403
+ /** The second names that analyzers gave the address. */
404
+ analyzerNames: SymbolRow[];
221
405
  /** The current region that contains the address. */
222
406
  region?: RegionRow;
223
407
  comments: CommentRow[];
224
408
  referencesFrom: ReferenceRow[];
225
409
  referencesTo: ReferenceRow[];
410
+ /** How the operand of the instruction here is written. */
411
+ operand?: OperandRow;
412
+ /** The patch whose range holds the address. */
413
+ patch?: PatchRow;
414
+ /** The macro use whose range holds the address. */
415
+ macroUse?: MacroUseRow;
226
416
  /** What user/LLM removals block here for the analyzers. */
227
417
  suppressions: SuppressionRow[];
228
- revision: number;
418
+ revision: string;
229
419
  }
230
420
 
231
421
  export function at(db: DatabaseSync | undefined, address: number, space: Space = DEFAULT_SPACE): AddressKnowledge {
@@ -235,6 +425,7 @@ export function at(db: DatabaseSync | undefined, address: number, space: Space =
235
425
  comments: commentsAt(db, address, space),
236
426
  referencesFrom: referencesFrom(db, address, space),
237
427
  referencesTo: referencesTo(db, address, space),
428
+ analyzerNames: analyzerNamesAt(db, address, space),
238
429
  suppressions: suppressionsAt(db, address, space),
239
430
  revision: currentRevision(db),
240
431
  };
@@ -242,6 +433,12 @@ export function at(db: DatabaseSync | undefined, address: number, space: Space =
242
433
  if (symbol !== undefined) result.symbol = symbol;
243
434
  const [region] = regionsOverlapping(db, address, address, space);
244
435
  if (region !== undefined) result.region = region;
436
+ const operand = operandAt(db, address, space);
437
+ if (operand !== undefined) result.operand = operand;
438
+ const [patch] = patchesOverlapping(db, address, address, space);
439
+ if (patch !== undefined) result.patch = patch;
440
+ const [macroUse] = macroUsesOverlapping(db, address, address, space);
441
+ if (macroUse !== undefined) result.macroUse = macroUse;
245
442
  return result;
246
443
  }
247
444
 
@@ -252,7 +449,7 @@ export interface Range {
252
449
 
253
450
  const ALL: Range = { start: 0x0000, end: 0xffff };
254
451
 
255
- /** Rows of these origins only; undefined keeps every origin (D42). */
452
+ /** Rows of these origins only; undefined keeps every origin. */
256
453
  export type OriginFilter = readonly Origin[] | undefined;
257
454
  const kept = (origins: OriginFilter) => (row: { origin: Origin }) => origins === undefined || origins.includes(row.origin);
258
455
 
@@ -312,12 +509,42 @@ export function listModules(db: DatabaseSync | undefined, range: Range = ALL, or
312
509
  .filter(kept(origins));
313
510
  }
314
511
 
512
+ /** Current operand expressions of instructions in the range, in address order. */
513
+ export function listOperands(db: DatabaseSync | undefined, range: Range = ALL, origins?: OriginFilter, space: Space = DEFAULT_SPACE): OperandRow[] {
514
+ if (db === undefined) return [];
515
+ return db
516
+ .prepare(`SELECT ${OPERAND_COLUMNS} FROM current_operands WHERE space = ? AND address BETWEEN ? AND ? ORDER BY address`)
517
+ .all(space, range.start, range.end)
518
+ .map((row) => toOperand(row as Raw))
519
+ .filter(kept(origins));
520
+ }
521
+
522
+ /** Current patches that share an address with the range, of one switch or of all, in address order. */
523
+ export function listPatches(db: DatabaseSync | undefined, range: Range = ALL, switchName?: string, origins?: OriginFilter, space: Space = DEFAULT_SPACE): PatchRow[] {
524
+ return patchesOverlapping(db, range.start, range.end, space).filter((patch) => (switchName === undefined || patch.switch === switchName) && kept(origins)(patch));
525
+ }
526
+
527
+ /** Current macros, by name. */
528
+ export function listMacros(db: DatabaseSync | undefined, origins?: OriginFilter, space: Space = DEFAULT_SPACE): MacroRow[] {
529
+ if (db === undefined) return [];
530
+ return db
531
+ .prepare(`SELECT ${MACRO_COLUMNS} FROM current_macros WHERE space = ? ORDER BY name`)
532
+ .all(space)
533
+ .map((row) => toMacro(row as Raw))
534
+ .filter(kept(origins));
535
+ }
536
+
537
+ /** Current macro uses that share an address with the range, of one macro or of all, in address order. */
538
+ export function listMacroUses(db: DatabaseSync | undefined, range: Range = ALL, macro?: string, origins?: OriginFilter, space: Space = DEFAULT_SPACE): MacroUseRow[] {
539
+ return macroUsesOverlapping(db, range.start, range.end, space).filter((use) => (macro === undefined || use.macro === macro) && kept(origins)(use));
540
+ }
541
+
315
542
  /** The spaces that hold current knowledge, the C64 first. */
316
543
  export function listSpaces(db: DatabaseSync | undefined): Space[] {
317
544
  if (db === undefined) return [];
318
545
  const rows = db
319
546
  .prepare(
320
- `SELECT space FROM current_symbols UNION SELECT space FROM current_regions UNION SELECT space FROM current_comments UNION SELECT space FROM current_references UNION SELECT space FROM current_suppressions UNION SELECT space FROM current_modules`,
547
+ `SELECT space FROM current_symbols UNION SELECT space FROM current_analyzer_names UNION SELECT space FROM current_regions UNION SELECT space FROM current_comments UNION SELECT space FROM current_references UNION SELECT space FROM current_suppressions UNION SELECT space FROM current_modules UNION SELECT space FROM current_operands UNION SELECT space FROM current_patches UNION SELECT space FROM current_macros UNION SELECT space FROM current_macro_uses UNION SELECT space FROM current_memory UNION SELECT space FROM current_entries UNION SELECT space FROM current_coverage`,
321
548
  )
322
549
  .all() as Array<{ space: Space }>;
323
550
  const order = (space: Space) => (space === "c64" ? -2 : space === "drive8" ? -1 : Number(space.slice(4)));
@@ -325,8 +552,8 @@ export function listSpaces(db: DatabaseSync | undefined): Space[] {
325
552
  }
326
553
 
327
554
  /** Case-insensitive text search in current symbol names and comment texts of one space. */
328
- export function search(db: DatabaseSync | undefined, text: string, limit = 50, origins?: OriginFilter, space: Space = DEFAULT_SPACE): { symbols: SymbolRow[]; comments: CommentRow[] } {
329
- if (db === undefined || text.length === 0) return { symbols: [], comments: [] };
555
+ export function search(db: DatabaseSync | undefined, text: string, limit = 50, origins?: OriginFilter, space: Space = DEFAULT_SPACE): { symbols: SymbolRow[]; analyzerNames: SymbolRow[]; comments: CommentRow[] } {
556
+ if (db === undefined || text.length === 0) return { symbols: [], analyzerNames: [], comments: [] };
330
557
  const pattern = `%${text.replace(/[\\%_]/g, (character) => `\\${character}`)}%`;
331
558
  const only = origins === undefined ? "" : ` AND origin IN (${origins.map(() => "?").join(", ")})`;
332
559
  const values = origins ?? [];
@@ -334,9 +561,13 @@ export function search(db: DatabaseSync | undefined, text: string, limit = 50, o
334
561
  .prepare(`SELECT ${SYMBOL_COLUMNS} FROM current_symbols WHERE space = ? AND name LIKE ? ESCAPE '\\'${only} ORDER BY address LIMIT ?`)
335
562
  .all(space, pattern, ...values, limit)
336
563
  .map((row) => toSymbol(row as Raw));
564
+ const analyzerNames = db
565
+ .prepare(`SELECT ${SYMBOL_COLUMNS} FROM current_analyzer_names WHERE space = ? AND name LIKE ? ESCAPE '\\'${only} ORDER BY address LIMIT ?`)
566
+ .all(space, pattern, ...values, limit)
567
+ .map((row) => toSymbol(row as Raw));
337
568
  const comments = db
338
569
  .prepare(`SELECT ${COMMENT_COLUMNS} FROM current_comments WHERE space = ? AND text LIKE ? ESCAPE '\\'${only} ORDER BY address, placement LIMIT ?`)
339
570
  .all(space, pattern, ...values, limit)
340
571
  .map((row) => toComment(row as Raw));
341
- return { symbols, comments };
572
+ return { symbols, analyzerNames, comments };
342
573
  }