brito-cli 0.5.0 → 0.5.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/dist/graph/indexer.js +7 -1
- package/dist/graph/langs.js +7 -1
- package/dist/graph/store.js +82 -23
- package/package.json +1 -1
package/dist/graph/indexer.js
CHANGED
|
@@ -38,7 +38,13 @@ function extract(root, lang, file) {
|
|
|
38
38
|
const bindings = [];
|
|
39
39
|
const isPy = lang === "python";
|
|
40
40
|
const mk = (name, kind, n) => {
|
|
41
|
-
const
|
|
41
|
+
const id = `${file}::${name}:${n.startPosition.row + 1}`;
|
|
42
|
+
const dup = syms.find((x) => x.id === id); // getter+setter (ou overloads) na mesma linha: id único
|
|
43
|
+
if (dup) {
|
|
44
|
+
dup.endLine = Math.max(dup.endLine, n.endPosition.row + 1);
|
|
45
|
+
return dup;
|
|
46
|
+
}
|
|
47
|
+
const s = { id, name, kind, file, startLine: n.startPosition.row + 1, endLine: n.endPosition.row + 1 };
|
|
42
48
|
syms.push(s);
|
|
43
49
|
return s;
|
|
44
50
|
};
|
package/dist/graph/langs.js
CHANGED
|
@@ -373,7 +373,13 @@ export function extractLang(root, lang, file) {
|
|
|
373
373
|
const syms = [], calls = [];
|
|
374
374
|
const acc = { imports: [], bindings: [] };
|
|
375
375
|
const mk = (name, kind, n) => {
|
|
376
|
-
const
|
|
376
|
+
const id = `${file}::${name}:${n.startPosition.row + 1}`;
|
|
377
|
+
const dup = syms.find((x) => x.id === id); // getter+setter (ou overloads) na mesma linha: id único
|
|
378
|
+
if (dup) {
|
|
379
|
+
dup.endLine = Math.max(dup.endLine, n.endPosition.row + 1);
|
|
380
|
+
return dup;
|
|
381
|
+
}
|
|
382
|
+
const s = { id, name, kind, file, startLine: n.startPosition.row + 1, endLine: n.endPosition.row + 1 };
|
|
377
383
|
syms.push(s);
|
|
378
384
|
return s;
|
|
379
385
|
};
|
package/dist/graph/store.js
CHANGED
|
@@ -15,9 +15,8 @@ async function drain(r, read = true) {
|
|
|
15
15
|
/** Outro processo do Brito está com o banco aberto: o Kùzu só deixa um processo por projeto. */
|
|
16
16
|
export class GraphLockedError extends Error {
|
|
17
17
|
constructor(file) {
|
|
18
|
-
super(`
|
|
19
|
-
"
|
|
20
|
-
"Feche o outro, ou espere o turno em andamento terminar, e tente de novo.");
|
|
18
|
+
super(`Outro processo do Brito está gravando no grafo deste projeto (${file}) há mais tempo que o esperado.\n` +
|
|
19
|
+
"As sessões soltam o banco quando ficam ociosas; uma indexação longa em outro terminal pode segurá-lo. Espere ela terminar e tente de novo.");
|
|
21
20
|
this.name = "GraphLockedError";
|
|
22
21
|
}
|
|
23
22
|
}
|
|
@@ -25,35 +24,82 @@ const isLockError = (e) => /could not set lock/i.test(String(e?.message ?? e));
|
|
|
25
24
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
26
25
|
/** Quanto esperar o banco liberar antes de desistir: cobre sobreposições curtas (a reflexão de skills e o turno, por exemplo). */
|
|
27
26
|
const LOCK_WAIT_MS = 4000;
|
|
27
|
+
/** Tempo ocioso até soltar o banco: o lock só existe enquanto há consultas em andamento. */
|
|
28
|
+
const IDLE_MS = 300;
|
|
29
|
+
/** Cypher que altera o banco precisa do modo escrita; o resto abre somente leitura (vários processos em paralelo). */
|
|
30
|
+
const WRITE_RE = /\b(CREATE|MERGE|SET|DELETE|REMOVE|DROP|ALTER|COPY|LOAD|INSTALL|ATTACH|DETACH)\b/i;
|
|
28
31
|
export class GraphStore {
|
|
29
|
-
db;
|
|
30
|
-
conn;
|
|
32
|
+
db = null;
|
|
33
|
+
conn = null;
|
|
34
|
+
mode = null;
|
|
35
|
+
idle = null;
|
|
36
|
+
migrated = false;
|
|
31
37
|
queue = Promise.resolve();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
file;
|
|
39
|
+
waitMs;
|
|
40
|
+
constructor(file, waitMs) {
|
|
41
|
+
this.file = file;
|
|
42
|
+
this.waitMs = waitMs;
|
|
35
43
|
}
|
|
36
44
|
static async open(root, waitMs = LOCK_WAIT_MS) {
|
|
37
45
|
const dir = path.join(root, ".brito");
|
|
38
46
|
fs.mkdirSync(dir, { recursive: true });
|
|
39
|
-
const
|
|
40
|
-
|
|
47
|
+
const store = new GraphStore(path.join(dir, "graph.kuzu"), waitMs);
|
|
48
|
+
await store.run("RETURN 1 AS ok", {}, true); // cria o banco e migra; solta o lock logo em seguida
|
|
49
|
+
return store;
|
|
50
|
+
}
|
|
51
|
+
/** Abre (ou promove) o banco no modo pedido, esperando o lock de outro processo. Roda dentro da fila. */
|
|
52
|
+
async acquire(write) {
|
|
53
|
+
if (this.idle) {
|
|
54
|
+
clearTimeout(this.idle);
|
|
55
|
+
this.idle = null;
|
|
56
|
+
}
|
|
57
|
+
if (this.db && (this.mode === "w" || !write))
|
|
58
|
+
return;
|
|
59
|
+
await this.release(); // leitura -> escrita: fecha antes de reabrir
|
|
60
|
+
const deadline = Date.now() + this.waitMs;
|
|
41
61
|
for (;;) {
|
|
42
62
|
try {
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
63
|
+
const ro = !write && fs.existsSync(this.file) && this.migrated;
|
|
64
|
+
const db = ro ? new kuzu.Database(this.file, 0, true, true) : new kuzu.Database(this.file);
|
|
65
|
+
this.db = db;
|
|
66
|
+
this.conn = new kuzu.Connection(db);
|
|
67
|
+
this.mode = ro ? "r" : "w";
|
|
68
|
+
if (!ro && !this.migrated) {
|
|
69
|
+
await this.migrate();
|
|
70
|
+
this.migrated = true;
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
47
73
|
}
|
|
48
74
|
catch (e) {
|
|
75
|
+
await this.release();
|
|
49
76
|
if (!isLockError(e))
|
|
50
77
|
throw e;
|
|
51
78
|
if (Date.now() >= deadline)
|
|
52
|
-
throw new GraphLockedError(file);
|
|
53
|
-
await sleep(
|
|
79
|
+
throw new GraphLockedError(this.file);
|
|
80
|
+
await sleep(100 + Math.random() * 150);
|
|
54
81
|
}
|
|
55
82
|
}
|
|
56
83
|
}
|
|
84
|
+
async release() {
|
|
85
|
+
const { conn, db } = this;
|
|
86
|
+
this.conn = this.db = null;
|
|
87
|
+
this.mode = null;
|
|
88
|
+
try {
|
|
89
|
+
await conn?.close();
|
|
90
|
+
}
|
|
91
|
+
catch { /* já fechado */ }
|
|
92
|
+
try {
|
|
93
|
+
await db?.close();
|
|
94
|
+
}
|
|
95
|
+
catch { /* já fechado */ }
|
|
96
|
+
}
|
|
97
|
+
touch() {
|
|
98
|
+
if (this.idle)
|
|
99
|
+
clearTimeout(this.idle);
|
|
100
|
+
this.idle = setTimeout(() => { this.queue = this.queue.then(() => this.release()).catch(() => undefined); }, IDLE_MS);
|
|
101
|
+
this.idle.unref();
|
|
102
|
+
}
|
|
57
103
|
async migrate() {
|
|
58
104
|
const ddl = [
|
|
59
105
|
"CREATE NODE TABLE IF NOT EXISTS File(path STRING, lang STRING, hash STRING, calls STRING[], imports STRING[], bindings STRING[], PRIMARY KEY(path))",
|
|
@@ -70,19 +116,28 @@ export class GraphStore {
|
|
|
70
116
|
await drain(await this.conn.query(`ALTER TABLE File ADD IF NOT EXISTS ${col} STRING[] DEFAULT []`), false);
|
|
71
117
|
}
|
|
72
118
|
/** Serializa as queries: leituras paralelas de subagentes dividem uma única Connection. */
|
|
73
|
-
run(cypher, params = {}) {
|
|
74
|
-
const next = this.queue.then(() =>
|
|
119
|
+
run(cypher, params = {}, forceWrite = false) {
|
|
120
|
+
const next = this.queue.then(async () => {
|
|
121
|
+
await this.acquire(forceWrite || WRITE_RE.test(cypher));
|
|
122
|
+
try {
|
|
123
|
+
return await this.exec(cypher, params);
|
|
124
|
+
}
|
|
125
|
+
finally {
|
|
126
|
+
this.touch();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
75
129
|
this.queue = next.catch(() => undefined);
|
|
76
130
|
return next;
|
|
77
131
|
}
|
|
78
132
|
async exec(cypher, params) {
|
|
133
|
+
const conn = this.conn;
|
|
79
134
|
if (Object.keys(params).length === 0) {
|
|
80
|
-
return drain(await
|
|
135
|
+
return drain(await conn.query(cypher));
|
|
81
136
|
}
|
|
82
|
-
const ps = await
|
|
137
|
+
const ps = await conn.prepare(cypher);
|
|
83
138
|
if (!ps.isSuccess())
|
|
84
139
|
throw new Error(ps.getErrorMessage());
|
|
85
|
-
return drain(await
|
|
140
|
+
return drain(await conn.execute(ps, params));
|
|
86
141
|
}
|
|
87
142
|
/** Apaga só o grafo de código; notas (memória) são preservadas. */
|
|
88
143
|
async clearCode() {
|
|
@@ -90,7 +145,11 @@ export class GraphStore {
|
|
|
90
145
|
await this.run("MATCH (f:File) DETACH DELETE f");
|
|
91
146
|
}
|
|
92
147
|
async close() {
|
|
93
|
-
await this.
|
|
94
|
-
|
|
148
|
+
await this.queue.catch(() => undefined);
|
|
149
|
+
if (this.idle) {
|
|
150
|
+
clearTimeout(this.idle);
|
|
151
|
+
this.idle = null;
|
|
152
|
+
}
|
|
153
|
+
await this.release();
|
|
95
154
|
}
|
|
96
155
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brito-cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Coding-agent CLI with a code knowledge graph, memory and reviewable skills / CLI de agente de código com grafo de conhecimento, memória e skills revisáveis",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|