@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 +5 -0
- package/README.md +13 -9
- package/extensions/commands.ts +1 -1
- package/extensions/tools.ts +1 -1
- package/lib/knowledge/query.ts +1 -1
- package/lib/knowledge/types.ts +1 -1
- package/lib/model/references.ts +6 -6
- package/lib/model/stats.ts +1 -1
- package/lib/render/markdown.ts +1 -1
- package/lib/search.ts +4 -4
- package/lib/store/guards.ts +2 -2
- package/lib/store/snapshots.ts +6 -5
- package/lib/usecases/code-scan.ts +13 -13
- package/lib/usecases/knowledge.ts +1 -1
- package/lib/usecases/project.ts +3 -3
- package/package.json +1 -1
- package/skills/heydb/SKILL.md +6 -6
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 `
|
|
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 (`
|
|
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
|
|
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
|
|
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
|
|
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 `
|
|
99
|
-
- **Project notes:** facts about the whole system ("one database per client; the central one is
|
|
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
|
|
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,
|
|
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
|
|
package/extensions/commands.ts
CHANGED
|
@@ -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
|
|
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
|
*/
|
package/extensions/tools.ts
CHANGED
|
@@ -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
|
|
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",
|
package/lib/knowledge/query.ts
CHANGED
|
@@ -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
|
-
*
|
|
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;
|
package/lib/knowledge/types.ts
CHANGED
|
@@ -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
|
|
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;
|
package/lib/model/references.ts
CHANGED
|
@@ -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: "
|
|
7
|
-
// columns elsewhere called
|
|
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
|
|
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 "_".
|
|
159
|
-
* ant. So "
|
|
160
|
-
* whole token, but never
|
|
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();
|
package/lib/model/stats.ts
CHANGED
|
@@ -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 ("
|
|
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);
|
package/lib/render/markdown.ts
CHANGED
|
@@ -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.
|
|
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
|
|
33
|
-
// fifty columns called
|
|
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: "
|
|
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
|
|
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) {
|
package/lib/store/guards.ts
CHANGED
|
@@ -25,8 +25,8 @@ export function assertConnectionName(name: string): void {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* Turns what a person typed ("
|
|
29
|
-
* assertConnectionName accepts ("
|
|
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
|
*/
|
package/lib/store/snapshots.ts
CHANGED
|
@@ -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
|
|
16
|
-
// JSON, which nobody reviews in a pull request.
|
|
17
|
-
// load: on 1,500 tables × 60 columns that is
|
|
18
|
-
// takes (JSON.parse and validation are the rest),
|
|
19
|
-
// copy would save little and add a second file to
|
|
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
|
|
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
|
|
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.
|
|
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. "
|
|
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 "
|
|
220
|
-
//
|
|
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 "+
|
|
228
|
-
* (PHP), `FROM ${db}.
|
|
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: '
|
|
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
|
|
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
|
|
372
|
-
//
|
|
373
|
-
//
|
|
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
|
-
//
|
|
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(
|
package/lib/usecases/project.ts
CHANGED
|
@@ -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
|
|
318
|
-
* with a column
|
|
319
|
-
* needs its table: "
|
|
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.
|
|
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",
|
package/skills/heydb/SKILL.md
CHANGED
|
@@ -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)`: `
|
|
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.
|
|
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 `
|
|
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
|
|
43
|
-
- `heydb_relate` with `to_connection` when the referenced table is in another connection of the project (`crm.clientes.cod_erp → erp`'s `
|
|
44
|
-
- A name without a dot is a table: `heydb_annotate` with `
|
|
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.
|