@pvidaal07/heydb 2.0.0-beta.0 → 2.0.0-beta.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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.0.0-beta.1]
4
+
5
+ - Documentation, the bundled skill and tests use generic examples only.
6
+ - Releases are published from GitHub Actions through npm trusted publishing.
7
+
3
8
  ## [2.0.0-beta.0]
4
9
 
5
10
  heydb is rebuilt from scratch in TypeScript as a [Pi](https://pi.dev) package. The Go CLI and MCP server (v1) are archived at tag `v1.3.1`.
package/README.md CHANGED
@@ -11,10 +11,10 @@ It is a [Pi](https://pi.dev) package: Pi gets agent tools, slash commands and a
11
11
 
12
12
  ## Why
13
13
 
14
- On a legacy database the agent guesses. A table called `gar` "looks like" guarantees and turns out to hold the articles; `estado = 3` means something only the team knows; half the joins have no foreign key. heydb gives the agent the evidence instead of the name:
14
+ On a legacy database the agent guesses. A table called `art` "looks like" artwork and turns out to hold the articles; `estado = 3` means something only the team knows; half the joins have no foreign key. heydb gives the agent the evidence instead of the name:
15
15
 
16
16
  - the structure, read-only and complete, with warnings for anything your privileges hide;
17
- - who references each table and how those columns are commented (`ref_gar` "Artículo" ×12 says more than the name `gar`);
17
+ - who references each table and how those columns are commented (`ref_art` "Artículo" ×12 says more than the name `art`);
18
18
  - the notes your team and your agent saved, each with its author and its source.
19
19
 
20
20
  The agent is told to present inferences as inferences and ask before saving them.
@@ -45,7 +45,7 @@ Then just work: ask your agent about the database. It reads the snapshot with th
45
45
 
46
46
  | Engine | Tested in CI | Notes |
47
47
  |---|---|---|
48
- | MySQL | 8.0 | 5.7 used on a real 1,500-table ERP |
48
+ | MySQL | 8.0 | 5.7 used on a schema with about 1,500 tables |
49
49
  | MariaDB | 10.6 | 10.2 or later expected |
50
50
  | PostgreSQL | 16 | 14 or later expected; one connection reaches one database, choose schemas inside it |
51
51
 
@@ -85,9 +85,9 @@ Notes saved by the agent are signed `<your git user.name> (via agent)` and end w
85
85
 
86
86
  After a sync, heydb looks for code in the project that uses the synced tables. It walks the folders, including ones git ignores (a reference copy of another repository, for example), and skips `node_modules`, `vendor`, build output, CI folders and lockfiles. It reads modern and legacy languages: PHP, TypeScript, JavaScript, Python, Java, C#, Go, Ruby, SQL, Kotlin, Scala, Harbour/Clipper, Delphi, Visual Basic, COBOL, ABL and RPG.
87
87
 
88
- A table only counts in database context: SQL with its statement verb (`SELECT … FROM gar`, `UPDATE gar … SET`, `INSERT INTO gar`), including SQL whose database prefix is built by code (`"FROM " + cEmp + ".gar"`), or a table declaration (`protected $table = 'gar'`, `cTabla := "gar"`, `@Table(name = "gar")`, `__tablename__`, `DB::table('gar')`…). A table called `config` is not matched by every comment that says "config".
88
+ A table only counts in database context: SQL with its statement verb (`SELECT … FROM art`, `UPDATE art … SET`, `INSERT INTO art`), including SQL whose database prefix is built by code (`"FROM " + cDb + ".art"`), or a table declaration (`protected $table = 'art'`, `cTabla := "art"`, `@Table(name = "art")`, `__tablename__`, `DB::table('art')`…). A table called `config` is not matched by every comment that says "config".
89
89
 
90
- If it finds such code it asks first, because reading it costs tokens. The agent then notes only what the code makes explicit (valid values, units, joins without a foreign key), stops after about 20 notes and leaves what remains unclear as questions. `/heydb:learn-code eagle ../api-eagle` reads code that lives in another folder. The scan stops after 10 seconds or 50 MB on very large codebases.
90
+ If it finds such code it asks first, because reading it costs tokens. The agent then notes only what the code makes explicit (valid values, units, joins without a foreign key), stops after about 20 notes and leaves what remains unclear as questions. `/heydb:learn-code acme ../legacy-app` reads code that lives in another folder. The scan stops after 10 seconds or 50 MB on very large codebases.
91
91
 
92
92
  Without such code, notes come from your conversations: tell the agent what a table means and it saves it.
93
93
 
@@ -95,8 +95,8 @@ Without such code, notes come from your conversations: tell the agent what a tab
95
95
 
96
96
  A project can have as many connections as it needs: `dev` and `prod`, or one per service. Commands and tools use the default unless you name one (`/heydb:sync prod`). Each connection has its own snapshot and notes.
97
97
 
98
- - **Across connections:** when `crm.clientes.cod_erp` holds codes of `erp`'s `gcl.cod`, the agent records it with `heydb_relate` and `to_connection: "erp"`, and `heydb_table gcl` on `erp` shows it.
99
- - **Project notes:** facts about the whole system ("one database per client; the central one is eagle") are saved with `scope: "project"` and shown first by `heydb_overview`.
98
+ - **Across connections:** when `crm.clientes.cod_erp` holds codes of `erp`'s `cus.cod`, the agent records it with `heydb_relate` and `to_connection: "erp"`, and `heydb_table cus` on `erp` shows it.
99
+ - **Project notes:** facts about the whole system ("one database per client; the central one is acme") are saved with `scope: "project"` and shown first by `heydb_overview`.
100
100
  - **Microservices:** give each service its own `.heydb/`; heydb uses the nearest one above the folder Pi runs in.
101
101
 
102
102
  ## Passwords
@@ -114,7 +114,7 @@ This keeps passwords out of git and out of the chat, not away from your own user
114
114
  | Path | Commit it? |
115
115
  |---|---|
116
116
  | `.heydb/config.json` | Yes: connections, with password references only |
117
- | `.heydb/snapshots/*.json.gz` | Yes: the schema, compressed (a 1,500-table ERP is about 470 KB) |
117
+ | `.heydb/snapshots/*.json.gz` | Yes: the schema, compressed (a schema with about 1,500 tables is about 470 KB) |
118
118
  | `.heydb/knowledge/` | Yes: the team's notes and relationships |
119
119
  | `.heydb/.env` | Never: local passwords |
120
120
 
@@ -131,6 +131,10 @@ If your project's `.gitignore` excludes `.heydb/`, notes stay on your machine; h
131
131
 
132
132
  Ideas and bugs are welcome in [issues](https://github.com/pvidaal07/heydb/issues).
133
133
 
134
+ ## Contributing
135
+
136
+ Contributions are welcome: open an issue, wait for `status:approved`, then fork and send a pull request to `main` that says `Closes #<issue>`. [CONTRIBUTING.md](CONTRIBUTING.md) explains the workflow, the setup and how the code is organized. Security problems go to a [private advisory](https://github.com/pvidaal07/heydb/security/advisories/new).
137
+
134
138
  ## Development
135
139
 
136
140
  ```sh
@@ -143,7 +147,7 @@ node --run db:down
143
147
  pi -e . # try the local copy in Pi
144
148
  ```
145
149
 
146
- Releases: bump `version` in `package.json`, add the entry to `CHANGELOG.md`, and push a tag `vX.Y.Z`; the publish workflow tests, publishes to npm and creates the GitHub release.
150
+ Releases (maintainers): bump `version` in `package.json`, add the entry to `CHANGELOG.md`, merge, and push a tag `vX.Y.Z` on `main`; the publish workflow tests, stages the version on npm through trusted publishing and creates the GitHub release. The version becomes public once a maintainer approves it on npmjs.com (Staged Packages) with 2FA.
147
151
 
148
152
  ## License
149
153
 
@@ -430,7 +430,7 @@ interface CodeFolder {
430
430
 
431
431
  /**
432
432
  * Same offer as after a sync, from the stored snapshot: it never connects.
433
- * The code may live elsewhere (`/heydb:learn-code eagle ../api-eagle`); a
433
+ * The code may live elsewhere (`/heydb:learn-code acme ../legacy-app`); a
434
434
  * single argument that is not a connection but an existing folder is that
435
435
  * path, with the default connection.
436
436
  */
@@ -245,7 +245,7 @@ export function createRelateTool(deps: HeydbDeps): ToolDefinition<typeof RELATE_
245
245
  promptSnippet: "Record a join between tables that has no foreign key",
246
246
  promptGuidelines: [
247
247
  "When you find tables joined without a foreign key (in code, queries or data), record it with heydb_relate and say how you know in the note.",
248
- "When the referenced table is in another connection of the project (crm.clientes.cod_erp → erp's gcl.cod), call heydb_relate from the referencing connection with to_connection.",
248
+ "When the referenced table is in another connection of the project (crm.clientes.cod_erp → erp's cus.cod), call heydb_relate from the referencing connection with to_connection.",
249
249
  ],
250
250
  parameters: RELATE_PARAMETERS,
251
251
  executionMode: "sequential",
@@ -59,7 +59,7 @@ export function knowledgeForTable(knowledge: Knowledge, tableName: string): Tabl
59
59
  /**
60
60
  * The relationships of a knowledge file that point to `table` of another
61
61
  * connection, `connection`: crm's file stores crm.clientes(cod_erp) → erp's
62
- * gcl(cod), and erp's gcl must show it.
62
+ * cus(cod), and erp's cus must show it.
63
63
  */
64
64
  export function relationshipsInto(knowledge: Knowledge, connection: string, table: string): Relationship[] {
65
65
  const into = (side: RelationshipSide) => side.connection === connection && side.table === table;
@@ -54,7 +54,7 @@ export interface Relationship {
54
54
  export interface RelationshipSide {
55
55
  /**
56
56
  * The connection the table belongs to when it is not the one whose file
57
- * stores the relationship (crm.clientes.cod_erp → erp's gcl.cod). Absent
57
+ * stores the relationship (crm.clientes.cod_erp → erp's cus.cod). Absent
58
58
  * means that connection, so files written before this field stay valid.
59
59
  */
60
60
  readonly connection?: string;
@@ -3,13 +3,13 @@ import { formatName, type ObjectRef, parseName, parseSegments } from "./names.ts
3
3
  import type { Snapshot, Table } from "./snapshot.ts";
4
4
 
5
5
  // Who points to a table. In a legacy schema this is the strongest evidence of
6
- // what a table means: "gar" has no comment and looks like "garantías", but 500
7
- // columns elsewhere called ref_gar or id_gar say "Artículo" in their comments.
6
+ // what a table means: "art" has no comment and looks like artwork, but 500
7
+ // columns elsewhere called ref_art or id_art say "Artículo" in their comments.
8
8
  //
9
9
  // Three kinds of evidence, strongest first:
10
10
  // - declared foreign keys, enforced by the engine;
11
11
  // - implicit relationships the team documented (knowledge);
12
- // - name-pattern guesses: a column called ref_gar, id_gar, cod_gar, gar_id…
12
+ // - name-pattern guesses: a column called ref_art, id_art, cod_art, art_id…
13
13
  // in another table of the same database. Nothing enforces them, so they are
14
14
  // always shown as guesses.
15
15
  //
@@ -155,9 +155,9 @@ export function incomingCounts(snapshot: Snapshot, knowledge: Knowledge): Map<st
155
155
 
156
156
  /**
157
157
  * The table names a column name may point to, lowercased: what comes before
158
- * or after any "_". ref_gar → ref, gar; cod_gar_ant → cod, cod_gar, gar_ant,
159
- * ant. So "gar" matches ref_gar, id_gar, cod_gar, gar_id and gar_precio as a
160
- * whole token, but never ref_garantia.
158
+ * or after any "_". ref_art → ref, art; cod_art_ant → cod, cod_art, art_ant,
159
+ * ant. So "art" matches ref_art, id_art, cod_art, art_id and art_precio as a
160
+ * whole token, but never ref_artista.
161
161
  */
162
162
  export function namePatternTargets(columnName: string): string[] {
163
163
  const lower = columnName.toLowerCase();
@@ -82,7 +82,7 @@ export function schemaStats(snapshot: Snapshot, knowledge: Knowledge, counts = i
82
82
  /**
83
83
  * The family a table name belongs to: the start of the name up to the first
84
84
  * "_" ("sii_fac" → "sii_") or digit ("log2020" → "log0-9"), lowercased. null
85
- * when there is no such start ("gcl", "_tmp", "2020_log").
85
+ * when there is no such start ("cus", "_tmp", "2020_log").
86
86
  */
87
87
  export function tablePrefix(name: string): string | null {
88
88
  const match = /^([^_0-9]+)([_0-9])/.exec(name);
@@ -416,7 +416,7 @@ function implicitRelationship(relationship: Relationship, tableName: string, con
416
416
  return `- ${left} → ${right} · implicit, documented by ${relationship.author}${note} [id ${relationship.id}${where} · ${day(relationship.createdAt)}]`;
417
417
  }
418
418
 
419
- /** "erp: erp.gcl(cod)" for a side in another connection than `current`, "shop.orders(id)" otherwise. */
419
+ /** "erp: erp.cus(cod)" for a side in another connection than `current`, "shop.orders(id)" otherwise. */
420
420
  function sideText(side: RelationshipSide, current?: string): string {
421
421
  const prefix = side.connection === undefined || side.connection === current ? "" : `${side.connection}: `;
422
422
  return `${prefix}${displayText(side.table)}${list(side.columns)}`;
package/lib/search.ts CHANGED
@@ -29,8 +29,8 @@ import type { Snapshot, Table } from "./model/snapshot.ts";
29
29
  // by its note (120 as a word, 30 inside one). A table name is also an exact
30
30
  // match when only a plural differs: "cliente" finds a table called clientes.
31
31
  //
32
- // Inferred meaning is how "cliente" finds gcl, a table without comment that
33
- // fifty columns called cod_gcl describe as "Cliente Habitual". It is evidence,
32
+ // Inferred meaning is how "cliente" finds cus, a table without comment that
33
+ // fifty columns called cod_cus describe as "Cliente Habitual". It is evidence,
34
34
  // not a fact: such results carry `inferred` so the agent says so. The
35
35
  // evidence is the summary heydb_table shows ("Referencing columns are
36
36
  // commented as", from incomingReferences), read for the MAX_INFERRED_TABLES
@@ -78,7 +78,7 @@ export interface SearchResults {
78
78
 
79
79
  export interface SearchOptions {
80
80
  readonly limit?: number;
81
- /** Only this database: "eagle", "eagle.eagle", or in PostgreSQL "app" or "app.public". */
81
+ /** Only this database: "acme", "acme.acme", or in PostgreSQL "app" or "app.public". */
82
82
  readonly database?: string;
83
83
  }
84
84
 
@@ -150,7 +150,7 @@ export function searchSchema(snapshot: Snapshot, knowledge: Knowledge, query: st
150
150
  };
151
151
 
152
152
  // Per database, lowercased table name → canonical names: where a column
153
- // named cod_gcl may point. Two entries when names differ only by case.
153
+ // named cod_cus may point. Two entries when names differ only by case.
154
154
  const byName = new Map<string, Map<string, string[]>>();
155
155
  const kinds = new Map<string, Table["kind"]>();
156
156
  for (const database of snapshot.databases) {
@@ -25,8 +25,8 @@ export function assertConnectionName(name: string): void {
25
25
  }
26
26
 
27
27
  /**
28
- * Turns what a person typed ("Eagle", "Prod EU", "Producción") into a name
29
- * assertConnectionName accepts ("eagle", "prod-eu", "produccion"), so the
28
+ * Turns what a person typed ("Acme", "Prod EU", "Producción") into a name
29
+ * assertConnectionName accepts ("acme", "prod-eu", "produccion"), so the
30
30
  * rule protects the files without getting in the user's way. Returns null
31
31
  * when nothing usable is left.
32
32
  */
@@ -12,11 +12,12 @@ import { HEYDB_DIR } from "./project.ts";
12
12
  // Snapshots live in <project>/.heydb/snapshots/<connection>.json.gz and are
13
13
  // meant to be committed: teammates without database credentials still get the
14
14
  // schema. The content is the same stable JSON earlier builds wrote as plain
15
- // <connection>.json, gzip-compressed: a 1,500-table ERP was 395,460 lines of
16
- // JSON, which nobody reviews in a pull request. Reading decompresses on every
17
- // load: on 1,500 tables × 60 columns that is ~13 ms of the ~75 ms a load
18
- // takes (JSON.parse and validation are the rest), so a decompressed local
19
- // copy would save little and add a second file to keep in step.
15
+ // <connection>.json, gzip-compressed: a schema with about 1,500 tables was
16
+ // about 400,000 lines of JSON, which nobody reviews in a pull request.
17
+ // Reading decompresses on every load: on 1,500 tables × 60 columns that is
18
+ // ~13 ms of the ~75 ms a load takes (JSON.parse and validation are the rest),
19
+ // so a decompressed local copy would save little and add a second file to
20
+ // keep in step.
20
21
 
21
22
  const SUPPORTED_FORMAT_VERSION = 1;
22
23
 
@@ -13,7 +13,7 @@ import { HEYDB_DIR } from "../store/project.ts";
13
13
  // and then the notes come from conversations instead.
14
14
  //
15
15
  // This only finds candidates: a table name in database context, that is SQL
16
- // (`SELECT … FROM gar`) or an ORM declaration (`protected $table = 'gar'`).
16
+ // (`SELECT … FROM art`) or an ORM declaration (`protected $table = 'art'`).
17
17
  // A name that is only mentioned (a comment, help text, a variable) does not
18
18
  // count: tables called "config" or "para" are ordinary words. It never
19
19
  // interprets the code; the agent does that, if the person agrees. The scan runs right after a sync, so it is bounded in files,
@@ -36,7 +36,7 @@ export const DEFAULT_SCAN_LIMITS: ScanLimits = {
36
36
  maxFiles: 5000,
37
37
  maxBytes: 50 * 1024 * 1024,
38
38
  maxFileBytes: 512 * 1024,
39
- // A large legacy codebase (900 Harbour files) needs about 9 s; progress is shown meanwhile.
39
+ // A large legacy codebase (about 900 source files) needs about 9 s; progress is shown meanwhile.
40
40
  maxMillis: 10_000,
41
41
  maxTables: 300,
42
42
  };
@@ -124,9 +124,9 @@ export interface ScanOptions {
124
124
  }
125
125
 
126
126
  export interface TableUsage {
127
- /** Display name, e.g. "erp.gar"; tables with the same name in several databases are joined with " / ". */
127
+ /** Display name, e.g. "erp.art"; tables with the same name in several databases are joined with " / ". */
128
128
  readonly table: string;
129
- /** The table name as the schema writes it, e.g. "gar". */
129
+ /** The table name as the schema writes it, e.g. "art". */
130
130
  readonly name: string;
131
131
  /** Files that use it most, at most 5. */
132
132
  readonly files: readonly string[];
@@ -216,21 +216,21 @@ export async function findSchemaUsage(projectRoot: string, snapshot: Snapshot, o
216
216
  }
217
217
 
218
218
  // The patterns of database context. Each captures the table name as `name`:
219
- // a whole identifier (letters, digits, "_"), so "gar" never matches in
220
- // ref_gar or garage. Keywords are case-insensitive.
219
+ // a whole identifier (letters, digits, "_"), so "art" never matches in
220
+ // ref_art or article. Keywords are case-insensitive.
221
221
 
222
222
  const ID = "[\\p{L}\\p{N}_]+";
223
223
  /** A name as SQL writes it: optionally quoted (` " ' [ ]) and qualified (db.table, db.schema.table). */
224
224
  const SQL_NAME = `(?:[\`"'\\[]?${ID}[\`"'\\]]?\\.){0,2}[\`"'\\[]?(?<name>${ID})(?![\\p{L}\\p{N}_])`;
225
225
  /**
226
226
  * A database prefix built by code in front of the table, as legacy and web
227
- * code often write it: "FROM "+cEmp+".gar" (Harbour, JS), "FROM " . $db . ".gar"
228
- * (PHP), `FROM ${db}.gar` (template strings). Bounded, so it stays cheap.
227
+ * code often write it: "FROM "+cDb+".art" (Harbour, JS), "FROM " . $db . ".art"
228
+ * (PHP), `FROM ${db}.art` (template strings). Bounded, so it stays cheap.
229
229
  */
230
230
  const BUILT_PREFIX = `(?:["'\`]\\s*(?:\\+|\\.|&|\\|\\|)\\s*[^"'\`\\n]{1,60}?\\s*(?:\\+|\\.|&|\\|\\|)\\s*["'\`]\\.|\\$\\{[^}\\n]{1,60}\\}\\.)`;
231
231
  /** A table name after a SQL keyword: plain, quoted or qualified, or behind a built prefix. */
232
232
  const SQL_TARGET = `(?:${BUILT_PREFIX})?${SQL_NAME}`;
233
- /** A quoted string that holds only the name: 'gar', "gar", `gar`. */
233
+ /** A quoted string that holds only the name: 'art', "art", `art`. */
234
234
  const QUOTED_NAME = `["'\`](?<name>${ID})["'\`]`;
235
235
  /** How far back, within one statement (no ";"), the verb that opens it may be. */
236
236
  const STATEMENT_CHARS = 2000;
@@ -257,7 +257,7 @@ const STATEMENT_CHARS = 2000;
257
257
  * A keyword match plus the context it needs. The keyword is searched first and
258
258
  * the context is checked only around each match, in a bounded window cut at
259
259
  * the statement end (";"). A lookbehind such as (?<=SELECT[^;]{0,2000}?) would
260
- * be tried at every position of the file: on a real 500 KB Harbour source that
260
+ * be tried at every position of the file: on a 500 KB legacy source file that
261
261
  * meant minutes per file.
262
262
  */
263
263
  interface ReferenceRule {
@@ -368,9 +368,9 @@ function tableNames(snapshot: Snapshot, maxTables: number): Map<string, TableNam
368
368
  */
369
369
  // The folders are walked directly, never listed through git: the code that
370
370
  // uses a database is often a reference copy kept in a folder the project's
371
- // .gitignore excludes (found on a real ERP: source-code/), and git would hide
372
- // it. Generated and third-party folders are skipped by name, and the walk is
373
- // bounded by the file and time limits.
371
+ // .gitignore excludes, and git would hide it. Generated and third-party
372
+ // folders are skipped by name, and the walk is bounded by the file and time
373
+ // limits.
374
374
  async function listSources(
375
375
  files: CodeFiles,
376
376
  maxFiles: number,
@@ -43,7 +43,7 @@ import { loadSnapshot } from "../store/snapshots.ts";
43
43
  // corrected or forgotten after a column is dropped.
44
44
  //
45
45
  // A relationship may cross connections (crm.clientes.cod_erp → erp's
46
- // gcl.cod). It is stored in the file of its "from" connection, and its "to"
46
+ // cus.cod). It is stored in the file of its "from" connection, and its "to"
47
47
  // side names the other connection and is checked against that snapshot.
48
48
 
49
49
  export function annotate(
@@ -314,9 +314,9 @@ export type TargetKind = "database" | "table" | "column";
314
314
  * every reading is tried, and the target must be exactly one object.
315
315
  *
316
316
  * A name without a dot is a table (or a database), never a column when one
317
- * of those matches: in a legacy ERP the articles table "gar" shares its name
318
- * with a column postv.gar, and "gar" must still name the table. A column then
319
- * needs its table: "postv.gar". Errors use display names.
317
+ * of those matches: in a legacy schema the articles table "art" shares its name
318
+ * with a column pos.art, and "art" must still name the table. A column then
319
+ * needs its table: "pos.art". Errors use display names.
320
320
  */
321
321
  export function resolveTarget(snapshot: Snapshot, input: string): { kind: TargetKind; name: string } {
322
322
  const segments = parseSegments(input);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pvidaal07/heydb",
3
- "version": "2.0.0-beta.0",
3
+ "version": "2.0.0-beta.1",
4
4
  "description": "Database schema context for AI agents: introspect MySQL, MariaDB and PostgreSQL, keep a local snapshot, and share annotated knowledge through git.",
5
5
  "license": "MIT",
6
6
  "author": "pvidaal07",
@@ -10,17 +10,17 @@ heydb keeps a snapshot of the project's database structure (`.heydb/snapshots/`)
10
10
  ## Read before you write SQL
11
11
 
12
12
  1. Unknown schema: `heydb_overview` once. It shows, per database, how many tables have comments and which tables the rest point to (the core).
13
- 2. Looking for a concept ("invoices", "fecha de baja"): `heydb_search`. It ignores case, accents and simple plurals, lists tables (with their matching columns) before anything else, and can keep to one `database`. Results also include tables whose meaning is inferred from the columns that reference them, marked `(inferred)`: `gcl` has no comment, but columns pointing to it say "Cliente Habitual". Present those as inferences, never as facts.
13
+ 2. Looking for a concept ("invoices", "fecha de baja"): `heydb_search`. It ignores case, accents and simple plurals, lists tables (with their matching columns) before anything else, and can keep to one `database`. Results also include tables whose meaning is inferred from the columns that reference them, marked `(inferred)`: `cus` has no comment, but columns pointing to it say "Cliente Habitual". Present those as inferences, never as facts.
14
14
  3. Before using a table: `heydb_table`. Use the real column names, types and joins it shows, including implicit relationships and notes. On wide tables it lists the 60 most telling columns; ask for `columns: "all"` or a `column_filter` when you need the rest.
15
15
 
16
16
  Never guess table or column names from memory when heydb can answer.
17
17
 
18
18
  ## Never guess a meaning from a table name
19
19
 
20
- Legacy names lie. In one ERP, `gar` looked like "garantías"; it holds the articles. Before you say what a table is, read the `heydb_table` header, strongest evidence first:
20
+ Legacy names lie. A table named `art` looks like artwork; it holds the articles. Before you say what a table is, read the `heydb_table` header, strongest evidence first:
21
21
 
22
22
  1. Team notes and the table comment.
23
- 2. Who points to it ("Referenced by"): foreign keys, documented relationships and the comments of columns like `ref_gar`, `cod_gar`. `Referencing columns are commented as: "Artículo" ×12` is strong evidence. Name-pattern guesses are not enforced; treat them as clues.
23
+ 2. Who points to it ("Referenced by"): foreign keys, documented relationships and the comments of columns like `ref_art`, `cod_art`. `Referencing columns are commented as: "Artículo" ×12` is strong evidence. Name-pattern guesses are not enforced; treat them as clues.
24
24
  3. Its own columns.
25
25
 
26
26
  When the meaning comes from 2 or 3, say it is an inference and ask the user to confirm. Once confirmed, save it with `heydb_annotate` so nobody has to guess again. When the overview says most tables have no comment, do not infer anything from names alone.
@@ -39,9 +39,9 @@ When the conversation or the code teaches you something the schema does not say,
39
39
 
40
40
  - `heydb_annotate` for meaning: valid values, units, encodings, who writes the column, legacy quirks.
41
41
  - `heydb_relate` for joins without a foreign key: `orders.customer_id → customers.id`. Say how you know in `note`.
42
- - `heydb_annotate` with `scope: "project"` and no `target` for facts above any one database: "the ERP uses one database per client; the central one is eagle". `heydb_overview` shows them first. A table called `project` is still `target: "project"`.
43
- - `heydb_relate` with `to_connection` when the referenced table is in another connection of the project (`crm.clientes.cod_erp → erp`'s `gcl.cod`). Call it from the referencing connection; `heydb_table` on the other side shows it too, prefixed with the connection.
44
- - A name without a dot is a table: `heydb_annotate` with `gar` notes the table even if some column is also called `gar`; write `postv.gar` for the column.
42
+ - `heydb_annotate` with `scope: "project"` and no `target` for facts above any one database: "the ERP uses one database per client; the central one is acme". `heydb_overview` shows them first. A table called `project` is still `target: "project"`.
43
+ - `heydb_relate` with `to_connection` when the referenced table is in another connection of the project (`crm.clientes.cod_erp → erp`'s `cus.cod`). Call it from the referencing connection; `heydb_table` on the other side shows it too, prefixed with the connection.
44
+ - A name without a dot is a table: `heydb_annotate` with `art` notes the table even if some column is also called `art`; write `pos.art` for the column.
45
45
  - If the save result says git ignores the notes, tell the user: the note stays on their machine until the project shares `.heydb/knowledge/`.
46
46
  - End every note with its source: `(code: src/Models/Order.php)` when code shows it, `(told by the user)` when the user said it. An inference is saved only after the user confirms it.
47
47
  - After `/heydb:sync` (or `/heydb:learn-code`) the user may ask you to read the project's code: follow the rules in that message and stop after about 20 notes.