@deeplake/hivemind 0.7.75 → 0.7.77

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.
@@ -0,0 +1,3148 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // dist/src/index-marker-store.js
12
+ var index_marker_store_exports = {};
13
+ __export(index_marker_store_exports, {
14
+ buildIndexMarkerPath: () => buildIndexMarkerPath,
15
+ getIndexMarkerDir: () => getIndexMarkerDir,
16
+ hasFreshIndexMarker: () => hasFreshIndexMarker,
17
+ writeIndexMarker: () => writeIndexMarker
18
+ });
19
+ import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
20
+ import { join as join8 } from "node:path";
21
+ import { tmpdir } from "node:os";
22
+ function getIndexMarkerDir() {
23
+ return process.env.HIVEMIND_INDEX_MARKER_DIR ?? join8(tmpdir(), "hivemind-deeplake-indexes");
24
+ }
25
+ function buildIndexMarkerPath(workspaceId, orgId, table, suffix) {
26
+ const markerKey = [workspaceId, orgId, table, suffix].join("__").replace(/[^a-zA-Z0-9_.-]/g, "_");
27
+ return join8(getIndexMarkerDir(), `${markerKey}.json`);
28
+ }
29
+ function hasFreshIndexMarker(markerPath) {
30
+ if (!existsSync4(markerPath))
31
+ return false;
32
+ try {
33
+ const raw = JSON.parse(readFileSync7(markerPath, "utf-8"));
34
+ const updatedAt = raw.updatedAt ? new Date(raw.updatedAt).getTime() : NaN;
35
+ if (!Number.isFinite(updatedAt) || Date.now() - updatedAt > INDEX_MARKER_TTL_MS)
36
+ return false;
37
+ return true;
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+ function writeIndexMarker(markerPath) {
43
+ mkdirSync5(getIndexMarkerDir(), { recursive: true });
44
+ writeFileSync5(markerPath, JSON.stringify({ updatedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf-8");
45
+ }
46
+ var INDEX_MARKER_TTL_MS;
47
+ var init_index_marker_store = __esm({
48
+ "dist/src/index-marker-store.js"() {
49
+ "use strict";
50
+ INDEX_MARKER_TTL_MS = Number(process.env.HIVEMIND_INDEX_MARKER_TTL_MS ?? 6 * 60 * 6e4);
51
+ }
52
+ });
53
+
54
+ // dist/src/hooks/graph-on-stop.js
55
+ import { execFileSync as execFileSync3 } from "node:child_process";
56
+ import { createHash as createHash7 } from "node:crypto";
57
+ import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync13 } from "node:fs";
58
+ import { join as join18 } from "node:path";
59
+
60
+ // dist/src/commands/graph.js
61
+ import { execSync as execSync2 } from "node:child_process";
62
+ import { readFileSync as readFileSync13, readdirSync } from "node:fs";
63
+ import { join as join16, relative, resolve as resolve4, sep } from "node:path";
64
+ import { createHash as createHash6 } from "node:crypto";
65
+
66
+ // dist/src/cli/version.js
67
+ import { readFileSync as readFileSync2 } from "node:fs";
68
+ import { join as join2 } from "node:path";
69
+
70
+ // dist/src/cli/util.js
71
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync, symlinkSync, unlinkSync, lstatSync } from "node:fs";
72
+ import { join, dirname } from "node:path";
73
+ import { homedir } from "node:os";
74
+ import { fileURLToPath } from "node:url";
75
+ import { createInterface } from "node:readline";
76
+ var HOME = homedir();
77
+ function pkgRoot() {
78
+ let dir = fileURLToPath(new URL(".", import.meta.url));
79
+ for (let i = 0; i < 8; i++) {
80
+ try {
81
+ const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf-8"));
82
+ if (pkg.name === "@deeplake/hivemind" || pkg.name === "hivemind")
83
+ return dir;
84
+ } catch {
85
+ }
86
+ const parent = dirname(dir);
87
+ if (parent === dir)
88
+ break;
89
+ dir = parent;
90
+ }
91
+ return fileURLToPath(new URL("..", import.meta.url));
92
+ }
93
+ var PLATFORM_MARKERS = [
94
+ { id: "claude", markerDir: join(HOME, ".claude") },
95
+ { id: "codex", markerDir: join(HOME, ".codex") },
96
+ { id: "claw", markerDir: join(HOME, ".openclaw") },
97
+ { id: "cursor", markerDir: join(HOME, ".cursor") },
98
+ { id: "hermes", markerDir: join(HOME, ".hermes") },
99
+ // pi (badlogic/pi-mono coding-agent) — config at ~/.pi/agent/. pi exposes
100
+ // a rich extension event API (session_start / input / tool_call /
101
+ // tool_result / message_end / session_shutdown / etc.) — Tier 1 capable.
102
+ { id: "pi", markerDir: join(HOME, ".pi") }
103
+ ];
104
+
105
+ // dist/src/cli/version.js
106
+ function getVersion() {
107
+ try {
108
+ const pkg = JSON.parse(readFileSync2(join2(pkgRoot(), "package.json"), "utf-8"));
109
+ return pkg.version ?? "0.0.0";
110
+ } catch {
111
+ return "0.0.0";
112
+ }
113
+ }
114
+
115
+ // dist/src/graph/cache.js
116
+ import { createHash } from "node:crypto";
117
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "node:fs";
118
+ import { dirname as dirname2, join as join3 } from "node:path";
119
+ var CACHE_SCHEMA_VERSION = 1;
120
+ function fileContentHash(contents) {
121
+ return createHash("sha256").update(contents).digest("hex");
122
+ }
123
+ function cacheDir(baseDir) {
124
+ return join3(baseDir, ".cache");
125
+ }
126
+ function cachePath(baseDir, contentSha256) {
127
+ return join3(cacheDir(baseDir), `${contentSha256}.json`);
128
+ }
129
+ function readCache(baseDir, contentSha256, relativePath) {
130
+ const path = cachePath(baseDir, contentSha256);
131
+ if (!existsSync2(path))
132
+ return null;
133
+ let raw;
134
+ try {
135
+ raw = readFileSync3(path, "utf8");
136
+ } catch {
137
+ return null;
138
+ }
139
+ let parsed;
140
+ try {
141
+ parsed = JSON.parse(raw);
142
+ } catch {
143
+ return null;
144
+ }
145
+ if (parsed === null || typeof parsed !== "object" || parsed.schema !== CACHE_SCHEMA_VERSION || parsed.content_sha256 !== contentSha256) {
146
+ return null;
147
+ }
148
+ const cached = parsed.extraction;
149
+ if (cached === void 0 || typeof cached !== "object" || !Array.isArray(cached.nodes) || !Array.isArray(cached.edges) || !Array.isArray(cached.parse_errors)) {
150
+ return null;
151
+ }
152
+ if (!validateItems(cached)) {
153
+ return null;
154
+ }
155
+ try {
156
+ return rewriteSourceFile(cached, relativePath);
157
+ } catch {
158
+ return null;
159
+ }
160
+ }
161
+ function validateItems(ex) {
162
+ if (typeof ex.source_file !== "string")
163
+ return false;
164
+ if (typeof ex.language !== "string")
165
+ return false;
166
+ for (const n of ex.nodes) {
167
+ if (n === null || typeof n !== "object")
168
+ return false;
169
+ if (typeof n.id !== "string")
170
+ return false;
171
+ if (typeof n.label !== "string")
172
+ return false;
173
+ if (typeof n.kind !== "string")
174
+ return false;
175
+ if (typeof n.source_file !== "string")
176
+ return false;
177
+ if (typeof n.source_location !== "string")
178
+ return false;
179
+ if (typeof n.language !== "string")
180
+ return false;
181
+ if (typeof n.exported !== "boolean")
182
+ return false;
183
+ }
184
+ for (const e of ex.edges) {
185
+ if (e === null || typeof e !== "object")
186
+ return false;
187
+ if (typeof e.source !== "string")
188
+ return false;
189
+ if (typeof e.target !== "string")
190
+ return false;
191
+ if (typeof e.relation !== "string")
192
+ return false;
193
+ if (typeof e.confidence !== "string")
194
+ return false;
195
+ if (e.ord !== void 0 && typeof e.ord !== "number")
196
+ return false;
197
+ }
198
+ for (const p of ex.parse_errors) {
199
+ if (p === null || typeof p !== "object")
200
+ return false;
201
+ if (typeof p.source_file !== "string")
202
+ return false;
203
+ if (typeof p.message !== "string")
204
+ return false;
205
+ if (p.location !== void 0 && typeof p.location !== "string")
206
+ return false;
207
+ }
208
+ return true;
209
+ }
210
+ function writeCache(baseDir, contentSha256, extraction) {
211
+ const entry = {
212
+ schema: CACHE_SCHEMA_VERSION,
213
+ content_sha256: contentSha256,
214
+ extraction
215
+ };
216
+ const path = cachePath(baseDir, contentSha256);
217
+ try {
218
+ mkdirSync2(dirname2(path), { recursive: true });
219
+ const tmp = `${path}.tmp.${process.pid}.${Date.now()}`;
220
+ writeFileSync2(tmp, JSON.stringify(entry));
221
+ renameSync(tmp, path);
222
+ } catch {
223
+ }
224
+ }
225
+ function rewriteSourceFile(cached, newPath) {
226
+ const oldPath = cached.source_file;
227
+ if (oldPath === newPath) {
228
+ return cached;
229
+ }
230
+ const swap = (id) => {
231
+ if (id.startsWith(`${oldPath}:`))
232
+ return `${newPath}${id.slice(oldPath.length)}`;
233
+ if (id.startsWith(`unresolved:${oldPath}:`)) {
234
+ return `unresolved:${newPath}${id.slice(`unresolved:${oldPath}`.length)}`;
235
+ }
236
+ return id;
237
+ };
238
+ return {
239
+ source_file: newPath,
240
+ language: cached.language,
241
+ // The synthetic module node uses source_file as its `label` (see
242
+ // makeModuleNode in the extractor). On a cache hit after a rename/copy
243
+ // we already rewrite `id` + `source_file`, but were leaving `label`
244
+ // pointing at the OLD path — the snapshot then disagreed with a
245
+ // fresh (non-cached) extraction. Rewrite `label` for module nodes too.
246
+ // CodeRabbit P1.
247
+ nodes: cached.nodes.map((n) => ({
248
+ ...n,
249
+ id: swap(n.id),
250
+ label: n.kind === "module" ? newPath : n.label,
251
+ source_file: newPath
252
+ })),
253
+ edges: cached.edges.map((e) => ({ ...e, source: swap(e.source), target: swap(e.target) })),
254
+ parse_errors: cached.parse_errors.map((p) => ({ ...p, source_file: newPath }))
255
+ };
256
+ }
257
+
258
+ // dist/src/graph/deeplake-push.js
259
+ import { createHash as createHash2 } from "node:crypto";
260
+
261
+ // dist/src/config.js
262
+ import { readFileSync as readFileSync4, existsSync as existsSync3 } from "node:fs";
263
+ import { join as join4 } from "node:path";
264
+ import { homedir as homedir2, userInfo } from "node:os";
265
+ function loadConfig() {
266
+ const home = homedir2();
267
+ const credPath = join4(home, ".deeplake", "credentials.json");
268
+ let creds = null;
269
+ if (existsSync3(credPath)) {
270
+ try {
271
+ creds = JSON.parse(readFileSync4(credPath, "utf-8"));
272
+ } catch {
273
+ return null;
274
+ }
275
+ }
276
+ const token = process.env.HIVEMIND_TOKEN ?? creds?.token;
277
+ const orgId = process.env.HIVEMIND_ORG_ID ?? creds?.orgId;
278
+ if (!token || !orgId)
279
+ return null;
280
+ return {
281
+ token,
282
+ orgId,
283
+ orgName: creds?.orgName ?? orgId,
284
+ userName: creds?.userName || userInfo().username || "unknown",
285
+ workspaceId: process.env.HIVEMIND_WORKSPACE_ID ?? creds?.workspaceId ?? "default",
286
+ apiUrl: process.env.HIVEMIND_API_URL ?? creds?.apiUrl ?? "https://api.deeplake.ai",
287
+ tableName: process.env.HIVEMIND_TABLE ?? "memory",
288
+ sessionsTableName: process.env.HIVEMIND_SESSIONS_TABLE ?? "sessions",
289
+ skillsTableName: process.env.HIVEMIND_SKILLS_TABLE ?? "skills",
290
+ // Defaults match the table name written into the SQL — keep aligned
291
+ // with RULES_COLUMNS in deeplake-schema.ts and with the e2e test-org
292
+ // override convention (memory_test / sessions_test → goals_test, etc.)
293
+ // documented in CLAUDE.md.
294
+ rulesTableName: process.env.HIVEMIND_RULES_TABLE ?? "hivemind_rules",
295
+ // Goals + KPIs (refined design — VFS path classifier maps
296
+ // memory/goal/<user>/<status>/<uuid>.md → hivemind_goals row
297
+ // memory/kpi/<uuid>/<kpi_id>.md → hivemind_kpis row
298
+ // See src/shell/deeplake-fs.ts for the translation logic and
299
+ // GOALS_COLUMNS / KPIS_COLUMNS in deeplake-schema.ts for the
300
+ // table shape.
301
+ goalsTableName: process.env.HIVEMIND_GOALS_TABLE ?? "hivemind_goals",
302
+ kpisTableName: process.env.HIVEMIND_KPIS_TABLE ?? "hivemind_kpis",
303
+ codebaseTableName: process.env.HIVEMIND_CODEBASE_TABLE ?? "codebase",
304
+ memoryPath: process.env.HIVEMIND_MEMORY_PATH ?? join4(home, ".deeplake", "memory")
305
+ };
306
+ }
307
+
308
+ // dist/src/deeplake-api.js
309
+ import { randomUUID } from "node:crypto";
310
+
311
+ // dist/src/utils/debug.js
312
+ import { appendFileSync } from "node:fs";
313
+ import { join as join5 } from "node:path";
314
+ import { homedir as homedir3 } from "node:os";
315
+ var LOG = join5(homedir3(), ".deeplake", "hook-debug.log");
316
+ function isDebug() {
317
+ return process.env.HIVEMIND_DEBUG === "1";
318
+ }
319
+ function log(tag, msg) {
320
+ if (!isDebug())
321
+ return;
322
+ appendFileSync(LOG, `${(/* @__PURE__ */ new Date()).toISOString()} [${tag}] ${msg}
323
+ `);
324
+ }
325
+
326
+ // dist/src/utils/sql.js
327
+ function sqlStr(value) {
328
+ return value.replace(/\\/g, "\\\\").replace(/'/g, "''").replace(/\0/g, "").replace(/[\x01-\x08\x0b\x0c\x0e-\x1f\x7f]/g, "");
329
+ }
330
+ function sqlIdent(name) {
331
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
332
+ throw new Error(`Invalid SQL identifier: ${JSON.stringify(name)}`);
333
+ }
334
+ return name;
335
+ }
336
+
337
+ // dist/src/embeddings/columns.js
338
+ var SUMMARY_EMBEDDING_COL = "summary_embedding";
339
+
340
+ // dist/src/utils/client-header.js
341
+ var DEEPLAKE_CLIENT_HEADER = "X-Deeplake-Client";
342
+ function deeplakeClientValue() {
343
+ return "hivemind";
344
+ }
345
+ function deeplakeClientHeader() {
346
+ return { [DEEPLAKE_CLIENT_HEADER]: deeplakeClientValue() };
347
+ }
348
+
349
+ // dist/src/deeplake-schema.js
350
+ var MEMORY_COLUMNS = Object.freeze([
351
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
352
+ { name: "path", sql: "TEXT NOT NULL DEFAULT ''" },
353
+ { name: "filename", sql: "TEXT NOT NULL DEFAULT ''" },
354
+ { name: "summary", sql: "TEXT NOT NULL DEFAULT ''" },
355
+ { name: "summary_embedding", sql: "FLOAT4[]" },
356
+ { name: "author", sql: "TEXT NOT NULL DEFAULT ''" },
357
+ { name: "mime_type", sql: "TEXT NOT NULL DEFAULT 'text/plain'" },
358
+ { name: "size_bytes", sql: "BIGINT NOT NULL DEFAULT 0" },
359
+ { name: "project", sql: "TEXT NOT NULL DEFAULT ''" },
360
+ { name: "description", sql: "TEXT NOT NULL DEFAULT ''" },
361
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT ''" },
362
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" },
363
+ { name: "creation_date", sql: "TEXT NOT NULL DEFAULT ''" },
364
+ { name: "last_update_date", sql: "TEXT NOT NULL DEFAULT ''" }
365
+ ]);
366
+ var SESSIONS_COLUMNS = Object.freeze([
367
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
368
+ { name: "path", sql: "TEXT NOT NULL DEFAULT ''" },
369
+ { name: "filename", sql: "TEXT NOT NULL DEFAULT ''" },
370
+ { name: "message", sql: "JSONB" },
371
+ { name: "message_embedding", sql: "FLOAT4[]" },
372
+ { name: "author", sql: "TEXT NOT NULL DEFAULT ''" },
373
+ { name: "mime_type", sql: "TEXT NOT NULL DEFAULT 'application/json'" },
374
+ { name: "size_bytes", sql: "BIGINT NOT NULL DEFAULT 0" },
375
+ { name: "project", sql: "TEXT NOT NULL DEFAULT ''" },
376
+ { name: "description", sql: "TEXT NOT NULL DEFAULT ''" },
377
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT ''" },
378
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" },
379
+ { name: "creation_date", sql: "TEXT NOT NULL DEFAULT ''" },
380
+ { name: "last_update_date", sql: "TEXT NOT NULL DEFAULT ''" }
381
+ ]);
382
+ var SKILLS_COLUMNS = Object.freeze([
383
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
384
+ { name: "name", sql: "TEXT NOT NULL DEFAULT ''" },
385
+ { name: "project", sql: "TEXT NOT NULL DEFAULT ''" },
386
+ { name: "project_key", sql: "TEXT NOT NULL DEFAULT ''" },
387
+ { name: "local_path", sql: "TEXT NOT NULL DEFAULT ''" },
388
+ { name: "install", sql: "TEXT NOT NULL DEFAULT 'project'" },
389
+ { name: "source_sessions", sql: "TEXT NOT NULL DEFAULT '[]'" },
390
+ { name: "source_agent", sql: "TEXT NOT NULL DEFAULT ''" },
391
+ { name: "scope", sql: "TEXT NOT NULL DEFAULT 'me'" },
392
+ { name: "author", sql: "TEXT NOT NULL DEFAULT ''" },
393
+ { name: "contributors", sql: "TEXT NOT NULL DEFAULT '[]'" },
394
+ { name: "description", sql: "TEXT NOT NULL DEFAULT ''" },
395
+ { name: "trigger_text", sql: "TEXT NOT NULL DEFAULT ''" },
396
+ { name: "body", sql: "TEXT NOT NULL DEFAULT ''" },
397
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
398
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
399
+ { name: "updated_at", sql: "TEXT NOT NULL DEFAULT ''" }
400
+ ]);
401
+ var RULES_COLUMNS = Object.freeze([
402
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
403
+ { name: "rule_id", sql: "TEXT NOT NULL DEFAULT ''" },
404
+ { name: "text", sql: "TEXT NOT NULL DEFAULT ''" },
405
+ { name: "scope", sql: "TEXT NOT NULL DEFAULT 'team'" },
406
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'active'" },
407
+ { name: "assigned_by", sql: "TEXT NOT NULL DEFAULT ''" },
408
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
409
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
410
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
411
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
412
+ ]);
413
+ var GOALS_COLUMNS = Object.freeze([
414
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
415
+ { name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
416
+ { name: "owner", sql: "TEXT NOT NULL DEFAULT ''" },
417
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'opened'" },
418
+ { name: "content", sql: "TEXT NOT NULL DEFAULT ''" },
419
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
420
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
421
+ { name: "updated_at", sql: "TEXT NOT NULL DEFAULT ''" },
422
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
423
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
424
+ ]);
425
+ var KPIS_COLUMNS = Object.freeze([
426
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
427
+ { name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
428
+ { name: "kpi_id", sql: "TEXT NOT NULL DEFAULT ''" },
429
+ { name: "content", sql: "TEXT NOT NULL DEFAULT ''" },
430
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
431
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
432
+ { name: "updated_at", sql: "TEXT NOT NULL DEFAULT ''" },
433
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
434
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
435
+ ]);
436
+ function validateSchema(label, cols) {
437
+ const seen = /* @__PURE__ */ new Set();
438
+ for (const col of cols) {
439
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(col.name)) {
440
+ throw new Error(`${label}: column name "${col.name}" is not a valid SQL identifier`);
441
+ }
442
+ if (seen.has(col.name)) {
443
+ throw new Error(`${label}: duplicate column "${col.name}"`);
444
+ }
445
+ seen.add(col.name);
446
+ const notNull = /\bNOT\s+NULL\b/i.test(col.sql);
447
+ const hasDefault = /\bDEFAULT\b/i.test(col.sql);
448
+ if (notNull && !hasDefault) {
449
+ throw new Error(`${label}: column "${col.name}" is NOT NULL but has no DEFAULT \u2014 ALTER TABLE ADD COLUMN on a populated table would fail.`);
450
+ }
451
+ }
452
+ }
453
+ var CODEBASE_COLUMNS = Object.freeze([
454
+ // Identity key (matches the PK below)
455
+ { name: "org_id", sql: "TEXT NOT NULL DEFAULT ''" },
456
+ { name: "workspace_id", sql: "TEXT NOT NULL DEFAULT ''" },
457
+ { name: "repo_slug", sql: "TEXT NOT NULL DEFAULT ''" },
458
+ { name: "user_id", sql: "TEXT NOT NULL DEFAULT ''" },
459
+ { name: "worktree_id", sql: "TEXT NOT NULL DEFAULT ''" },
460
+ { name: "commit_sha", sql: "TEXT NOT NULL DEFAULT ''" },
461
+ // Observation metadata
462
+ { name: "parent_sha", sql: "TEXT NOT NULL DEFAULT ''" },
463
+ { name: "branch", sql: "TEXT NOT NULL DEFAULT ''" },
464
+ { name: "ts", sql: "TIMESTAMP" },
465
+ { name: "pushed_by", sql: "TEXT NOT NULL DEFAULT ''" },
466
+ // Snapshot payload
467
+ { name: "snapshot_sha256", sql: "TEXT NOT NULL DEFAULT ''" },
468
+ { name: "snapshot_jsonb", sql: "TEXT NOT NULL DEFAULT ''" },
469
+ { name: "node_count", sql: "BIGINT NOT NULL DEFAULT 0" },
470
+ { name: "edge_count", sql: "BIGINT NOT NULL DEFAULT 0" },
471
+ // Generator metadata (for drift diagnostics — what hivemind version produced this?)
472
+ { name: "generator", sql: "TEXT NOT NULL DEFAULT 'hivemind-graph'" },
473
+ { name: "generator_version", sql: "TEXT NOT NULL DEFAULT ''" },
474
+ { name: "schema_version", sql: "BIGINT NOT NULL DEFAULT 1" }
475
+ ]);
476
+ validateSchema("MEMORY_COLUMNS", MEMORY_COLUMNS);
477
+ validateSchema("SESSIONS_COLUMNS", SESSIONS_COLUMNS);
478
+ validateSchema("SKILLS_COLUMNS", SKILLS_COLUMNS);
479
+ validateSchema("RULES_COLUMNS", RULES_COLUMNS);
480
+ validateSchema("GOALS_COLUMNS", GOALS_COLUMNS);
481
+ validateSchema("KPIS_COLUMNS", KPIS_COLUMNS);
482
+ validateSchema("CODEBASE_COLUMNS", CODEBASE_COLUMNS);
483
+ function buildCreateTableSql(tableName, cols) {
484
+ const safe = sqlIdent(tableName);
485
+ const colSql = cols.map((c) => `${c.name} ${c.sql}`).join(", ");
486
+ return `CREATE TABLE IF NOT EXISTS "${safe}" (${colSql}) USING deeplake`;
487
+ }
488
+ function buildIntrospectionSql(tableName, workspaceId) {
489
+ return `SELECT column_name FROM information_schema.columns WHERE table_name = '${sqlStr(tableName)}' AND table_schema = '${sqlStr(workspaceId)}'`;
490
+ }
491
+ async function healMissingColumns(args) {
492
+ const safeTable = sqlIdent(args.tableName);
493
+ const introspectSql = buildIntrospectionSql(args.tableName, args.workspaceId);
494
+ const rows = await args.query(introspectSql);
495
+ const existing = /* @__PURE__ */ new Set();
496
+ for (const row of rows) {
497
+ const v = row?.column_name;
498
+ if (typeof v === "string")
499
+ existing.add(v.toLowerCase());
500
+ }
501
+ const missingCols = args.columns.filter((c) => !existing.has(c.name.toLowerCase()));
502
+ const missing = missingCols.map((c) => c.name);
503
+ if (missingCols.length === 0)
504
+ return { missing, altered: [] };
505
+ const altered = [];
506
+ for (const col of missingCols) {
507
+ try {
508
+ await args.query(`ALTER TABLE "${safeTable}" ADD COLUMN ${col.name} ${col.sql}`);
509
+ altered.push(col.name);
510
+ args.log?.(`schema-heal: added "${args.tableName}"."${col.name}"`);
511
+ } catch (e) {
512
+ const msg = e instanceof Error ? e.message : String(e);
513
+ if (!/already exists/i.test(msg))
514
+ throw e;
515
+ const recheck = await args.query(introspectSql);
516
+ const present = recheck.some((r) => {
517
+ const v = r?.column_name;
518
+ return typeof v === "string" && v.toLowerCase() === col.name.toLowerCase();
519
+ });
520
+ if (!present)
521
+ throw e;
522
+ args.log?.(`schema-heal: "${args.tableName}"."${col.name}" appeared via race, treating as success`);
523
+ }
524
+ }
525
+ return { missing, altered };
526
+ }
527
+
528
+ // dist/src/notifications/queue.js
529
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync as renameSync2, mkdirSync as mkdirSync3, openSync, closeSync, unlinkSync as unlinkSync2, statSync } from "node:fs";
530
+ import { join as join6, resolve } from "node:path";
531
+ import { homedir as homedir4 } from "node:os";
532
+ import { setTimeout as sleep } from "node:timers/promises";
533
+ var log2 = (msg) => log("notifications-queue", msg);
534
+ var LOCK_RETRY_MAX = 50;
535
+ var LOCK_RETRY_BASE_MS = 5;
536
+ var LOCK_STALE_MS = 5e3;
537
+ function queuePath() {
538
+ return join6(homedir4(), ".deeplake", "notifications-queue.json");
539
+ }
540
+ function lockPath() {
541
+ return `${queuePath()}.lock`;
542
+ }
543
+ function readQueue() {
544
+ try {
545
+ const raw = readFileSync5(queuePath(), "utf-8");
546
+ const parsed = JSON.parse(raw);
547
+ if (!parsed || !Array.isArray(parsed.queue)) {
548
+ log2(`queue malformed \u2192 treating as empty`);
549
+ return { queue: [] };
550
+ }
551
+ return { queue: parsed.queue };
552
+ } catch {
553
+ return { queue: [] };
554
+ }
555
+ }
556
+ function _isQueuePathInsideHome(path, home) {
557
+ const r = resolve(path);
558
+ const h = resolve(home);
559
+ return r.startsWith(h + "/") || r === h;
560
+ }
561
+ function writeQueue(q) {
562
+ const path = queuePath();
563
+ const home = resolve(homedir4());
564
+ if (!_isQueuePathInsideHome(path, home)) {
565
+ throw new Error(`notifications-queue write blocked: ${path} is outside ${home}`);
566
+ }
567
+ mkdirSync3(join6(home, ".deeplake"), { recursive: true, mode: 448 });
568
+ const tmp = `${path}.${process.pid}.tmp`;
569
+ writeFileSync3(tmp, JSON.stringify(q, null, 2), { mode: 384 });
570
+ renameSync2(tmp, path);
571
+ }
572
+ async function withQueueLock(fn) {
573
+ const path = lockPath();
574
+ mkdirSync3(join6(homedir4(), ".deeplake"), { recursive: true, mode: 448 });
575
+ let fd = null;
576
+ for (let attempt = 0; attempt < LOCK_RETRY_MAX; attempt++) {
577
+ try {
578
+ fd = openSync(path, "wx", 384);
579
+ break;
580
+ } catch (e) {
581
+ const code = e.code;
582
+ if (code !== "EEXIST")
583
+ throw e;
584
+ try {
585
+ const age = Date.now() - statSync(path).mtimeMs;
586
+ if (age > LOCK_STALE_MS) {
587
+ unlinkSync2(path);
588
+ continue;
589
+ }
590
+ } catch {
591
+ }
592
+ const delay = LOCK_RETRY_BASE_MS * (attempt + 1);
593
+ await sleep(delay);
594
+ }
595
+ }
596
+ if (fd === null) {
597
+ log2(`lock acquisition gave up after ${LOCK_RETRY_MAX} attempts \u2014 proceeding unlocked (last-writer-wins)`);
598
+ return fn();
599
+ }
600
+ try {
601
+ return fn();
602
+ } finally {
603
+ try {
604
+ closeSync(fd);
605
+ } catch {
606
+ }
607
+ try {
608
+ unlinkSync2(path);
609
+ } catch {
610
+ }
611
+ }
612
+ }
613
+ function sameDedupKey(a, b) {
614
+ if (a.id !== b.id)
615
+ return false;
616
+ return JSON.stringify(a.dedupKey) === JSON.stringify(b.dedupKey);
617
+ }
618
+ async function enqueueNotification(n) {
619
+ await withQueueLock(() => {
620
+ const q = readQueue();
621
+ if (q.queue.some((existing) => sameDedupKey(existing, n))) {
622
+ return;
623
+ }
624
+ q.queue.push(n);
625
+ writeQueue(q);
626
+ });
627
+ }
628
+
629
+ // dist/src/commands/auth-creds.js
630
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, unlinkSync as unlinkSync3 } from "node:fs";
631
+ import { join as join7 } from "node:path";
632
+ import { homedir as homedir5 } from "node:os";
633
+ function configDir() {
634
+ return join7(homedir5(), ".deeplake");
635
+ }
636
+ function credsPath() {
637
+ return join7(configDir(), "credentials.json");
638
+ }
639
+ function loadCredentials() {
640
+ try {
641
+ return JSON.parse(readFileSync6(credsPath(), "utf-8"));
642
+ } catch {
643
+ return null;
644
+ }
645
+ }
646
+
647
+ // dist/src/deeplake-api.js
648
+ var indexMarkerStorePromise = null;
649
+ function getIndexMarkerStore() {
650
+ if (!indexMarkerStorePromise)
651
+ indexMarkerStorePromise = Promise.resolve().then(() => (init_index_marker_store(), index_marker_store_exports));
652
+ return indexMarkerStorePromise;
653
+ }
654
+ var log3 = (msg) => log("sdk", msg);
655
+ function summarizeSql(sql, maxLen = 220) {
656
+ const compact = sql.replace(/\s+/g, " ").trim();
657
+ return compact.length > maxLen ? `${compact.slice(0, maxLen)}...` : compact;
658
+ }
659
+ function traceSql(msg) {
660
+ const traceEnabled = process.env.HIVEMIND_TRACE_SQL === "1" || process.env.HIVEMIND_DEBUG === "1";
661
+ if (!traceEnabled)
662
+ return;
663
+ process.stderr.write(`[deeplake-sql] ${msg}
664
+ `);
665
+ if (process.env.HIVEMIND_DEBUG === "1")
666
+ log3(msg);
667
+ }
668
+ var _signalledBalanceExhausted = false;
669
+ function maybeSignalBalanceExhausted(status, bodyText) {
670
+ if (status !== 402)
671
+ return;
672
+ if (!bodyText.includes("balance_cents"))
673
+ return;
674
+ if (_signalledBalanceExhausted)
675
+ return;
676
+ _signalledBalanceExhausted = true;
677
+ log3(`balance exhausted \u2014 enqueuing session-start banner (body=${bodyText.slice(0, 120)})`);
678
+ enqueueNotification({
679
+ id: "balance-exhausted",
680
+ severity: "warn",
681
+ transient: true,
682
+ title: "Hivemind credits exhausted \u2014 top up to keep capturing",
683
+ body: `Sessions are not being saved and memory recall is returning empty. Top up at ${billingUrl()} to restore capture and recall.`,
684
+ dedupKey: { reason: "balance-zero" },
685
+ // User-facing billing notice → user channel only. Never the model's
686
+ // additionalContext: a "top up at <url>" instruction in the agent prompt
687
+ // is a prompt-injection pattern external agents flag.
688
+ userVisibleOnly: true
689
+ }).catch((e) => {
690
+ log3(`enqueue balance-exhausted failed: ${e instanceof Error ? e.message : String(e)}`);
691
+ });
692
+ }
693
+ function billingUrl() {
694
+ try {
695
+ const c = loadCredentials();
696
+ if (c?.orgName && c?.workspaceId) {
697
+ return `https://deeplake.ai/${encodeURIComponent(c.orgName)}/workspace/${encodeURIComponent(c.workspaceId)}/billing`;
698
+ }
699
+ } catch {
700
+ }
701
+ return "https://deeplake.ai";
702
+ }
703
+ var RETRYABLE_CODES = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
704
+ var MAX_RETRIES = 3;
705
+ var BASE_DELAY_MS = 500;
706
+ var MAX_CONCURRENCY = 5;
707
+ function getQueryTimeoutMs() {
708
+ return Number(process.env.HIVEMIND_QUERY_TIMEOUT_MS ?? 1e4);
709
+ }
710
+ function sleep2(ms) {
711
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
712
+ }
713
+ function isTimeoutError(error) {
714
+ const name = error instanceof Error ? error.name.toLowerCase() : "";
715
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
716
+ return name.includes("timeout") || name === "aborterror" || message.includes("timeout") || message.includes("timed out");
717
+ }
718
+ function isDuplicateIndexError(error) {
719
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
720
+ return message.includes("duplicate key value violates unique constraint") || message.includes("pg_class_relname_nsp_index") || message.includes("already exists");
721
+ }
722
+ function isSessionInsertQuery(sql) {
723
+ return /^\s*insert\s+into\s+"[^"]+"\s*\(\s*id\s*,\s*path\s*,\s*filename\s*,\s*message\s*,/i.test(sql);
724
+ }
725
+ function isTransientHtml403(text) {
726
+ const body = text.toLowerCase();
727
+ return body.includes("<html") || body.includes("403 forbidden") || body.includes("cloudflare") || body.includes("nginx");
728
+ }
729
+ var Semaphore = class {
730
+ max;
731
+ waiting = [];
732
+ active = 0;
733
+ constructor(max) {
734
+ this.max = max;
735
+ }
736
+ async acquire() {
737
+ if (this.active < this.max) {
738
+ this.active++;
739
+ return;
740
+ }
741
+ await new Promise((resolve6) => this.waiting.push(resolve6));
742
+ }
743
+ release() {
744
+ this.active--;
745
+ const next = this.waiting.shift();
746
+ if (next) {
747
+ this.active++;
748
+ next();
749
+ }
750
+ }
751
+ };
752
+ var DeeplakeApi = class {
753
+ token;
754
+ apiUrl;
755
+ orgId;
756
+ workspaceId;
757
+ tableName;
758
+ _pendingRows = [];
759
+ _sem = new Semaphore(MAX_CONCURRENCY);
760
+ _tablesCache = null;
761
+ constructor(token, apiUrl, orgId, workspaceId, tableName) {
762
+ this.token = token;
763
+ this.apiUrl = apiUrl;
764
+ this.orgId = orgId;
765
+ this.workspaceId = workspaceId;
766
+ this.tableName = tableName;
767
+ }
768
+ /** Execute SQL with retry on transient errors and bounded concurrency. */
769
+ async query(sql) {
770
+ const startedAt = Date.now();
771
+ const summary = summarizeSql(sql);
772
+ traceSql(`query start: ${summary}`);
773
+ await this._sem.acquire();
774
+ try {
775
+ const rows = await this._queryWithRetry(sql);
776
+ traceSql(`query ok (${Date.now() - startedAt}ms, rows=${rows.length}): ${summary}`);
777
+ return rows;
778
+ } catch (e) {
779
+ const message = e instanceof Error ? e.message : String(e);
780
+ traceSql(`query fail (${Date.now() - startedAt}ms): ${summary} :: ${message}`);
781
+ throw e;
782
+ } finally {
783
+ this._sem.release();
784
+ }
785
+ }
786
+ async _queryWithRetry(sql) {
787
+ let lastError;
788
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
789
+ let resp;
790
+ const timeoutMs = getQueryTimeoutMs();
791
+ try {
792
+ const signal = AbortSignal.timeout(timeoutMs);
793
+ resp = await fetch(`${this.apiUrl}/workspaces/${this.workspaceId}/tables/query`, {
794
+ method: "POST",
795
+ headers: {
796
+ Authorization: `Bearer ${this.token}`,
797
+ "Content-Type": "application/json",
798
+ "X-Activeloop-Org-Id": this.orgId,
799
+ ...deeplakeClientHeader()
800
+ },
801
+ signal,
802
+ body: JSON.stringify({ query: sql })
803
+ });
804
+ } catch (e) {
805
+ if (isTimeoutError(e)) {
806
+ lastError = new Error(`Query timeout after ${timeoutMs}ms`);
807
+ throw lastError;
808
+ }
809
+ lastError = e instanceof Error ? e : new Error(String(e));
810
+ if (attempt < MAX_RETRIES) {
811
+ const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200;
812
+ log3(`query retry ${attempt + 1}/${MAX_RETRIES} (fetch error: ${lastError.message}) in ${delay.toFixed(0)}ms`);
813
+ await sleep2(delay);
814
+ continue;
815
+ }
816
+ throw lastError;
817
+ }
818
+ if (resp.ok) {
819
+ const raw = await resp.json();
820
+ if (!raw?.rows || !raw?.columns)
821
+ return [];
822
+ return raw.rows.map((row) => Object.fromEntries(raw.columns.map((col, i) => [col, row[i]])));
823
+ }
824
+ const text = await resp.text().catch(() => "");
825
+ const retryable403 = isSessionInsertQuery(sql) && (resp.status === 401 || resp.status === 403 && (text.length === 0 || isTransientHtml403(text)));
826
+ const alreadyExists = resp.status === 500 && isDuplicateIndexError(text);
827
+ if (!alreadyExists && attempt < MAX_RETRIES && (RETRYABLE_CODES.has(resp.status) || retryable403)) {
828
+ const delay = BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200;
829
+ log3(`query retry ${attempt + 1}/${MAX_RETRIES} (${resp.status}) in ${delay.toFixed(0)}ms`);
830
+ await sleep2(delay);
831
+ continue;
832
+ }
833
+ maybeSignalBalanceExhausted(resp.status, text);
834
+ throw new Error(`Query failed: ${resp.status}: ${text.slice(0, 200)}`);
835
+ }
836
+ throw lastError ?? new Error("Query failed: max retries exceeded");
837
+ }
838
+ // ── Writes ──────────────────────────────────────────────────────────────────
839
+ /** Queue rows for writing. Call commit() to flush. */
840
+ appendRows(rows) {
841
+ this._pendingRows.push(...rows);
842
+ }
843
+ /** Flush pending rows via SQL. */
844
+ async commit() {
845
+ if (this._pendingRows.length === 0)
846
+ return;
847
+ const rows = this._pendingRows;
848
+ this._pendingRows = [];
849
+ const CONCURRENCY = 10;
850
+ for (let i = 0; i < rows.length; i += CONCURRENCY) {
851
+ const chunk = rows.slice(i, i + CONCURRENCY);
852
+ await Promise.allSettled(chunk.map((r) => this.upsertRowSql(r)));
853
+ }
854
+ log3(`commit: ${rows.length} rows`);
855
+ }
856
+ async upsertRowSql(row) {
857
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
858
+ const cd = row.creationDate ?? ts;
859
+ const lud = row.lastUpdateDate ?? ts;
860
+ const exists = await this.query(`SELECT path FROM "${this.tableName}" WHERE path = '${sqlStr(row.path)}' LIMIT 1`);
861
+ if (exists.length > 0) {
862
+ let setClauses = `summary = E'${sqlStr(row.contentText)}', ${SUMMARY_EMBEDDING_COL} = NULL, mime_type = '${sqlStr(row.mimeType)}', size_bytes = ${row.sizeBytes}, last_update_date = '${lud}'`;
863
+ if (row.project !== void 0)
864
+ setClauses += `, project = '${sqlStr(row.project)}'`;
865
+ if (row.description !== void 0)
866
+ setClauses += `, description = '${sqlStr(row.description)}'`;
867
+ await this.query(`UPDATE "${this.tableName}" SET ${setClauses} WHERE path = '${sqlStr(row.path)}'`);
868
+ } else {
869
+ const id = randomUUID();
870
+ let cols = `id, path, filename, summary, ${SUMMARY_EMBEDDING_COL}, mime_type, size_bytes, creation_date, last_update_date`;
871
+ let vals = `'${id}', '${sqlStr(row.path)}', '${sqlStr(row.filename)}', E'${sqlStr(row.contentText)}', NULL, '${sqlStr(row.mimeType)}', ${row.sizeBytes}, '${cd}', '${lud}'`;
872
+ if (row.project !== void 0) {
873
+ cols += ", project";
874
+ vals += `, '${sqlStr(row.project)}'`;
875
+ }
876
+ if (row.description !== void 0) {
877
+ cols += ", description";
878
+ vals += `, '${sqlStr(row.description)}'`;
879
+ }
880
+ await this.query(`INSERT INTO "${this.tableName}" (${cols}) VALUES (${vals})`);
881
+ }
882
+ }
883
+ /** Update specific columns on a row by path. */
884
+ async updateColumns(path, columns) {
885
+ const setClauses = Object.entries(columns).map(([col, val]) => typeof val === "number" ? `${col} = ${val}` : `${col} = '${sqlStr(String(val))}'`).join(", ");
886
+ await this.query(`UPDATE "${this.tableName}" SET ${setClauses} WHERE path = '${sqlStr(path)}'`);
887
+ }
888
+ // ── Convenience ─────────────────────────────────────────────────────────────
889
+ /** Create a BM25 search index on a column. */
890
+ async createIndex(column) {
891
+ await this.query(`CREATE INDEX IF NOT EXISTS idx_${sqlStr(column)}_bm25 ON "${this.tableName}" USING deeplake_index ("${column}")`);
892
+ }
893
+ buildLookupIndexName(table, suffix) {
894
+ return `idx_${table}_${suffix}`.replace(/[^a-zA-Z0-9_]/g, "_");
895
+ }
896
+ async ensureLookupIndex(table, suffix, columnsSql) {
897
+ const markers = await getIndexMarkerStore();
898
+ const markerPath = markers.buildIndexMarkerPath(this.workspaceId, this.orgId, table, suffix);
899
+ if (markers.hasFreshIndexMarker(markerPath))
900
+ return;
901
+ const indexName = this.buildLookupIndexName(table, suffix);
902
+ try {
903
+ await this.query(`CREATE INDEX IF NOT EXISTS "${indexName}" ON "${table}" ${columnsSql}`);
904
+ markers.writeIndexMarker(markerPath);
905
+ } catch (e) {
906
+ if (isDuplicateIndexError(e)) {
907
+ markers.writeIndexMarker(markerPath);
908
+ return;
909
+ }
910
+ log3(`index "${indexName}" skipped: ${e.message}`);
911
+ }
912
+ }
913
+ /**
914
+ * Heal any missing columns on a table so it matches one of the schema
915
+ * definitions in `deeplake-schema.ts`. One SELECT against
916
+ * `information_schema.columns` per call, then `ALTER TABLE ADD COLUMN`
917
+ * only the genuinely missing ones — never blanket, never `IF NOT
918
+ * EXISTS`.
919
+ *
920
+ * History: an earlier path used a local marker file (`col_<name>` under
921
+ * the index-marker dir) to skip even the SELECT after the first
922
+ * confirmation, plus per-column ALTERs for `summary_embedding`,
923
+ * `message_embedding`, `agent`, `plugin_version`. The marker existed
924
+ * because Deeplake used to expose a ~30s post-ALTER bug where
925
+ * subsequent INSERTs failed, so we wanted to keep ALTER traffic to a
926
+ * minimum. The bug was re-verified on 2026-05-18 against
927
+ * `api.deeplake.ai` (`test_plugin` org) and no longer reproduces
928
+ * (71/71 INSERTs OK, first success 2ms after ALTER). The single SELECT
929
+ * + targeted ALTER pattern survives the marker removal because: each
930
+ * ALTER still costs ~800ms (so blanket sweeps are wasteful) and the
931
+ * diff produces clearer logs than "ALTER all with IF NOT EXISTS".
932
+ */
933
+ async healSchema(table, columns) {
934
+ await healMissingColumns({
935
+ query: (sql) => this.query(sql),
936
+ tableName: table,
937
+ workspaceId: this.workspaceId,
938
+ columns,
939
+ log: log3
940
+ });
941
+ }
942
+ /** List all tables in the workspace (with retry). */
943
+ async listTables(forceRefresh = false) {
944
+ if (!forceRefresh && this._tablesCache)
945
+ return [...this._tablesCache];
946
+ const { tables, cacheable } = await this._fetchTables();
947
+ if (cacheable)
948
+ this._tablesCache = [...tables];
949
+ return tables;
950
+ }
951
+ /**
952
+ * Like listTables() but returns null when the list could NOT be trusted
953
+ * (the fetch failed / was non-cacheable). Callers gating a read on table
954
+ * existence use this to tell a genuinely-empty workspace ([]) apart from a
955
+ * failed lookup (null): on [] they can safely skip the read (no table → no
956
+ * 42P01), on null they must fall back to SELECT-then-catch so a transient
957
+ * lookup blip doesn't drop a read of a table that really exists.
958
+ */
959
+ async knownTablesOrNull() {
960
+ if (this._tablesCache)
961
+ return [...this._tablesCache];
962
+ const { tables, cacheable } = await this._fetchTables();
963
+ if (!cacheable)
964
+ return null;
965
+ this._tablesCache = [...tables];
966
+ return [...tables];
967
+ }
968
+ async _fetchTables() {
969
+ for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
970
+ try {
971
+ const resp = await fetch(`${this.apiUrl}/workspaces/${this.workspaceId}/tables`, {
972
+ headers: {
973
+ Authorization: `Bearer ${this.token}`,
974
+ "X-Activeloop-Org-Id": this.orgId,
975
+ ...deeplakeClientHeader()
976
+ }
977
+ });
978
+ if (resp.ok) {
979
+ const data = await resp.json();
980
+ return {
981
+ tables: (data.tables ?? []).map((t) => t.table_name),
982
+ cacheable: true
983
+ };
984
+ }
985
+ if (attempt < MAX_RETRIES && RETRYABLE_CODES.has(resp.status)) {
986
+ await sleep2(BASE_DELAY_MS * Math.pow(2, attempt) + Math.random() * 200);
987
+ continue;
988
+ }
989
+ return { tables: [], cacheable: false };
990
+ } catch {
991
+ if (attempt < MAX_RETRIES) {
992
+ await sleep2(BASE_DELAY_MS * Math.pow(2, attempt));
993
+ continue;
994
+ }
995
+ return { tables: [], cacheable: false };
996
+ }
997
+ }
998
+ return { tables: [], cacheable: false };
999
+ }
1000
+ /**
1001
+ * Run a `CREATE TABLE` with an extra outer retry budget. The base
1002
+ * `query()` already retries 3 times on fetch errors (~3.5s total), but a
1003
+ * failed CREATE is permanent corruption — every subsequent SELECT against
1004
+ * the missing table fails. Wrapping in an outer loop with longer backoff
1005
+ * (2s, 5s, then 10s) gives us ~17s of reach across transient network
1006
+ * blips before giving up. Failures still propagate; getApi() resets its
1007
+ * cache on init failure (openclaw plugin) so the next call retries the
1008
+ * whole init flow.
1009
+ */
1010
+ async createTableWithRetry(sql, label) {
1011
+ const OUTER_BACKOFFS_MS = [2e3, 5e3, 1e4];
1012
+ let lastErr = null;
1013
+ for (let attempt = 0; attempt <= OUTER_BACKOFFS_MS.length; attempt++) {
1014
+ try {
1015
+ await this.query(sql);
1016
+ return;
1017
+ } catch (err) {
1018
+ lastErr = err;
1019
+ const msg = err instanceof Error ? err.message : String(err);
1020
+ log3(`CREATE TABLE "${label}" attempt ${attempt + 1}/${OUTER_BACKOFFS_MS.length + 1} failed: ${msg}`);
1021
+ if (attempt < OUTER_BACKOFFS_MS.length) {
1022
+ await sleep2(OUTER_BACKOFFS_MS[attempt]);
1023
+ }
1024
+ }
1025
+ }
1026
+ throw lastErr;
1027
+ }
1028
+ /** Create the memory table if it doesn't already exist. Heal missing columns on existing tables. */
1029
+ async ensureTable(name) {
1030
+ if (!MEMORY_COLUMNS.some((c) => c.name === SUMMARY_EMBEDDING_COL)) {
1031
+ throw new Error(`MEMORY_COLUMNS missing "${SUMMARY_EMBEDDING_COL}" (embeddings/columns.ts drift)`);
1032
+ }
1033
+ const tbl = sqlIdent(name ?? this.tableName);
1034
+ const tables = await this.listTables();
1035
+ if (!tables.includes(tbl)) {
1036
+ log3(`table "${tbl}" not found, creating`);
1037
+ await this.createTableWithRetry(buildCreateTableSql(tbl, MEMORY_COLUMNS), tbl);
1038
+ log3(`table "${tbl}" created`);
1039
+ if (!tables.includes(tbl))
1040
+ this._tablesCache = [...tables, tbl];
1041
+ }
1042
+ await this.healSchema(tbl, MEMORY_COLUMNS);
1043
+ }
1044
+ /** Create the sessions table (uses JSONB for message since every row is a JSON event). */
1045
+ async ensureSessionsTable(name) {
1046
+ const safe = sqlIdent(name);
1047
+ const tables = await this.listTables();
1048
+ if (!tables.includes(safe)) {
1049
+ log3(`table "${safe}" not found, creating`);
1050
+ await this.createTableWithRetry(buildCreateTableSql(safe, SESSIONS_COLUMNS), safe);
1051
+ log3(`table "${safe}" created`);
1052
+ if (!tables.includes(safe))
1053
+ this._tablesCache = [...tables, safe];
1054
+ }
1055
+ await this.healSchema(safe, SESSIONS_COLUMNS);
1056
+ await this.ensureLookupIndex(safe, "path_creation_date", `("path", "creation_date")`);
1057
+ }
1058
+ /**
1059
+ * Create the skills table.
1060
+ *
1061
+ * One row per skill version. Workers INSERT a fresh row on every KEEP /
1062
+ * MERGE rather than UPDATE-ing in place, so the full version history is
1063
+ * recoverable. Uniqueness in the *current* state is by (project_key, name)
1064
+ * — newer rows shadow older ones at read time (ORDER BY version DESC).
1065
+ * This sidesteps the Deeplake UPDATE-coalescing quirk that bit the wiki
1066
+ * worker.
1067
+ */
1068
+ /**
1069
+ * Create the codebase table. One row per (org, workspace, repo, user,
1070
+ * worktree, commit) — see CODEBASE_COLUMNS for the schema. Healing
1071
+ * + index follow the same pattern as ensureSessionsTable.
1072
+ */
1073
+ async ensureCodebaseTable(name) {
1074
+ const safe = sqlIdent(name);
1075
+ const tables = await this.listTables();
1076
+ if (!tables.includes(safe)) {
1077
+ log3(`table "${safe}" not found, creating`);
1078
+ await this.createTableWithRetry(buildCreateTableSql(safe, CODEBASE_COLUMNS), safe);
1079
+ log3(`table "${safe}" created`);
1080
+ if (!tables.includes(safe))
1081
+ this._tablesCache = [...tables, safe];
1082
+ }
1083
+ await this.healSchema(safe, CODEBASE_COLUMNS);
1084
+ await this.ensureLookupIndex(safe, "codebase_identity", `("org_id", "workspace_id", "repo_slug", "user_id", "worktree_id", "commit_sha")`);
1085
+ }
1086
+ async ensureSkillsTable(name) {
1087
+ const safe = sqlIdent(name);
1088
+ const tables = await this.listTables();
1089
+ if (!tables.includes(safe)) {
1090
+ log3(`table "${safe}" not found, creating`);
1091
+ await this.createTableWithRetry(buildCreateTableSql(safe, SKILLS_COLUMNS), safe);
1092
+ log3(`table "${safe}" created`);
1093
+ if (!tables.includes(safe))
1094
+ this._tablesCache = [...tables, safe];
1095
+ }
1096
+ await this.healSchema(safe, SKILLS_COLUMNS);
1097
+ await this.ensureLookupIndex(safe, "project_key_name", `("project_key", "name")`);
1098
+ }
1099
+ /**
1100
+ * Create the rules table.
1101
+ *
1102
+ * One row per rule version (same write pattern as skills): edits INSERT
1103
+ * a fresh row with version+1, reads pick latest per rule_id via
1104
+ * `ORDER BY version DESC LIMIT 1`. Sidesteps the Deeplake
1105
+ * UPDATE-coalescing quirk by never UPDATEing.
1106
+ */
1107
+ async ensureRulesTable(name) {
1108
+ const safe = sqlIdent(name);
1109
+ const tables = await this.listTables();
1110
+ if (!tables.includes(safe)) {
1111
+ log3(`table "${safe}" not found, creating`);
1112
+ await this.createTableWithRetry(buildCreateTableSql(safe, RULES_COLUMNS), safe);
1113
+ log3(`table "${safe}" created`);
1114
+ if (!tables.includes(safe))
1115
+ this._tablesCache = [...tables, safe];
1116
+ }
1117
+ await this.healSchema(safe, RULES_COLUMNS);
1118
+ await this.ensureLookupIndex(safe, "rule_id_version", `("rule_id", "version")`);
1119
+ }
1120
+ /**
1121
+ * Create the goals table.
1122
+ *
1123
+ * Backed by the VFS path convention memory/goal/<owner>/<status>/<goal_id>.md.
1124
+ * INSERT-only version-bumped: rm and mv operations translate to fresh
1125
+ * v=N+1 rows (status flips for mv → closed; rm is the same soft-close).
1126
+ * The (goal_id, version) index lets the VFS dispatch a cheap latest-row
1127
+ * read on cat / Read of a single goal.
1128
+ */
1129
+ async ensureGoalsTable(name) {
1130
+ const safe = sqlIdent(name);
1131
+ const tables = await this.listTables();
1132
+ if (!tables.includes(safe)) {
1133
+ log3(`table "${safe}" not found, creating`);
1134
+ await this.createTableWithRetry(buildCreateTableSql(safe, GOALS_COLUMNS), safe);
1135
+ log3(`table "${safe}" created`);
1136
+ if (!tables.includes(safe))
1137
+ this._tablesCache = [...tables, safe];
1138
+ }
1139
+ await this.healSchema(safe, GOALS_COLUMNS);
1140
+ await this.ensureLookupIndex(safe, "goal_id_version", `("goal_id", "version")`);
1141
+ await this.ensureLookupIndex(safe, "owner_status", `("owner", "status")`);
1142
+ }
1143
+ /**
1144
+ * Create the kpis table.
1145
+ *
1146
+ * Backed by memory/kpi/<goal_id>/<kpi_id>.md. KPI rows do NOT carry
1147
+ * owner — ownership derives from the parent goal via logical join on
1148
+ * goal_id. INSERT-only version-bumped. (goal_id, kpi_id) index is the
1149
+ * canonical lookup the VFS uses on Read and Write.
1150
+ */
1151
+ async ensureKpisTable(name) {
1152
+ const safe = sqlIdent(name);
1153
+ const tables = await this.listTables();
1154
+ if (!tables.includes(safe)) {
1155
+ log3(`table "${safe}" not found, creating`);
1156
+ await this.createTableWithRetry(buildCreateTableSql(safe, KPIS_COLUMNS), safe);
1157
+ log3(`table "${safe}" created`);
1158
+ if (!tables.includes(safe))
1159
+ this._tablesCache = [...tables, safe];
1160
+ }
1161
+ await this.healSchema(safe, KPIS_COLUMNS);
1162
+ await this.ensureLookupIndex(safe, "goal_id_kpi_id", `("goal_id", "kpi_id")`);
1163
+ }
1164
+ };
1165
+
1166
+ // dist/src/graph/deeplake-push.js
1167
+ async function pushSnapshot(snapshot, worktreeId, deps = {}) {
1168
+ if (process.env.HIVEMIND_GRAPH_PUSH === "0") {
1169
+ return { kind: "skipped-disabled" };
1170
+ }
1171
+ const config = (deps.loadConfig ?? loadConfig)();
1172
+ if (config === null) {
1173
+ return { kind: "skipped-no-auth" };
1174
+ }
1175
+ const commitSha = snapshot.graph.commit_sha;
1176
+ if (commitSha === null) {
1177
+ return { kind: "skipped-no-commit" };
1178
+ }
1179
+ const api = (deps.makeApi ?? defaultMakeApi)(config);
1180
+ try {
1181
+ await api.ensureCodebaseTable(config.codebaseTableName);
1182
+ } catch (err) {
1183
+ return errorOutcome("ensureCodebaseTable", err);
1184
+ }
1185
+ const snapshotSha256 = computeSnapshotSha256(snapshot);
1186
+ const tableId = sqlIdent(config.codebaseTableName);
1187
+ const repoSlug = snapshot.graph.repo_key;
1188
+ const userId = config.userName;
1189
+ const selectSql = `SELECT snapshot_sha256 FROM "${tableId}" WHERE org_id = '${sqlStr(config.orgId)}' AND workspace_id = '${sqlStr(config.workspaceId)}' AND repo_slug = '${sqlStr(repoSlug)}' AND user_id = '${sqlStr(userId)}' AND worktree_id = '${sqlStr(worktreeId)}' AND commit_sha = '${sqlStr(commitSha)}'`;
1190
+ let existing;
1191
+ try {
1192
+ existing = await api.query(selectSql);
1193
+ } catch (err) {
1194
+ return errorOutcome("SELECT existing", err);
1195
+ }
1196
+ if (existing.length > 0) {
1197
+ const cloudSha = String(existing[0].snapshot_sha256 ?? "");
1198
+ if (cloudSha === snapshotSha256) {
1199
+ return { kind: "already-current", commitSha };
1200
+ }
1201
+ return {
1202
+ kind: "drift",
1203
+ commitSha,
1204
+ localSha256: snapshotSha256,
1205
+ cloudSha256: cloudSha
1206
+ };
1207
+ }
1208
+ const canonical = canonicalJSON(snapshot);
1209
+ const observation = snapshot.observation;
1210
+ const insertSql = `INSERT INTO "${tableId}" (org_id, workspace_id, repo_slug, user_id, worktree_id, commit_sha, parent_sha, branch, ts, pushed_by, snapshot_sha256, snapshot_jsonb, node_count, edge_count, generator, generator_version, schema_version) VALUES ('${sqlStr(config.orgId)}', '${sqlStr(config.workspaceId)}', '${sqlStr(repoSlug)}', '${sqlStr(userId)}', '${sqlStr(worktreeId)}', '${sqlStr(commitSha)}', '', '${sqlStr(observation.branch ?? "")}', '${sqlStr(observation.ts)}', '${sqlStr(userId)}', '${sqlStr(snapshotSha256)}', '${sqlStr(canonical)}', ${snapshot.nodes.length}, ${snapshot.links.length}, '${sqlStr(snapshot.graph.generator)}', '${sqlStr(observation.generator_version)}', ${snapshot.graph.schema_version})`;
1211
+ try {
1212
+ await api.query(insertSql);
1213
+ } catch (err) {
1214
+ return errorOutcome("INSERT", err);
1215
+ }
1216
+ try {
1217
+ const verify = await api.query(selectSql);
1218
+ if (verify.length > 1) {
1219
+ return { kind: "inserted-with-duplicate-race", commitSha, rowCount: verify.length };
1220
+ }
1221
+ } catch {
1222
+ }
1223
+ return { kind: "inserted", commitSha };
1224
+ }
1225
+ function defaultMakeApi(config) {
1226
+ return new DeeplakeApi(config.token, config.apiUrl, config.orgId, config.workspaceId, config.tableName);
1227
+ }
1228
+ function errorOutcome(stage, err) {
1229
+ const message = err instanceof Error ? err.message : String(err);
1230
+ return { kind: "error", message: `${stage}: ${message}` };
1231
+ }
1232
+ function computeSnapshotSha256(snapshot) {
1233
+ const stable = {
1234
+ directed: snapshot.directed,
1235
+ multigraph: snapshot.multigraph,
1236
+ graph: snapshot.graph,
1237
+ nodes: snapshot.nodes,
1238
+ links: snapshot.links
1239
+ };
1240
+ return createHash2("sha256").update(canonicalJSON(stable)).digest("hex");
1241
+ }
1242
+ function canonicalJSON(value) {
1243
+ return JSON.stringify(value, (_key, v) => {
1244
+ if (v !== null && typeof v === "object" && !Array.isArray(v)) {
1245
+ const sorted = {};
1246
+ for (const k of Object.keys(v).sort()) {
1247
+ sorted[k] = v[k];
1248
+ }
1249
+ return sorted;
1250
+ }
1251
+ return v;
1252
+ });
1253
+ }
1254
+
1255
+ // dist/src/graph/deeplake-pull.js
1256
+ import { execFileSync } from "node:child_process";
1257
+ import { createHash as createHash5 } from "node:crypto";
1258
+ import { existsSync as existsSync7, mkdirSync as mkdirSync9, renameSync as renameSync5, writeFileSync as writeFileSync8 } from "node:fs";
1259
+ import { dirname as dirname6, join as join12 } from "node:path";
1260
+
1261
+ // dist/src/utils/repo-identity.js
1262
+ import { execSync } from "node:child_process";
1263
+ import { createHash as createHash3 } from "node:crypto";
1264
+ import { basename, resolve as resolve2 } from "node:path";
1265
+ var DEFAULT_PORTS = {
1266
+ http: "80",
1267
+ https: "443",
1268
+ ssh: "22",
1269
+ git: "9418"
1270
+ };
1271
+ function normalizeGitRemoteUrl(url) {
1272
+ let s = url.trim();
1273
+ const schemeMatch = s.match(/^([a-z][a-z0-9+.-]*):\/\//i);
1274
+ const scheme = schemeMatch ? schemeMatch[1].toLowerCase() : null;
1275
+ if (schemeMatch)
1276
+ s = s.slice(schemeMatch[0].length);
1277
+ if (!scheme) {
1278
+ const scp = s.match(/^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/);
1279
+ if (scp)
1280
+ s = `${scp[1]}/${scp[2]}`;
1281
+ }
1282
+ s = s.replace(/^[^@/]+@/, "");
1283
+ if (scheme && DEFAULT_PORTS[scheme]) {
1284
+ s = s.replace(new RegExp(`^([^/]+):${DEFAULT_PORTS[scheme]}(/|$)`), "$1$2");
1285
+ }
1286
+ s = s.replace(/\.git\/?$/i, "");
1287
+ s = s.replace(/\/+$/, "");
1288
+ return s.toLowerCase();
1289
+ }
1290
+ function deriveProjectKey(cwd) {
1291
+ const absCwd = resolve2(cwd);
1292
+ const project = basename(absCwd) || "unknown";
1293
+ let signature = null;
1294
+ try {
1295
+ const raw = execSync("git config --get remote.origin.url", {
1296
+ cwd: absCwd,
1297
+ encoding: "utf-8",
1298
+ stdio: ["ignore", "pipe", "ignore"]
1299
+ }).trim();
1300
+ signature = raw ? normalizeGitRemoteUrl(raw) : null;
1301
+ } catch {
1302
+ }
1303
+ const input = signature ?? absCwd;
1304
+ const key = createHash3("sha1").update(input).digest("hex").slice(0, 16);
1305
+ return { key, project };
1306
+ }
1307
+
1308
+ // dist/src/graph/last-build.js
1309
+ import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync8, renameSync as renameSync3, writeFileSync as writeFileSync6 } from "node:fs";
1310
+ import { dirname as dirname3, join as join9 } from "node:path";
1311
+ function lastBuildPath(baseDir, worktreeId) {
1312
+ if (worktreeId !== void 0) {
1313
+ return join9(baseDir, "worktrees", worktreeId, ".last-build.json");
1314
+ }
1315
+ return join9(baseDir, ".last-build.json");
1316
+ }
1317
+ function writeLastBuild(baseDir, state, worktreeId) {
1318
+ const path = lastBuildPath(baseDir, worktreeId);
1319
+ try {
1320
+ mkdirSync6(dirname3(path), { recursive: true });
1321
+ const tmp = `${path}.tmp.${process.pid}.${Date.now()}`;
1322
+ writeFileSync6(tmp, JSON.stringify(state));
1323
+ renameSync3(tmp, path);
1324
+ } catch {
1325
+ }
1326
+ }
1327
+ function readLastBuild(baseDir, worktreeId) {
1328
+ let path = lastBuildPath(baseDir, worktreeId);
1329
+ if (!existsSync5(path)) {
1330
+ if (worktreeId === void 0)
1331
+ return null;
1332
+ const legacy = lastBuildPath(baseDir, void 0);
1333
+ if (!existsSync5(legacy))
1334
+ return null;
1335
+ path = legacy;
1336
+ }
1337
+ let raw;
1338
+ try {
1339
+ raw = readFileSync8(path, "utf8");
1340
+ } catch {
1341
+ return null;
1342
+ }
1343
+ let parsed;
1344
+ try {
1345
+ parsed = JSON.parse(raw);
1346
+ } catch {
1347
+ return null;
1348
+ }
1349
+ if (parsed === null || typeof parsed !== "object")
1350
+ return null;
1351
+ const o = parsed;
1352
+ if (typeof o.ts !== "number" || !Number.isFinite(o.ts))
1353
+ return null;
1354
+ if (o.commit_sha !== null && typeof o.commit_sha !== "string")
1355
+ return null;
1356
+ if (typeof o.snapshot_sha256 !== "string")
1357
+ return null;
1358
+ const out = { ts: o.ts, commit_sha: o.commit_sha, snapshot_sha256: o.snapshot_sha256 };
1359
+ if (typeof o.node_count === "number" && Number.isFinite(o.node_count) && o.node_count >= 0) {
1360
+ out.node_count = o.node_count;
1361
+ }
1362
+ if (typeof o.edge_count === "number" && Number.isFinite(o.edge_count) && o.edge_count >= 0) {
1363
+ out.edge_count = o.edge_count;
1364
+ }
1365
+ return out;
1366
+ }
1367
+
1368
+ // dist/src/graph/history.js
1369
+ import { appendFileSync as appendFileSync2, existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync9 } from "node:fs";
1370
+ import { dirname as dirname4, join as join10 } from "node:path";
1371
+ function historyPath(baseDir) {
1372
+ return join10(baseDir, "history.jsonl");
1373
+ }
1374
+ function appendHistoryEntry(baseDir, entry) {
1375
+ const path = historyPath(baseDir);
1376
+ try {
1377
+ mkdirSync7(dirname4(path), { recursive: true });
1378
+ appendFileSync2(path, JSON.stringify(entry) + "\n");
1379
+ } catch {
1380
+ }
1381
+ }
1382
+ function entryFromSnapshot(snapshot, snapshot_sha256, trigger) {
1383
+ return {
1384
+ ts: snapshot.observation.ts,
1385
+ commit_sha: snapshot.graph.commit_sha,
1386
+ snapshot_sha256,
1387
+ node_count: snapshot.nodes.length,
1388
+ edge_count: snapshot.links.length,
1389
+ trigger
1390
+ };
1391
+ }
1392
+
1393
+ // dist/src/graph/snapshot.js
1394
+ import { createHash as createHash4 } from "node:crypto";
1395
+ import { mkdirSync as mkdirSync8, renameSync as renameSync4, writeFileSync as writeFileSync7 } from "node:fs";
1396
+ import { homedir as homedir6 } from "node:os";
1397
+ import { dirname as dirname5, join as join11 } from "node:path";
1398
+
1399
+ // dist/src/graph/resolve/cross-file.js
1400
+ import { posix } from "node:path";
1401
+ var EXPORTABLE_KINDS = /* @__PURE__ */ new Set([
1402
+ "function",
1403
+ "class",
1404
+ "const",
1405
+ "interface",
1406
+ "type_alias",
1407
+ "enum"
1408
+ ]);
1409
+ var HERITAGE_KINDS = /* @__PURE__ */ new Set([
1410
+ "class",
1411
+ "interface",
1412
+ "type_alias",
1413
+ "enum"
1414
+ ]);
1415
+ function buildExportIndex(nodes) {
1416
+ const idx = /* @__PURE__ */ new Map();
1417
+ for (const n of nodes) {
1418
+ if (!n.exported || !EXPORTABLE_KINDS.has(n.kind))
1419
+ continue;
1420
+ let m = idx.get(n.source_file);
1421
+ if (!m) {
1422
+ m = /* @__PURE__ */ new Map();
1423
+ idx.set(n.source_file, m);
1424
+ }
1425
+ if (!m.has(n.label))
1426
+ m.set(n.label, n.id);
1427
+ }
1428
+ return idx;
1429
+ }
1430
+ function resolveCrossFileCalls(extractions, nodes) {
1431
+ const knownFiles = /* @__PURE__ */ new Set();
1432
+ for (const ex of extractions)
1433
+ knownFiles.add(ex.source_file);
1434
+ const exportIndex = buildExportIndex(nodes);
1435
+ const edges = [];
1436
+ const seen = /* @__PURE__ */ new Set();
1437
+ for (const ex of extractions) {
1438
+ const rawCalls = ex.raw_calls ?? [];
1439
+ const bindings = ex.import_bindings ?? [];
1440
+ if (rawCalls.length === 0 || bindings.length === 0)
1441
+ continue;
1442
+ const byLocal = /* @__PURE__ */ new Map();
1443
+ for (const b of bindings) {
1444
+ if (!byLocal.has(b.local_name))
1445
+ byLocal.set(b.local_name, b);
1446
+ }
1447
+ for (const rc of rawCalls) {
1448
+ const target = resolveOne(rc, byLocal, ex.source_file, knownFiles, exportIndex);
1449
+ if (target === null)
1450
+ continue;
1451
+ const key = `${rc.caller_id}\0${target}`;
1452
+ if (seen.has(key))
1453
+ continue;
1454
+ seen.add(key);
1455
+ edges.push({
1456
+ source: rc.caller_id,
1457
+ target,
1458
+ relation: "calls",
1459
+ confidence: "EXTRACTED"
1460
+ });
1461
+ }
1462
+ }
1463
+ return edges;
1464
+ }
1465
+ function resolveOne(rc, byLocal, fromFile, knownFiles, exportIndex) {
1466
+ let binding;
1467
+ let exportName;
1468
+ if (rc.receiver !== void 0) {
1469
+ binding = byLocal.get(rc.receiver);
1470
+ if (binding === void 0 || binding.kind !== "namespace")
1471
+ return null;
1472
+ if (binding.type_only)
1473
+ return null;
1474
+ exportName = rc.callee_name;
1475
+ } else {
1476
+ binding = byLocal.get(rc.callee_name);
1477
+ if (binding === void 0)
1478
+ return null;
1479
+ if (binding.type_only)
1480
+ return null;
1481
+ if (binding.kind !== "named")
1482
+ return null;
1483
+ exportName = binding.imported_name;
1484
+ }
1485
+ const targetFile = resolveModule(fromFile, binding.specifier, knownFiles);
1486
+ if (targetFile === null)
1487
+ return null;
1488
+ return exportIndex.get(targetFile)?.get(exportName) ?? null;
1489
+ }
1490
+ var MODULE_SUFFIX = "::module";
1491
+ var EXTERNAL_PREFIX = "external:";
1492
+ function repointImportEdges(links, knownFiles) {
1493
+ return links.map((e) => {
1494
+ if (e.relation !== "imports" || !e.target.startsWith(EXTERNAL_PREFIX))
1495
+ return e;
1496
+ if (!e.source.endsWith(MODULE_SUFFIX))
1497
+ return e;
1498
+ const fromFile = e.source.slice(0, -MODULE_SUFFIX.length);
1499
+ const specifier = e.target.slice(EXTERNAL_PREFIX.length);
1500
+ const resolved = resolveModule(fromFile, specifier, knownFiles);
1501
+ if (resolved === null)
1502
+ return e;
1503
+ return { ...e, target: `${resolved}${MODULE_SUFFIX}` };
1504
+ });
1505
+ }
1506
+ var UNRESOLVED_PREFIX = "unresolved:";
1507
+ function resolveHeritageEdges(links, extractions, nodes) {
1508
+ const knownFiles = /* @__PURE__ */ new Set();
1509
+ for (const ex of extractions)
1510
+ knownFiles.add(ex.source_file);
1511
+ const exportIndex = buildExportIndex(nodes);
1512
+ const localIndex = /* @__PURE__ */ new Map();
1513
+ for (const n of nodes) {
1514
+ if (!HERITAGE_KINDS.has(n.kind))
1515
+ continue;
1516
+ let m = localIndex.get(n.source_file);
1517
+ if (!m) {
1518
+ m = /* @__PURE__ */ new Map();
1519
+ localIndex.set(n.source_file, m);
1520
+ }
1521
+ if (!m.has(n.label))
1522
+ m.set(n.label, n.id);
1523
+ }
1524
+ const bindingsByFile = /* @__PURE__ */ new Map();
1525
+ for (const ex of extractions) {
1526
+ const m = /* @__PURE__ */ new Map();
1527
+ for (const b of ex.import_bindings ?? [])
1528
+ if (!m.has(b.local_name))
1529
+ m.set(b.local_name, b);
1530
+ bindingsByFile.set(ex.source_file, m);
1531
+ }
1532
+ return links.map((e) => {
1533
+ if (e.relation !== "extends" && e.relation !== "implements")
1534
+ return e;
1535
+ if (!e.target.startsWith(UNRESOLVED_PREFIX))
1536
+ return e;
1537
+ const parsed = parseUnresolved(e.target);
1538
+ if (parsed === null)
1539
+ return e;
1540
+ const { file, name } = parsed;
1541
+ const local = localIndex.get(file)?.get(name);
1542
+ if (local !== void 0)
1543
+ return { ...e, target: local };
1544
+ const binding = bindingsByFile.get(file)?.get(name);
1545
+ if (binding !== void 0 && binding.kind === "named") {
1546
+ const targetFile = resolveModule(file, binding.specifier, knownFiles);
1547
+ if (targetFile !== null) {
1548
+ const id = exportIndex.get(targetFile)?.get(binding.imported_name);
1549
+ if (id !== void 0)
1550
+ return { ...e, target: id };
1551
+ }
1552
+ }
1553
+ return e;
1554
+ });
1555
+ }
1556
+ function parseUnresolved(target) {
1557
+ const body = target.slice(UNRESOLVED_PREFIX.length);
1558
+ const lastColon = body.lastIndexOf(":");
1559
+ if (lastColon <= 0)
1560
+ return null;
1561
+ const rest = body.slice(0, lastColon);
1562
+ const nameColon = rest.lastIndexOf(":");
1563
+ if (nameColon <= 0)
1564
+ return null;
1565
+ const file = rest.slice(0, nameColon);
1566
+ const name = rest.slice(nameColon + 1);
1567
+ if (file.length === 0 || name.length === 0)
1568
+ return null;
1569
+ return { file, name };
1570
+ }
1571
+ function resolveModule(fromFile, specifier, knownFiles) {
1572
+ if (isPythonFile(fromFile))
1573
+ return resolvePythonModule(fromFile, specifier, knownFiles);
1574
+ if (!specifier.startsWith("./") && !specifier.startsWith("../"))
1575
+ return null;
1576
+ const baseDir = posix.dirname(fromFile);
1577
+ const explicit = specifier.match(/\.(tsx?|jsx?|mjs|cjs)$/)?.[0] ?? null;
1578
+ const stem = explicit ? specifier.slice(0, -explicit.length) : specifier;
1579
+ const joined = posix.normalize(posix.join(baseDir, stem));
1580
+ const TS_EXTS = [".ts", ".tsx"];
1581
+ const JS_EXTS = [".js", ".jsx", ".mjs", ".cjs"];
1582
+ const importerIsJs = /\.(jsx?|mjs|cjs)$/.test(fromFile);
1583
+ const primary = importerIsJs ? JS_EXTS : TS_EXTS;
1584
+ const secondary = importerIsJs ? TS_EXTS : JS_EXTS;
1585
+ const exts = [
1586
+ ...explicit ? [explicit] : [],
1587
+ ...primary,
1588
+ ...secondary
1589
+ ].filter((e, i, a) => a.indexOf(e) === i);
1590
+ for (const e of exts) {
1591
+ const c = `${joined}${e}`;
1592
+ if (knownFiles.has(c))
1593
+ return c;
1594
+ }
1595
+ for (const e of exts) {
1596
+ const c = `${joined}/index${e}`;
1597
+ if (knownFiles.has(c))
1598
+ return c;
1599
+ }
1600
+ return null;
1601
+ }
1602
+ var PY_EXTS = [".py", ".pyi"];
1603
+ function isPythonFile(p) {
1604
+ return p.endsWith(".py") || p.endsWith(".pyi");
1605
+ }
1606
+ function resolvePythonModule(fromFile, specifier, knownFiles) {
1607
+ let dots = 0;
1608
+ while (dots < specifier.length && specifier[dots] === ".")
1609
+ dots++;
1610
+ const tail = specifier.slice(dots);
1611
+ const segs = tail.length > 0 ? tail.split(".") : [];
1612
+ if (dots === 0) {
1613
+ if (segs.length === 0)
1614
+ return null;
1615
+ return matchPythonSuffix(segs.join("/"), knownFiles);
1616
+ }
1617
+ let dir = posix.dirname(fromFile);
1618
+ let climbed = 1;
1619
+ for (; climbed < dots && dir !== "" && dir !== "."; climbed++)
1620
+ dir = posix.dirname(dir);
1621
+ if (climbed < dots)
1622
+ return null;
1623
+ const base = segs.length > 0 ? posix.normalize(posix.join(dir, ...segs)) : dir;
1624
+ for (const e of PY_EXTS)
1625
+ if (knownFiles.has(`${base}${e}`))
1626
+ return `${base}${e}`;
1627
+ for (const e of PY_EXTS)
1628
+ if (knownFiles.has(`${base}/__init__${e}`))
1629
+ return `${base}/__init__${e}`;
1630
+ return null;
1631
+ }
1632
+ function matchPythonSuffix(suffix, knownFiles) {
1633
+ const targets = [
1634
+ ...PY_EXTS.map((e) => `${suffix}${e}`),
1635
+ ...PY_EXTS.map((e) => `${suffix}/__init__${e}`)
1636
+ ];
1637
+ for (const t of targets) {
1638
+ if (knownFiles.has(t))
1639
+ return t;
1640
+ let hit = null;
1641
+ let count = 0;
1642
+ for (const f of knownFiles) {
1643
+ if (f.endsWith(`/${t}`)) {
1644
+ hit = f;
1645
+ count++;
1646
+ }
1647
+ }
1648
+ if (count === 1)
1649
+ return hit;
1650
+ if (count > 1)
1651
+ return null;
1652
+ }
1653
+ return null;
1654
+ }
1655
+
1656
+ // dist/src/graph/node-metadata.js
1657
+ function annotateNodeDegrees(nodes, links) {
1658
+ const inDeg = /* @__PURE__ */ new Map();
1659
+ const outDeg = /* @__PURE__ */ new Map();
1660
+ for (const e of links) {
1661
+ outDeg.set(e.source, (outDeg.get(e.source) ?? 0) + 1);
1662
+ inDeg.set(e.target, (inDeg.get(e.target) ?? 0) + 1);
1663
+ }
1664
+ for (const n of nodes) {
1665
+ const fi = inDeg.get(n.id) ?? 0;
1666
+ const fo = outDeg.get(n.id) ?? 0;
1667
+ n.fan_in = fi;
1668
+ n.fan_out = fo;
1669
+ n.is_entrypoint = n.exported && fi === 0;
1670
+ }
1671
+ }
1672
+
1673
+ // dist/src/graph/snapshot.js
1674
+ function graphsRoot() {
1675
+ return process.env.HIVEMIND_GRAPHS_HOME ?? join11(homedir6(), ".hivemind", "graphs");
1676
+ }
1677
+ function repoDir(repoKey) {
1678
+ return join11(graphsRoot(), repoKey);
1679
+ }
1680
+ function buildSnapshot(extractions, metadata, observation) {
1681
+ const nodes = [];
1682
+ const links = [];
1683
+ for (const ex of extractions) {
1684
+ for (const n of ex.nodes)
1685
+ nodes.push(n);
1686
+ for (const e of ex.edges)
1687
+ links.push(e);
1688
+ }
1689
+ for (const e of resolveCrossFileCalls(extractions, nodes))
1690
+ links.push(e);
1691
+ const knownFiles = /* @__PURE__ */ new Set();
1692
+ for (const ex of extractions)
1693
+ knownFiles.add(ex.source_file);
1694
+ let resolvedLinks = repointImportEdges(links, knownFiles);
1695
+ resolvedLinks = resolveHeritageEdges(resolvedLinks, extractions, nodes);
1696
+ annotateNodeDegrees(nodes, resolvedLinks);
1697
+ nodes.sort(compareNodes);
1698
+ resolvedLinks.sort(compareEdges);
1699
+ return {
1700
+ directed: true,
1701
+ multigraph: true,
1702
+ graph: metadata,
1703
+ observation,
1704
+ nodes,
1705
+ links: resolvedLinks
1706
+ };
1707
+ }
1708
+ function compareNodes(a, b) {
1709
+ return cmp(a.id, b.id);
1710
+ }
1711
+ function compareEdges(a, b) {
1712
+ let c = cmp(a.source, b.source);
1713
+ if (c !== 0)
1714
+ return c;
1715
+ c = cmp(a.target, b.target);
1716
+ if (c !== 0)
1717
+ return c;
1718
+ c = cmp(a.relation, b.relation);
1719
+ if (c !== 0)
1720
+ return c;
1721
+ return (a.ord ?? 0) - (b.ord ?? 0);
1722
+ }
1723
+ function cmp(a, b) {
1724
+ return a < b ? -1 : a > b ? 1 : 0;
1725
+ }
1726
+ function canonicalSnapshot(snapshot) {
1727
+ return canonicalJSON2(snapshot);
1728
+ }
1729
+ function computeSnapshotSha2562(snapshot) {
1730
+ const stable = {
1731
+ directed: snapshot.directed,
1732
+ multigraph: snapshot.multigraph,
1733
+ graph: snapshot.graph,
1734
+ nodes: snapshot.nodes,
1735
+ links: snapshot.links
1736
+ };
1737
+ return createHash4("sha256").update(canonicalJSON2(stable)).digest("hex");
1738
+ }
1739
+ function canonicalJSON2(value) {
1740
+ return JSON.stringify(value, (_key, v) => {
1741
+ if (v !== null && typeof v === "object" && !Array.isArray(v)) {
1742
+ const sorted = {};
1743
+ for (const k of Object.keys(v).sort()) {
1744
+ sorted[k] = v[k];
1745
+ }
1746
+ return sorted;
1747
+ }
1748
+ return v;
1749
+ });
1750
+ }
1751
+ function writeSnapshot(snapshot, baseDir, trigger = "unknown", worktreeId) {
1752
+ const sha256 = computeSnapshotSha2562(snapshot);
1753
+ const commitSha = snapshot.graph.commit_sha;
1754
+ const fileBase = commitSha ?? sha256;
1755
+ const snapshotsDir = join11(baseDir, "snapshots");
1756
+ const snapshotPath = join11(snapshotsDir, `${fileBase}.json`);
1757
+ const canonical = canonicalSnapshot(snapshot);
1758
+ writeFileAtomic(snapshotPath, canonical);
1759
+ const worktreeRoot = worktreeId !== void 0 ? join11(baseDir, "worktrees", worktreeId) : baseDir;
1760
+ let latestCommitPath = null;
1761
+ if (commitSha !== null) {
1762
+ latestCommitPath = join11(worktreeRoot, "latest-commit.txt");
1763
+ writeFileAtomic(latestCommitPath, `${commitSha}
1764
+ `);
1765
+ }
1766
+ writeLastBuild(baseDir, {
1767
+ ts: Date.now(),
1768
+ commit_sha: commitSha,
1769
+ snapshot_sha256: sha256,
1770
+ node_count: snapshot.nodes.length,
1771
+ edge_count: snapshot.links.length
1772
+ }, worktreeId);
1773
+ appendHistoryEntry(baseDir, entryFromSnapshot(snapshot, sha256, trigger));
1774
+ return { snapshotPath, latestCommitPath, snapshotSha256: sha256 };
1775
+ }
1776
+ function writeFileAtomic(filePath, contents) {
1777
+ mkdirSync8(dirname5(filePath), { recursive: true });
1778
+ const tmp = `${filePath}.tmp.${process.pid}.${Date.now()}`;
1779
+ writeFileSync7(tmp, contents);
1780
+ renameSync4(tmp, filePath);
1781
+ }
1782
+
1783
+ // dist/src/graph/diff.js
1784
+ import { existsSync as existsSync8, readFileSync as readFileSync10 } from "node:fs";
1785
+ import { join as join13 } from "node:path";
1786
+
1787
+ // dist/src/graph/extract/typescript.js
1788
+ import Parser from "tree-sitter";
1789
+ import TypeScript from "tree-sitter-typescript";
1790
+ var _typescriptParser = null;
1791
+ var _tsxParser = null;
1792
+ function getTypescriptParser() {
1793
+ if (_typescriptParser === null) {
1794
+ _typescriptParser = new Parser();
1795
+ _typescriptParser.setLanguage(TypeScript.typescript);
1796
+ }
1797
+ return _typescriptParser;
1798
+ }
1799
+ function getTsxParser() {
1800
+ if (_tsxParser === null) {
1801
+ _tsxParser = new Parser();
1802
+ _tsxParser.setLanguage(TypeScript.tsx);
1803
+ }
1804
+ return _tsxParser;
1805
+ }
1806
+ function pickParserForPath(relativePath) {
1807
+ return relativePath.endsWith(".tsx") || relativePath.endsWith(".jsx") ? getTsxParser() : getTypescriptParser();
1808
+ }
1809
+ function extractTypeScript(sourceCode, relativePath) {
1810
+ const parser = pickParserForPath(relativePath);
1811
+ const CHUNK_BYTES2 = 16384;
1812
+ const tree = parser.parse((index) => {
1813
+ if (index >= sourceCode.length)
1814
+ return null;
1815
+ return sourceCode.slice(index, index + CHUNK_BYTES2);
1816
+ });
1817
+ const root = tree.rootNode;
1818
+ const result = {
1819
+ source_file: relativePath,
1820
+ language: "typescript",
1821
+ nodes: [],
1822
+ edges: [],
1823
+ parse_errors: [],
1824
+ raw_calls: [],
1825
+ import_bindings: []
1826
+ };
1827
+ collectParseErrors(root, relativePath, result.parse_errors);
1828
+ const moduleNode = makeModuleNode(relativePath);
1829
+ result.nodes.push(moduleNode);
1830
+ const declByName = /* @__PURE__ */ new Map();
1831
+ extractDeclarations(root, relativePath, result, declByName, moduleNode);
1832
+ extractImports(root, relativePath, result, moduleNode);
1833
+ extractCalls(root, relativePath, result, declByName);
1834
+ if (isJavaScriptPath(relativePath)) {
1835
+ result.language = "javascript";
1836
+ for (const n of result.nodes)
1837
+ n.language = "javascript";
1838
+ }
1839
+ return result;
1840
+ }
1841
+ function isJavaScriptPath(relativePath) {
1842
+ return /\.(jsx?|mjs|cjs)$/.test(relativePath);
1843
+ }
1844
+ function collectParseErrors(node, relativePath, out) {
1845
+ if (node.isError || node.isMissing) {
1846
+ out.push({
1847
+ source_file: relativePath,
1848
+ message: node.isMissing ? `missing node: ${node.type}` : `parse error at ${locationStr(node)}`,
1849
+ location: locationStr(node)
1850
+ });
1851
+ return;
1852
+ }
1853
+ for (let i = 0; i < node.namedChildCount; i++) {
1854
+ const child = node.namedChild(i);
1855
+ if (child !== null)
1856
+ collectParseErrors(child, relativePath, out);
1857
+ }
1858
+ }
1859
+ function extractDeclarations(node, relativePath, result, declByName, moduleNode) {
1860
+ for (let i = 0; i < node.namedChildCount; i++) {
1861
+ const child = node.namedChild(i);
1862
+ if (child === null)
1863
+ continue;
1864
+ const { decl, exported } = unwrapExport(child);
1865
+ if (decl !== null) {
1866
+ handleDeclaration(decl, exported, relativePath, result, declByName, moduleNode);
1867
+ }
1868
+ if (child.type === "internal_module" || child.type === "module") {
1869
+ extractDeclarations(child, relativePath, result, declByName, moduleNode);
1870
+ }
1871
+ }
1872
+ }
1873
+ function unwrapExport(node) {
1874
+ if (node.type === "export_statement") {
1875
+ const decl = node.childForFieldName("declaration") ?? firstNamedChildOfTypes(node, [
1876
+ "function_declaration",
1877
+ "class_declaration",
1878
+ "interface_declaration",
1879
+ "type_alias_declaration",
1880
+ "enum_declaration",
1881
+ "lexical_declaration"
1882
+ ]);
1883
+ return { decl, exported: true };
1884
+ }
1885
+ return { decl: node, exported: false };
1886
+ }
1887
+ function handleDeclaration(node, exported, relativePath, result, declByName, moduleNode) {
1888
+ switch (node.type) {
1889
+ case "function_declaration": {
1890
+ const name = textOfField(node, "name");
1891
+ if (name === null)
1892
+ return;
1893
+ const decl = makeNode(relativePath, name, "function", node, exported);
1894
+ pushNode(result, declByName, decl);
1895
+ return;
1896
+ }
1897
+ case "class_declaration": {
1898
+ const name = textOfField(node, "name");
1899
+ if (name === null)
1900
+ return;
1901
+ const classNode = makeNode(relativePath, name, "class", node, exported);
1902
+ pushNode(result, declByName, classNode);
1903
+ const heritage = firstNamedChildOfTypes(node, ["class_heritage"]);
1904
+ if (heritage !== null) {
1905
+ for (let i = 0; i < heritage.namedChildCount; i++) {
1906
+ const clause = heritage.namedChild(i);
1907
+ if (clause === null)
1908
+ continue;
1909
+ const relation = clause.type === "extends_clause" ? "extends" : clause.type === "implements_clause" ? "implements" : null;
1910
+ if (relation === null)
1911
+ continue;
1912
+ for (let j = 0; j < clause.namedChildCount; j++) {
1913
+ const base = clause.namedChild(j);
1914
+ if (base === null)
1915
+ continue;
1916
+ const baseName = base.text;
1917
+ if (baseName.length === 0)
1918
+ continue;
1919
+ result.edges.push({
1920
+ source: classNode.id,
1921
+ target: nodeIdUnresolved(relativePath, baseName, relation === "extends" ? "class" : "interface"),
1922
+ relation,
1923
+ confidence: "EXTRACTED"
1924
+ });
1925
+ }
1926
+ }
1927
+ }
1928
+ const body = firstNamedChildOfTypes(node, ["class_body"]);
1929
+ if (body !== null) {
1930
+ for (let i = 0; i < body.namedChildCount; i++) {
1931
+ const member = body.namedChild(i);
1932
+ if (member === null)
1933
+ continue;
1934
+ if (member.type === "method_definition") {
1935
+ const methodName = textOfField(member, "name");
1936
+ if (methodName === null)
1937
+ continue;
1938
+ const accessibility = firstNamedChildOfTypes(member, ["accessibility_modifier"]);
1939
+ const isHardPrivate = firstNamedChildOfTypes(member, ["private_property_identifier"]) !== null;
1940
+ const isPublic2 = !isHardPrivate && (accessibility === null || accessibility.text === "public");
1941
+ const methodExported = exported && isPublic2;
1942
+ const methodKey = `${classNode.label}.${methodName}`;
1943
+ const methodNode = makeNodeWithExplicitLabel(relativePath, methodKey, methodName, "method", member, methodExported);
1944
+ pushNode(result, declByName, methodNode, methodKey);
1945
+ result.edges.push({
1946
+ source: classNode.id,
1947
+ target: methodNode.id,
1948
+ relation: "method_of",
1949
+ confidence: "EXTRACTED"
1950
+ });
1951
+ }
1952
+ }
1953
+ }
1954
+ return;
1955
+ }
1956
+ case "interface_declaration": {
1957
+ const name = textOfField(node, "name");
1958
+ if (name === null)
1959
+ return;
1960
+ const decl = makeNode(relativePath, name, "interface", node, exported);
1961
+ pushNode(result, declByName, decl);
1962
+ return;
1963
+ }
1964
+ case "type_alias_declaration": {
1965
+ const name = textOfField(node, "name");
1966
+ if (name === null)
1967
+ return;
1968
+ const decl = makeNode(relativePath, name, "type_alias", node, exported);
1969
+ pushNode(result, declByName, decl);
1970
+ return;
1971
+ }
1972
+ case "enum_declaration": {
1973
+ const name = textOfField(node, "name");
1974
+ if (name === null)
1975
+ return;
1976
+ const decl = makeNode(relativePath, name, "enum", node, exported);
1977
+ pushNode(result, declByName, decl);
1978
+ return;
1979
+ }
1980
+ case "lexical_declaration": {
1981
+ for (let i = 0; i < node.namedChildCount; i++) {
1982
+ const declarator = node.namedChild(i);
1983
+ if (declarator === null || declarator.type !== "variable_declarator")
1984
+ continue;
1985
+ const ident = declarator.childForFieldName("name");
1986
+ if (ident === null || ident.type !== "identifier")
1987
+ continue;
1988
+ const decl = makeNode(relativePath, ident.text, "const", declarator, exported);
1989
+ pushNode(result, declByName, decl);
1990
+ }
1991
+ return;
1992
+ }
1993
+ }
1994
+ }
1995
+ function extractImports(node, relativePath, result, moduleNode) {
1996
+ if (node.type === "import_statement") {
1997
+ const src = firstNamedChildOfTypes(node, ["string"]);
1998
+ if (src !== null) {
1999
+ const frag = firstNamedChildOfTypes(src, ["string_fragment"]);
2000
+ const specifier = (frag !== null ? frag.text : src.text).replace(/^['"]|['"]$/g, "");
2001
+ if (specifier.length > 0) {
2002
+ result.edges.push({
2003
+ source: moduleNode.id,
2004
+ target: `external:${specifier}`,
2005
+ relation: "imports",
2006
+ confidence: "EXTRACTED"
2007
+ });
2008
+ extractImportBindings(node, specifier, result);
2009
+ }
2010
+ }
2011
+ return;
2012
+ }
2013
+ for (let i = 0; i < node.namedChildCount; i++) {
2014
+ const child = node.namedChild(i);
2015
+ if (child !== null)
2016
+ extractImports(child, relativePath, result, moduleNode);
2017
+ }
2018
+ }
2019
+ function extractImportBindings(importStmt, specifier, result) {
2020
+ const stmtTypeOnly = /^import\s+type\b/.test(importStmt.text.trimStart());
2021
+ const clause = firstNamedChildOfTypes(importStmt, ["import_clause"]);
2022
+ if (clause === null)
2023
+ return;
2024
+ const push = (b) => {
2025
+ result.import_bindings.push({ ...b, specifier });
2026
+ };
2027
+ for (let i = 0; i < clause.namedChildCount; i++) {
2028
+ const child = clause.namedChild(i);
2029
+ if (child === null)
2030
+ continue;
2031
+ if (child.type === "identifier") {
2032
+ push({ local_name: child.text, imported_name: "default", kind: "default", type_only: stmtTypeOnly });
2033
+ } else if (child.type === "namespace_import") {
2034
+ const id = firstNamedChildOfTypes(child, ["identifier"]);
2035
+ if (id !== null)
2036
+ push({ local_name: id.text, imported_name: "*", kind: "namespace", type_only: stmtTypeOnly });
2037
+ } else if (child.type === "named_imports") {
2038
+ for (let j = 0; j < child.namedChildCount; j++) {
2039
+ const spec = child.namedChild(j);
2040
+ if (spec === null || spec.type !== "import_specifier")
2041
+ continue;
2042
+ const specTypeOnly = stmtTypeOnly || /^type\s+(?!as\b)/.test(spec.text);
2043
+ const nameNode = spec.childForFieldName("name");
2044
+ const aliasNode = spec.childForFieldName("alias");
2045
+ const imported = nameNode !== null ? nameNode.text : null;
2046
+ if (imported === null)
2047
+ continue;
2048
+ const local = aliasNode !== null ? aliasNode.text : imported;
2049
+ push({ local_name: local, imported_name: imported, kind: "named", type_only: specTypeOnly });
2050
+ }
2051
+ }
2052
+ }
2053
+ }
2054
+ function extractCalls(node, relativePath, result, declByName) {
2055
+ if (node.type === "call_expression") {
2056
+ const callee = node.childForFieldName("function");
2057
+ if (callee !== null) {
2058
+ const callerNode = findEnclosingDeclaration(node, declByName);
2059
+ if (callerNode !== null) {
2060
+ const calleeKey = resolveCalleeKey(callee, declByName);
2061
+ const targetNode = calleeKey !== null ? declByName.get(calleeKey) : void 0;
2062
+ if (targetNode !== void 0) {
2063
+ result.edges.push({
2064
+ source: callerNode.id,
2065
+ target: targetNode.id,
2066
+ relation: "calls",
2067
+ confidence: "EXTRACTED"
2068
+ });
2069
+ } else {
2070
+ const rc = rawCallFromCallee(callee, callerNode.id);
2071
+ if (rc !== null)
2072
+ result.raw_calls.push(rc);
2073
+ }
2074
+ }
2075
+ }
2076
+ }
2077
+ for (let i = 0; i < node.namedChildCount; i++) {
2078
+ const child = node.namedChild(i);
2079
+ if (child !== null)
2080
+ extractCalls(child, relativePath, result, declByName);
2081
+ }
2082
+ }
2083
+ function rawCallFromCallee(callee, callerId) {
2084
+ if (callee.type === "identifier") {
2085
+ return { caller_id: callerId, callee_name: callee.text };
2086
+ }
2087
+ if (callee.type === "member_expression") {
2088
+ const object = callee.childForFieldName("object");
2089
+ const property = callee.childForFieldName("property");
2090
+ if (object !== null && object.type === "identifier" && property !== null && property.type === "property_identifier") {
2091
+ return { caller_id: callerId, callee_name: property.text, receiver: object.text };
2092
+ }
2093
+ }
2094
+ return null;
2095
+ }
2096
+ function resolveCalleeKey(callee, declByName) {
2097
+ if (callee.type === "identifier")
2098
+ return callee.text;
2099
+ if (callee.type === "member_expression") {
2100
+ const object = callee.childForFieldName("object");
2101
+ const property = callee.childForFieldName("property");
2102
+ if (object !== null && object.type === "this" && property !== null && property.type === "property_identifier") {
2103
+ const className = findEnclosingClassName(callee);
2104
+ if (className !== null)
2105
+ return `${className}.${property.text}`;
2106
+ }
2107
+ }
2108
+ return null;
2109
+ }
2110
+ function findEnclosingDeclaration(node, declByName) {
2111
+ let cur = node.parent;
2112
+ while (cur !== null) {
2113
+ if (cur.type === "function_declaration") {
2114
+ const name = textOfField(cur, "name");
2115
+ if (name !== null) {
2116
+ const n = declByName.get(name);
2117
+ if (n !== void 0)
2118
+ return n;
2119
+ }
2120
+ } else if (cur.type === "method_definition") {
2121
+ const methodName = textOfField(cur, "name");
2122
+ const className = findEnclosingClassName(cur);
2123
+ if (methodName !== null && className !== null) {
2124
+ const n = declByName.get(`${className}.${methodName}`);
2125
+ if (n !== void 0)
2126
+ return n;
2127
+ }
2128
+ } else if (cur.type === "variable_declarator") {
2129
+ const value = cur.childForFieldName("value");
2130
+ if (value?.type === "arrow_function" || value?.type === "function_expression") {
2131
+ const ident = cur.childForFieldName("name");
2132
+ if (ident !== null && ident.type === "identifier") {
2133
+ const n = declByName.get(ident.text);
2134
+ if (n !== void 0)
2135
+ return n;
2136
+ }
2137
+ }
2138
+ }
2139
+ cur = cur.parent;
2140
+ }
2141
+ return null;
2142
+ }
2143
+ function findEnclosingClassName(node) {
2144
+ let cur = node.parent;
2145
+ while (cur !== null) {
2146
+ if (cur.type === "class_declaration") {
2147
+ return textOfField(cur, "name");
2148
+ }
2149
+ cur = cur.parent;
2150
+ }
2151
+ return null;
2152
+ }
2153
+ function makeModuleNode(relativePath) {
2154
+ return {
2155
+ id: `${relativePath}::module`,
2156
+ label: relativePath,
2157
+ kind: "module",
2158
+ source_file: relativePath,
2159
+ source_location: "L1",
2160
+ language: "typescript",
2161
+ exported: false
2162
+ };
2163
+ }
2164
+ function makeNode(relativePath, name, kind, node, exported) {
2165
+ return {
2166
+ id: nodeId(relativePath, name, kind),
2167
+ label: name,
2168
+ kind,
2169
+ source_file: relativePath,
2170
+ source_location: locationStr(node),
2171
+ language: "typescript",
2172
+ exported,
2173
+ signature: signatureOf(node, kind)
2174
+ };
2175
+ }
2176
+ function signatureOf(node, kind) {
2177
+ const text = node.text;
2178
+ let end = text.length;
2179
+ const nl = text.indexOf("\n");
2180
+ if (nl >= 0)
2181
+ end = Math.min(end, nl);
2182
+ const cutsAtBody = kind === "function" || kind === "class" || kind === "method" || kind === "interface" || kind === "enum";
2183
+ if (cutsAtBody) {
2184
+ const body = node.childForFieldName("body");
2185
+ if (body !== null) {
2186
+ end = Math.min(end, body.startIndex - node.startIndex);
2187
+ } else {
2188
+ const brace = text.indexOf("{");
2189
+ if (brace >= 0)
2190
+ end = Math.min(end, brace);
2191
+ }
2192
+ }
2193
+ const sig = text.slice(0, end).replace(/\s+/g, " ").trim();
2194
+ const cps = [...sig];
2195
+ return cps.length > 120 ? `${cps.slice(0, 117).join("")}...` : sig;
2196
+ }
2197
+ function makeNodeWithExplicitLabel(relativePath, idName, label, kind, node, exported) {
2198
+ return {
2199
+ id: nodeId(relativePath, idName, kind),
2200
+ label,
2201
+ kind,
2202
+ source_file: relativePath,
2203
+ source_location: locationStr(node),
2204
+ language: "typescript",
2205
+ exported,
2206
+ signature: signatureOf(node, kind)
2207
+ };
2208
+ }
2209
+ function pushNode(result, declByName, node, lookupKey) {
2210
+ if (result.nodes.some((n) => n.id === node.id)) {
2211
+ if (!declByName.has(lookupKey ?? node.label)) {
2212
+ declByName.set(lookupKey ?? node.label, node);
2213
+ }
2214
+ return;
2215
+ }
2216
+ result.nodes.push(node);
2217
+ declByName.set(lookupKey ?? node.label, node);
2218
+ }
2219
+ function nodeId(relativePath, name, kind) {
2220
+ return `${relativePath}:${name}:${kind}`;
2221
+ }
2222
+ function nodeIdUnresolved(relativePath, name, kind) {
2223
+ return `unresolved:${relativePath}:${name}:${kind}`;
2224
+ }
2225
+ function locationStr(node) {
2226
+ const start = node.startPosition.row + 1;
2227
+ const end = node.endPosition.row + 1;
2228
+ return start === end ? `L${start}` : `L${start}-${end}`;
2229
+ }
2230
+ function textOfField(node, fieldName) {
2231
+ const child = node.childForFieldName(fieldName);
2232
+ if (child === null)
2233
+ return null;
2234
+ const text = child.text;
2235
+ return text.length > 0 ? text : null;
2236
+ }
2237
+ function firstNamedChildOfTypes(node, types) {
2238
+ for (let i = 0; i < node.namedChildCount; i++) {
2239
+ const child = node.namedChild(i);
2240
+ if (child !== null && types.includes(child.type))
2241
+ return child;
2242
+ }
2243
+ return null;
2244
+ }
2245
+
2246
+ // dist/src/graph/extract/python.js
2247
+ import Parser2 from "tree-sitter";
2248
+ import Python from "tree-sitter-python";
2249
+ var _pythonParser = null;
2250
+ function getPythonParser() {
2251
+ if (_pythonParser === null) {
2252
+ _pythonParser = new Parser2();
2253
+ _pythonParser.setLanguage(Python);
2254
+ }
2255
+ return _pythonParser;
2256
+ }
2257
+ var CHUNK_BYTES = 16384;
2258
+ function extractPython(sourceCode, relativePath) {
2259
+ const parser = getPythonParser();
2260
+ const tree = parser.parse((index) => index >= sourceCode.length ? null : sourceCode.slice(index, index + CHUNK_BYTES));
2261
+ const root = tree.rootNode;
2262
+ const result = {
2263
+ source_file: relativePath,
2264
+ language: "python",
2265
+ nodes: [],
2266
+ edges: [],
2267
+ parse_errors: [],
2268
+ raw_calls: [],
2269
+ import_bindings: []
2270
+ };
2271
+ collectParseErrors2(root, relativePath, result.parse_errors);
2272
+ const moduleNode = makeModuleNode2(relativePath);
2273
+ result.nodes.push(moduleNode);
2274
+ const declByName = /* @__PURE__ */ new Map();
2275
+ extractDeclarations2(
2276
+ root,
2277
+ relativePath,
2278
+ result,
2279
+ declByName,
2280
+ /*topLevel*/
2281
+ true
2282
+ );
2283
+ extractImports2(root, relativePath, result, moduleNode);
2284
+ extractCalls2(root, result, declByName);
2285
+ return result;
2286
+ }
2287
+ function collectParseErrors2(node, relativePath, out) {
2288
+ if (node.isError || node.isMissing) {
2289
+ out.push({ source_file: relativePath, message: node.isMissing ? `missing node: ${node.type}` : `parse error at ${loc(node)}`, location: loc(node) });
2290
+ return;
2291
+ }
2292
+ for (let i = 0; i < node.namedChildCount; i++) {
2293
+ const c = node.namedChild(i);
2294
+ if (c !== null)
2295
+ collectParseErrors2(c, relativePath, out);
2296
+ }
2297
+ }
2298
+ function extractDeclarations2(node, relativePath, result, declByName, topLevel) {
2299
+ for (let i = 0; i < node.namedChildCount; i++) {
2300
+ const child = node.namedChild(i);
2301
+ if (child === null)
2302
+ continue;
2303
+ if (child.type === "function_definition") {
2304
+ const name = textOfField2(child, "name");
2305
+ if (name !== null)
2306
+ pushNode2(result, declByName, makeNode2(relativePath, name, "function", child, isPublic(name)));
2307
+ } else if (child.type === "class_definition") {
2308
+ handleClass(child, relativePath, result, declByName);
2309
+ } else if (topLevel && child.type === "expression_statement") {
2310
+ const assign = firstOfType(child, "assignment");
2311
+ if (assign !== null) {
2312
+ const lhs = assign.childForFieldName("left");
2313
+ if (lhs !== null && lhs.type === "identifier") {
2314
+ pushNode2(result, declByName, makeNode2(relativePath, lhs.text, "const", assign, isPublic(lhs.text)));
2315
+ }
2316
+ }
2317
+ } else if (child.type === "decorated_definition") {
2318
+ extractDeclarations2(child, relativePath, result, declByName, topLevel);
2319
+ }
2320
+ }
2321
+ }
2322
+ function handleClass(node, relativePath, result, declByName) {
2323
+ const name = textOfField2(node, "name");
2324
+ if (name === null)
2325
+ return;
2326
+ const classNode = makeNode2(relativePath, name, "class", node, isPublic(name));
2327
+ pushNode2(result, declByName, classNode);
2328
+ const supers = node.childForFieldName("superclasses");
2329
+ if (supers !== null) {
2330
+ for (let i = 0; i < supers.namedChildCount; i++) {
2331
+ const base = supers.namedChild(i);
2332
+ if (base === null)
2333
+ continue;
2334
+ let baseName = null;
2335
+ if (base.type === "identifier")
2336
+ baseName = base.text;
2337
+ else if (base.type === "attribute") {
2338
+ const attr = base.childForFieldName("attribute");
2339
+ baseName = attr !== null ? attr.text : null;
2340
+ }
2341
+ if (baseName === null || baseName.length === 0)
2342
+ continue;
2343
+ result.edges.push({
2344
+ source: classNode.id,
2345
+ target: nodeIdUnresolved2(relativePath, baseName, "class"),
2346
+ relation: "extends",
2347
+ confidence: "EXTRACTED"
2348
+ });
2349
+ }
2350
+ }
2351
+ const body = node.childForFieldName("body");
2352
+ if (body !== null) {
2353
+ for (let i = 0; i < body.namedChildCount; i++) {
2354
+ let member = body.namedChild(i);
2355
+ if (member === null)
2356
+ continue;
2357
+ if (member.type === "decorated_definition")
2358
+ member = firstOfType(member, "function_definition");
2359
+ if (member === null || member.type !== "function_definition")
2360
+ continue;
2361
+ const mName = textOfField2(member, "name");
2362
+ if (mName === null)
2363
+ continue;
2364
+ const methodNode = makeNodeWithExplicitLabel2(relativePath, `${name}.${mName}`, mName, "method", member, isPublic(name) && isPublic(mName));
2365
+ pushNode2(result, declByName, methodNode);
2366
+ result.edges.push({ source: classNode.id, target: methodNode.id, relation: "method_of", confidence: "EXTRACTED" });
2367
+ }
2368
+ }
2369
+ }
2370
+ function extractImports2(node, relativePath, result, moduleNode) {
2371
+ if (node.type === "import_statement") {
2372
+ for (let i = 0; i < node.namedChildCount; i++) {
2373
+ const child = node.namedChild(i);
2374
+ if (child === null)
2375
+ continue;
2376
+ let modText = null;
2377
+ let local = null;
2378
+ if (child.type === "dotted_name") {
2379
+ modText = child.text;
2380
+ local = lastDottedSegment(child.text);
2381
+ } else if (child.type === "aliased_import") {
2382
+ const name = child.childForFieldName("name");
2383
+ const alias = child.childForFieldName("alias");
2384
+ if (name !== null) {
2385
+ modText = name.text;
2386
+ local = alias !== null ? alias.text : lastDottedSegment(name.text);
2387
+ }
2388
+ }
2389
+ if (modText !== null) {
2390
+ pushImportEdge(result, moduleNode, modText);
2391
+ if (local !== null)
2392
+ result.import_bindings.push({ local_name: local, imported_name: "*", kind: "namespace", specifier: modText });
2393
+ }
2394
+ }
2395
+ return;
2396
+ }
2397
+ if (node.type === "import_from_statement") {
2398
+ const modNode = node.childForFieldName("module_name");
2399
+ const modText = modNode !== null ? modNode.text : ".";
2400
+ pushImportEdge(result, moduleNode, modText);
2401
+ for (let i = 0; i < node.namedChildCount; i++) {
2402
+ const child = node.namedChild(i);
2403
+ if (child === null || child === modNode)
2404
+ continue;
2405
+ if (child.type === "dotted_name" || child.type === "identifier") {
2406
+ const imported = child.text;
2407
+ result.import_bindings.push({ local_name: lastDottedSegment(imported), imported_name: imported, kind: "named", specifier: modText });
2408
+ } else if (child.type === "aliased_import") {
2409
+ const name = child.childForFieldName("name");
2410
+ const alias = child.childForFieldName("alias");
2411
+ if (name !== null)
2412
+ result.import_bindings.push({ local_name: alias !== null ? alias.text : lastDottedSegment(name.text), imported_name: name.text, kind: "named", specifier: modText });
2413
+ }
2414
+ }
2415
+ return;
2416
+ }
2417
+ for (let i = 0; i < node.namedChildCount; i++) {
2418
+ const c = node.namedChild(i);
2419
+ if (c !== null)
2420
+ extractImports2(c, relativePath, result, moduleNode);
2421
+ }
2422
+ }
2423
+ function pushImportEdge(result, moduleNode, specifier) {
2424
+ if (specifier.length === 0)
2425
+ return;
2426
+ result.edges.push({ source: moduleNode.id, target: `external:${specifier}`, relation: "imports", confidence: "EXTRACTED" });
2427
+ }
2428
+ function extractCalls2(node, result, declByName) {
2429
+ if (node.type === "call") {
2430
+ const callee = node.childForFieldName("function");
2431
+ if (callee !== null) {
2432
+ const caller = findEnclosingDeclaration2(node, declByName);
2433
+ if (caller !== null) {
2434
+ const key = resolveCalleeKey2(callee);
2435
+ const target = key !== null ? declByName.get(key) : void 0;
2436
+ if (target !== void 0) {
2437
+ result.edges.push({ source: caller.id, target: target.id, relation: "calls", confidence: "EXTRACTED" });
2438
+ } else {
2439
+ const rc = rawCallFromCallee2(callee, caller.id);
2440
+ if (rc !== null)
2441
+ result.raw_calls.push(rc);
2442
+ }
2443
+ }
2444
+ }
2445
+ }
2446
+ for (let i = 0; i < node.namedChildCount; i++) {
2447
+ const c = node.namedChild(i);
2448
+ if (c !== null)
2449
+ extractCalls2(c, result, declByName);
2450
+ }
2451
+ }
2452
+ function resolveCalleeKey2(callee) {
2453
+ if (callee.type === "identifier")
2454
+ return callee.text;
2455
+ if (callee.type === "attribute") {
2456
+ const obj = callee.childForFieldName("object");
2457
+ const attr = callee.childForFieldName("attribute");
2458
+ if (obj !== null && obj.type === "identifier" && obj.text === "self" && attr !== null) {
2459
+ const cls = findEnclosingClassName2(callee);
2460
+ if (cls !== null)
2461
+ return `${cls}.${attr.text}`;
2462
+ }
2463
+ }
2464
+ return null;
2465
+ }
2466
+ function rawCallFromCallee2(callee, callerId) {
2467
+ if (callee.type === "identifier")
2468
+ return { caller_id: callerId, callee_name: callee.text };
2469
+ if (callee.type === "attribute") {
2470
+ const obj = callee.childForFieldName("object");
2471
+ const attr = callee.childForFieldName("attribute");
2472
+ if (obj !== null && obj.type === "identifier" && obj.text !== "self" && attr !== null) {
2473
+ return { caller_id: callerId, callee_name: attr.text, receiver: obj.text };
2474
+ }
2475
+ }
2476
+ return null;
2477
+ }
2478
+ function findEnclosingDeclaration2(node, declByName) {
2479
+ let cur = node.parent;
2480
+ while (cur !== null) {
2481
+ if (cur.type === "function_definition") {
2482
+ const name = textOfField2(cur, "name");
2483
+ const cls = findEnclosingClassName2(cur);
2484
+ if (name !== null) {
2485
+ const n = cls !== null ? declByName.get(`${cls}.${name}`) : declByName.get(name);
2486
+ if (n !== void 0)
2487
+ return n;
2488
+ }
2489
+ }
2490
+ cur = cur.parent;
2491
+ }
2492
+ return null;
2493
+ }
2494
+ function findEnclosingClassName2(node) {
2495
+ let cur = node.parent;
2496
+ while (cur !== null) {
2497
+ if (cur.type === "class_definition")
2498
+ return textOfField2(cur, "name");
2499
+ cur = cur.parent;
2500
+ }
2501
+ return null;
2502
+ }
2503
+ function makeNode2(relativePath, name, kind, node, exported) {
2504
+ return { id: nodeId2(relativePath, name, kind), label: name, kind, source_file: relativePath, source_location: loc(node), language: "python", exported, signature: signatureOf2(node, kind) };
2505
+ }
2506
+ function makeNodeWithExplicitLabel2(relativePath, idName, label, kind, node, exported) {
2507
+ return { id: nodeId2(relativePath, idName, kind), label, kind, source_file: relativePath, source_location: loc(node), language: "python", exported, signature: signatureOf2(node, kind) };
2508
+ }
2509
+ function makeModuleNode2(relativePath) {
2510
+ return { id: `${relativePath}::module`, label: relativePath, kind: "module", source_file: relativePath, source_location: "L1", language: "python", exported: false };
2511
+ }
2512
+ function pushNode2(result, declByName, node) {
2513
+ result.nodes.push(node);
2514
+ const key = node.kind === "method" ? node.id.split(":")[1] : node.label;
2515
+ if (!declByName.has(key))
2516
+ declByName.set(key, node);
2517
+ }
2518
+ function signatureOf2(node, kind) {
2519
+ const text = node.text;
2520
+ let end = text.length;
2521
+ const nl = text.indexOf("\n");
2522
+ if (nl >= 0)
2523
+ end = Math.min(end, nl);
2524
+ if (kind === "function" || kind === "method" || kind === "class") {
2525
+ const body = node.childForFieldName("body");
2526
+ if (body !== null)
2527
+ end = Math.min(end, body.startIndex - node.startIndex);
2528
+ }
2529
+ const sig = text.slice(0, end).replace(/\s+/g, " ").replace(/:\s*$/, "").trim();
2530
+ const cps = [...sig];
2531
+ return cps.length > 120 ? `${cps.slice(0, 117).join("")}...` : sig;
2532
+ }
2533
+ function nodeId2(relativePath, name, kind) {
2534
+ return `${relativePath}:${name}:${kind}`;
2535
+ }
2536
+ function nodeIdUnresolved2(relativePath, name, kind) {
2537
+ return `unresolved:${relativePath}:${name}:${kind}`;
2538
+ }
2539
+ function loc(node) {
2540
+ const start = node.startPosition.row + 1;
2541
+ const end = node.endPosition.row + 1;
2542
+ return end > start ? `L${start}-${end}` : `L${start}`;
2543
+ }
2544
+ function textOfField2(node, field) {
2545
+ const f = node.childForFieldName(field);
2546
+ return f !== null ? f.text : null;
2547
+ }
2548
+ function firstOfType(node, type) {
2549
+ for (let i = 0; i < node.namedChildCount; i++) {
2550
+ const c = node.namedChild(i);
2551
+ if (c !== null && c.type === type)
2552
+ return c;
2553
+ }
2554
+ return null;
2555
+ }
2556
+ function lastDottedSegment(dotted) {
2557
+ const parts = dotted.split(".");
2558
+ return parts[parts.length - 1] ?? dotted;
2559
+ }
2560
+ function isPublic(name) {
2561
+ return !name.startsWith("_");
2562
+ }
2563
+
2564
+ // dist/src/graph/extract/index.js
2565
+ function isPythonPath(relativePath) {
2566
+ return /\.pyi?$/.test(relativePath);
2567
+ }
2568
+ function extractFile(sourceCode, relativePath) {
2569
+ if (isPythonPath(relativePath))
2570
+ return extractPython(sourceCode, relativePath);
2571
+ return extractTypeScript(sourceCode, relativePath);
2572
+ }
2573
+
2574
+ // dist/src/graph/ignore-config.js
2575
+ import { mkdirSync as mkdirSync10, readFileSync as readFileSync11, writeFileSync as writeFileSync9 } from "node:fs";
2576
+ import { homedir as homedir7 } from "node:os";
2577
+ import { join as join14 } from "node:path";
2578
+ var DEFAULT_IGNORE_DIRS = [
2579
+ // JS / TS toolchains
2580
+ "node_modules",
2581
+ "bower_components",
2582
+ "jspm_packages",
2583
+ ".pnpm-store",
2584
+ "dist",
2585
+ "build",
2586
+ "out",
2587
+ "coverage",
2588
+ "bundle",
2589
+ ".next",
2590
+ ".nuxt",
2591
+ ".svelte-kit",
2592
+ ".turbo",
2593
+ ".parcel-cache",
2594
+ ".cache",
2595
+ ".vite",
2596
+ ".nyc_output",
2597
+ // Python
2598
+ "venv",
2599
+ ".venv",
2600
+ "env",
2601
+ ".env",
2602
+ "virtualenv",
2603
+ "__pycache__",
2604
+ "site-packages",
2605
+ "__pypackages__",
2606
+ ".pytest_cache",
2607
+ ".mypy_cache",
2608
+ ".ruff_cache",
2609
+ ".tox",
2610
+ ".eggs",
2611
+ ".ipynb_checkpoints",
2612
+ ".hypothesis",
2613
+ // Rust / Java / .NET / Go vendoring
2614
+ "target",
2615
+ "obj",
2616
+ "vendor",
2617
+ ".gradle",
2618
+ ".mvn",
2619
+ // Native / mobile
2620
+ "Pods",
2621
+ "DerivedData",
2622
+ ".build",
2623
+ // VCS / IDE
2624
+ ".git",
2625
+ ".svn",
2626
+ ".hg",
2627
+ ".idea",
2628
+ ".vscode",
2629
+ ".vs",
2630
+ // Infra / misc
2631
+ ".terraform",
2632
+ "tmp",
2633
+ "temp",
2634
+ "logs",
2635
+ "third_party",
2636
+ "third-party"
2637
+ ];
2638
+ var FILE_NAME = "graph-ignore.json";
2639
+ function defaultConfigObject() {
2640
+ return {
2641
+ _comment: "Directory names skipped when building the hivemind code graph. Edit freely. When respectGitignore is true, the repo's .gitignore is also honored (anchoring-correct).",
2642
+ ignoreDirs: [...DEFAULT_IGNORE_DIRS],
2643
+ respectGitignore: true
2644
+ };
2645
+ }
2646
+ function loadGraphIgnore(deeplakeDir = join14(homedir7(), ".deeplake")) {
2647
+ const path = join14(deeplakeDir, FILE_NAME);
2648
+ try {
2649
+ const parsed = JSON.parse(readFileSync11(path, "utf8"));
2650
+ const ignoreDirs = Array.isArray(parsed.ignoreDirs) ? parsed.ignoreDirs.filter((s) => typeof s === "string") : [...DEFAULT_IGNORE_DIRS];
2651
+ const respectGitignore = typeof parsed.respectGitignore === "boolean" ? parsed.respectGitignore : true;
2652
+ return { ignoreDirs, respectGitignore };
2653
+ } catch {
2654
+ }
2655
+ try {
2656
+ mkdirSync10(deeplakeDir, { recursive: true });
2657
+ writeFileSync9(path, JSON.stringify(defaultConfigObject(), null, 2) + "\n", { flag: "wx" });
2658
+ } catch {
2659
+ }
2660
+ return { ignoreDirs: [...DEFAULT_IGNORE_DIRS], respectGitignore: true };
2661
+ }
2662
+ function ignoreDirSet(config) {
2663
+ return new Set(config.ignoreDirs);
2664
+ }
2665
+ function pathHasIgnoredSegment(relPath, ignore) {
2666
+ const segs = relPath.split("/");
2667
+ return segs.some((seg, i) => ignore.has(seg) || i < segs.length - 1 && seg.startsWith("."));
2668
+ }
2669
+
2670
+ // dist/src/graph/git-hook-install.js
2671
+ import { chmodSync, existsSync as existsSync9, mkdirSync as mkdirSync11, readFileSync as readFileSync12, unlinkSync as unlinkSync4, writeFileSync as writeFileSync10 } from "node:fs";
2672
+ import { dirname as dirname7, join as join15, resolve as resolve3 } from "node:path";
2673
+ import { execFileSync as execFileSync2 } from "node:child_process";
2674
+
2675
+ // dist/src/commands/graph.js
2676
+ var USAGE = `hivemind graph \u2014 codebase-graph commands (TypeScript / JavaScript / Python)
2677
+
2678
+ Usage:
2679
+ hivemind graph build [--cwd <path>]
2680
+ Walk the project for supported source files (TS/JS/Python), extract symbols + edges,
2681
+ and write a snapshot to ~/.hivemind/graphs/<repo-key>/snapshots/<commit-sha>.json.
2682
+ Also updates ~/.hivemind/graphs/<repo-key>/latest-commit.txt and the
2683
+ per-repo .last-build.json (consumed by the SessionEnd auto-build hook).
2684
+
2685
+ hivemind graph diff <sha1> <sha2> [--cwd <path>] [--json] [--limit N]
2686
+ Diff two snapshots by their git commit SHA. Prints added/removed
2687
+ counts for nodes and edges, plus up to N=10 (default) examples of each.
2688
+ --json: emit machine-readable JSON instead of the human format.
2689
+ --limit N: cap the per-category examples (human format only).
2690
+
2691
+ hivemind graph history [--cwd <path>] [-n N] [--json]
2692
+ Print the last N (default 20) entries from the per-repo history.jsonl,
2693
+ newest last. Each entry shows ts, commit_sha (short), snapshot_sha256
2694
+ (short), node/edge counts, and the trigger that fired the build.
2695
+ --json: emit raw JSONL (one parsed entry per line, full fields).
2696
+
2697
+ hivemind graph init [--cwd <path>] [--force] [--no-initial-build]
2698
+ Install a managed block in .git/hooks/post-commit that fires
2699
+ \`hivemind graph build --trigger post-commit\` after each commit
2700
+ (async, non-blocking, exit 0 always). Idempotent: re-running on
2701
+ an already-installed hook is a no-op. Refuses to clobber an
2702
+ existing non-managed hook unless --force is passed.
2703
+ Also runs an initial \`hivemind graph build\` unless
2704
+ --no-initial-build is passed.
2705
+
2706
+ hivemind graph uninstall [--cwd <path>]
2707
+ Remove our managed block from .git/hooks/post-commit. If our block
2708
+ was the only content, deletes the file; otherwise leaves the rest
2709
+ intact. Snapshots and history are NOT touched (\`rm -rf
2710
+ ~/.hivemind/graphs/<key>\` if you really want them gone).
2711
+
2712
+ hivemind graph pull [--cwd <path>]
2713
+ Download the freshest cloud snapshot for HEAD into the local graph
2714
+ dir (any worktree of this user counts). No-op if local already
2715
+ matches cloud sha256 or local was built later than cloud. Requires
2716
+ \`hivemind login\`. Best-effort: any network/auth failure leaves
2717
+ the local files untouched. Disable via HIVEMIND_GRAPH_PULL=0.
2718
+
2719
+ hivemind graph --help
2720
+ Show this message.
2721
+
2722
+ Future subcommands (Phase 1.5+): daemon, search, latest, push, pull, prune.
2723
+ `;
2724
+ function parseBuildArgs(args) {
2725
+ let cwd = process.cwd();
2726
+ let trigger = "manual";
2727
+ for (let i = 0; i < args.length; i++) {
2728
+ const a = args[i];
2729
+ if (a === "--cwd" && i + 1 < args.length) {
2730
+ cwd = args[i + 1];
2731
+ i += 1;
2732
+ } else if (a === "--trigger" && i + 1 < args.length) {
2733
+ const v = args[i + 1];
2734
+ if (v === "manual" || v === "session-end" || v === "post-commit" || v === "unknown") {
2735
+ trigger = v;
2736
+ } else {
2737
+ console.error(`hivemind graph build: --trigger must be one of manual|session-end|post-commit|unknown (got '${v}')`);
2738
+ process.exit(2);
2739
+ }
2740
+ i += 1;
2741
+ } else if (a === "--help" || a === "-h") {
2742
+ console.log(USAGE);
2743
+ process.exit(0);
2744
+ } else {
2745
+ console.error(`hivemind graph build: unknown argument '${a}'`);
2746
+ console.error(USAGE);
2747
+ process.exit(2);
2748
+ }
2749
+ }
2750
+ return { cwd, trigger };
2751
+ }
2752
+ async function runBuildCommand(args) {
2753
+ const opts = parseBuildArgs(args);
2754
+ const cwd = resolve4(opts.cwd);
2755
+ const { key: repoKey, project } = deriveProjectKey(cwd);
2756
+ const baseDir = repoDir(repoKey);
2757
+ const commitSha = readGitCommit(cwd);
2758
+ const branch = readGitBranch(cwd);
2759
+ const version = getVersion();
2760
+ console.log(`Building codebase graph for ${project}`);
2761
+ console.log(` repo_key: ${repoKey}`);
2762
+ console.log(` commit_sha: ${commitSha ?? "(not in a git repo)"}`);
2763
+ console.log(` branch: ${branch ?? "(none / detached)"}`);
2764
+ console.log(` output: ${baseDir}`);
2765
+ console.log("");
2766
+ const ignoreConfig = loadGraphIgnore();
2767
+ const sourceFiles = discoverSourceFiles(cwd, ignoreConfig);
2768
+ console.log(`Discovered ${sourceFiles.length} source files. Extracting...`);
2769
+ const extractions = [];
2770
+ let skipped = 0;
2771
+ let totalParseErrors = 0;
2772
+ let cacheHits = 0;
2773
+ for (const abs of sourceFiles) {
2774
+ const rel = toForwardSlash(relative(cwd, abs));
2775
+ try {
2776
+ const content = readFileSync13(abs, "utf8");
2777
+ const contentSha = fileContentHash(content);
2778
+ let extraction = readCache(baseDir, contentSha, rel);
2779
+ if (extraction === null) {
2780
+ extraction = extractFile(content, rel);
2781
+ writeCache(baseDir, contentSha, extraction);
2782
+ } else {
2783
+ cacheHits += 1;
2784
+ }
2785
+ if (extraction.parse_errors.length > 0) {
2786
+ totalParseErrors += extraction.parse_errors.length;
2787
+ for (const err of extraction.parse_errors) {
2788
+ console.warn(` warn: parse issue in ${err.source_file} ${err.location ?? ""}: ${err.message}`);
2789
+ }
2790
+ }
2791
+ extractions.push(extraction);
2792
+ } catch (err) {
2793
+ const msg = err instanceof Error ? err.message : String(err);
2794
+ console.warn(` warn: skipping ${rel}: ${msg}`);
2795
+ skipped += 1;
2796
+ }
2797
+ }
2798
+ const metadata = {
2799
+ schema_version: 1,
2800
+ generator: "hivemind-graph",
2801
+ commit_sha: commitSha,
2802
+ repo_key: repoKey
2803
+ };
2804
+ const observation = {
2805
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2806
+ branch,
2807
+ worktree_path: cwd,
2808
+ repo_project: project,
2809
+ generator_version: version,
2810
+ source_files_extracted: extractions.length,
2811
+ source_files_skipped: skipped
2812
+ };
2813
+ const snapshot = buildSnapshot(extractions, metadata, observation);
2814
+ const worktreeId = workTreeIdFor(cwd);
2815
+ const result = writeSnapshot(snapshot, baseDir, opts.trigger, worktreeId);
2816
+ console.log("");
2817
+ console.log(`Snapshot: ${result.snapshotPath}`);
2818
+ console.log(`Latest: ${result.latestCommitPath ?? "(no commit context \u2014 latest-commit.txt not updated)"}`);
2819
+ console.log(`SHA-256: ${result.snapshotSha256}`);
2820
+ console.log(`Nodes: ${snapshot.nodes.length}`);
2821
+ console.log(`Edges: ${snapshot.links.length}`);
2822
+ console.log(`Files extracted: ${extractions.length} (skipped: ${skipped}, parse warnings: ${totalParseErrors}, cache hits: ${cacheHits}/${sourceFiles.length})`);
2823
+ const pushOutcome = await pushSnapshot(snapshot, worktreeId);
2824
+ switch (pushOutcome.kind) {
2825
+ case "inserted":
2826
+ console.log(`Cloud: pushed to codebase table (commit ${pushOutcome.commitSha.slice(0, 7)})`);
2827
+ break;
2828
+ case "inserted-with-duplicate-race":
2829
+ console.warn(`Cloud: pushed (commit ${pushOutcome.commitSha.slice(0, 7)}) but ${pushOutcome.rowCount} rows now share`);
2830
+ console.warn(` this identity key \u2014 a concurrent writer raced. v1.1 adds a server-side`);
2831
+ console.warn(` UNIQUE constraint; until then, the older row(s) should be deleted manually.`);
2832
+ break;
2833
+ case "already-current":
2834
+ console.log(`Cloud: already up-to-date (commit ${pushOutcome.commitSha.slice(0, 7)})`);
2835
+ break;
2836
+ case "skipped-no-auth":
2837
+ console.log(`Cloud: skipped (not authenticated; run \`hivemind login\` to enable cloud sync)`);
2838
+ break;
2839
+ case "skipped-no-commit":
2840
+ console.log(`Cloud: skipped (no commit context \u2014 not in a git repo)`);
2841
+ break;
2842
+ case "skipped-disabled":
2843
+ console.log(`Cloud: skipped (HIVEMIND_GRAPH_PUSH=0)`);
2844
+ break;
2845
+ case "drift":
2846
+ console.warn(`Cloud: DRIFT \u2014 commit ${pushOutcome.commitSha.slice(0, 7)} is in cloud with`);
2847
+ console.warn(` sha256=${pushOutcome.cloudSha256.slice(0, 12)}... but local rebuild produced`);
2848
+ console.warn(` sha256=${pushOutcome.localSha256.slice(0, 12)}...`);
2849
+ console.warn(` (probably extractor version drift; investigate before forcing.)`);
2850
+ break;
2851
+ case "error":
2852
+ console.warn(`Cloud: push error (non-fatal): ${pushOutcome.message}`);
2853
+ break;
2854
+ }
2855
+ }
2856
+ function workTreeIdFor(cwd) {
2857
+ return createHash6("sha256").update(cwd).digest("hex").slice(0, 16);
2858
+ }
2859
+ function discoverSourceFiles(rootDir, config) {
2860
+ const ignore = ignoreDirSet(config);
2861
+ if (config.respectGitignore) {
2862
+ const fromGit = gitListSourceFiles(rootDir, ignore);
2863
+ if (fromGit !== null)
2864
+ return fromGit;
2865
+ }
2866
+ const out = [];
2867
+ walk(rootDir, out, ignore);
2868
+ out.sort();
2869
+ return out;
2870
+ }
2871
+ function gitListSourceFiles(rootDir, ignore) {
2872
+ let stdout;
2873
+ try {
2874
+ stdout = execSync2("git ls-files --cached --others --exclude-standard -z", {
2875
+ cwd: rootDir,
2876
+ encoding: "utf8",
2877
+ stdio: ["ignore", "pipe", "ignore"],
2878
+ maxBuffer: 64 * 1024 * 1024
2879
+ });
2880
+ } catch {
2881
+ return null;
2882
+ }
2883
+ const out = [];
2884
+ for (const rel of stdout.split("\0")) {
2885
+ if (rel.length === 0)
2886
+ continue;
2887
+ if (!isSourceFile(rel))
2888
+ continue;
2889
+ if (pathHasIgnoredSegment(rel, ignore))
2890
+ continue;
2891
+ out.push(join16(rootDir, rel));
2892
+ }
2893
+ out.sort();
2894
+ return out;
2895
+ }
2896
+ function walk(dir, out, ignore) {
2897
+ let entries;
2898
+ try {
2899
+ entries = readdirSync(dir, { withFileTypes: true });
2900
+ } catch {
2901
+ return;
2902
+ }
2903
+ for (const entry of entries) {
2904
+ if (ignore.has(entry.name))
2905
+ continue;
2906
+ if (entry.name.startsWith("."))
2907
+ continue;
2908
+ const abs = join16(dir, entry.name);
2909
+ if (entry.isDirectory()) {
2910
+ walk(abs, out, ignore);
2911
+ } else if (entry.isFile() && isSourceFile(entry.name)) {
2912
+ out.push(abs);
2913
+ }
2914
+ }
2915
+ }
2916
+ function isSourceFile(name) {
2917
+ if (name.endsWith(".d.ts"))
2918
+ return false;
2919
+ return /\.(tsx?|jsx?|mjs|cjs|pyi?)$/.test(name);
2920
+ }
2921
+ function toForwardSlash(p) {
2922
+ return sep === "\\" ? p.replace(/\\/g, "/") : p;
2923
+ }
2924
+ function readGitCommit(cwd) {
2925
+ try {
2926
+ return execSync2("git rev-parse HEAD", {
2927
+ cwd,
2928
+ encoding: "utf8",
2929
+ stdio: ["ignore", "pipe", "ignore"]
2930
+ }).trim();
2931
+ } catch {
2932
+ return null;
2933
+ }
2934
+ }
2935
+ function readGitBranch(cwd) {
2936
+ try {
2937
+ const out = execSync2("git rev-parse --abbrev-ref HEAD", {
2938
+ cwd,
2939
+ encoding: "utf8",
2940
+ stdio: ["ignore", "pipe", "ignore"]
2941
+ }).trim();
2942
+ return out === "" || out === "HEAD" ? null : out;
2943
+ } catch {
2944
+ return null;
2945
+ }
2946
+ }
2947
+
2948
+ // dist/src/graph/build-lock.js
2949
+ import { mkdirSync as mkdirSync12, readFileSync as readFileSync14, statSync as statSync2, unlinkSync as unlinkSync5, writeFileSync as writeFileSync11 } from "node:fs";
2950
+ import { join as join17 } from "node:path";
2951
+ var STALE_LOCK_MS = 5 * 60 * 1e3;
2952
+ function lockPath2(baseDir) {
2953
+ return join17(baseDir, ".build.in-flight");
2954
+ }
2955
+ function acquireBuildLock(baseDir) {
2956
+ const path = lockPath2(baseDir);
2957
+ try {
2958
+ mkdirSync12(baseDir, { recursive: true });
2959
+ } catch {
2960
+ return { acquired: false, reason: "fs-error" };
2961
+ }
2962
+ try {
2963
+ writeFileSync11(path, JSON.stringify({ pid: process.pid, ts: Date.now() }), { flag: "wx" });
2964
+ return { acquired: true, reason: "acquired" };
2965
+ } catch (err) {
2966
+ const code = err.code;
2967
+ if (code !== "EEXIST") {
2968
+ return { acquired: false, reason: "fs-error" };
2969
+ }
2970
+ }
2971
+ let ageMs;
2972
+ try {
2973
+ const stat = statSync2(path);
2974
+ ageMs = Date.now() - stat.mtime.getTime();
2975
+ } catch {
2976
+ return { acquired: false, reason: "fs-error" };
2977
+ }
2978
+ if (ageMs <= STALE_LOCK_MS) {
2979
+ return { acquired: false, reason: "held-by-other" };
2980
+ }
2981
+ try {
2982
+ unlinkSync5(path);
2983
+ } catch (err) {
2984
+ const code = err.code;
2985
+ if (code !== "ENOENT") {
2986
+ return { acquired: false, reason: "fs-error" };
2987
+ }
2988
+ }
2989
+ try {
2990
+ writeFileSync11(path, JSON.stringify({ pid: process.pid, ts: Date.now() }), { flag: "wx" });
2991
+ return { acquired: true, reason: "stale-recovered" };
2992
+ } catch (err) {
2993
+ const code = err.code;
2994
+ if (code === "EEXIST") {
2995
+ return { acquired: false, reason: "held-by-other" };
2996
+ }
2997
+ return { acquired: false, reason: "fs-error" };
2998
+ }
2999
+ }
3000
+ function releaseBuildLock(baseDir) {
3001
+ const path = lockPath2(baseDir);
3002
+ try {
3003
+ const raw = readFileSync14(path, "utf8");
3004
+ const parsed = JSON.parse(raw);
3005
+ if (parsed.pid !== process.pid)
3006
+ return;
3007
+ unlinkSync5(path);
3008
+ } catch (err) {
3009
+ const code = err.code;
3010
+ if (code === "ENOENT")
3011
+ return;
3012
+ }
3013
+ }
3014
+
3015
+ // dist/src/utils/direct-run.js
3016
+ import { resolve as resolve5 } from "node:path";
3017
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
3018
+ function isDirectRun(metaUrl) {
3019
+ const entry = process.argv[1];
3020
+ if (!entry)
3021
+ return false;
3022
+ try {
3023
+ return resolve5(fileURLToPath2(metaUrl)) === resolve5(entry);
3024
+ } catch {
3025
+ return false;
3026
+ }
3027
+ }
3028
+
3029
+ // dist/src/hooks/graph-on-stop.js
3030
+ function workTreeIdFor2(cwd) {
3031
+ return createHash7("sha256").update(cwd).digest("hex").slice(0, 16);
3032
+ }
3033
+ function tickIntervalMs() {
3034
+ const raw = process.env.HIVEMIND_GRAPH_TICK_INTERVAL_MS;
3035
+ if (raw === void 0)
3036
+ return 10 * 60 * 1e3;
3037
+ const parsed = parseInt(raw, 10);
3038
+ return Number.isFinite(parsed) && parsed >= 0 ? parsed : 10 * 60 * 1e3;
3039
+ }
3040
+ var SOURCE_GLOBS = ["*.ts", "*.tsx", "*.js", "*.jsx", "*.mjs", "*.cjs", "*.py", "*.pyi", ":(exclude)*.d.ts"];
3041
+ function decideGate(ctx) {
3042
+ if (ctx.envDisable)
3043
+ return { fire: false, reason: "disabled (HIVEMIND_GRAPH_ON_STOP=0)" };
3044
+ const { key: repoKey } = deriveProjectKey(ctx.cwd);
3045
+ const baseDir = repoDir(repoKey);
3046
+ const last = readLastBuild(baseDir, workTreeIdFor2(ctx.cwd));
3047
+ const head = readGitCommit2(ctx.cwd);
3048
+ if (head === null) {
3049
+ return { fire: false, reason: "not in a git repo" };
3050
+ }
3051
+ if (last === null) {
3052
+ return { fire: true, reason: "first build (no prior .last-build.json)" };
3053
+ }
3054
+ if (ctx.now - last.ts < ctx.intervalMs) {
3055
+ return { fire: false, reason: `rate limit (${Math.round((ctx.now - last.ts) / 1e3)}s < ${Math.round(ctx.intervalMs / 1e3)}s)` };
3056
+ }
3057
+ if (head === last.commit_sha) {
3058
+ return { fire: false, reason: "HEAD unchanged since last build" };
3059
+ }
3060
+ const changedSourceCount = countSourceDiff(ctx.cwd, last.commit_sha, head);
3061
+ if (changedSourceCount < 1) {
3062
+ return { fire: false, reason: "no source files changed since last build" };
3063
+ }
3064
+ return { fire: true, reason: `${changedSourceCount} source file(s) changed since last build` };
3065
+ }
3066
+ function countSourceDiff(cwd, from, to) {
3067
+ if (from === null)
3068
+ return 1;
3069
+ try {
3070
+ const out = execFileSync3("git", ["diff", "--name-only", `${from}..${to}`, "--", ...SOURCE_GLOBS], {
3071
+ cwd,
3072
+ encoding: "utf8",
3073
+ stdio: ["ignore", "pipe", "ignore"]
3074
+ }).trim();
3075
+ return out === "" ? 0 : out.split("\n").length;
3076
+ } catch {
3077
+ return 0;
3078
+ }
3079
+ }
3080
+ function readGitCommit2(cwd) {
3081
+ try {
3082
+ return execFileSync3("git", ["rev-parse", "HEAD"], {
3083
+ cwd,
3084
+ encoding: "utf8",
3085
+ stdio: ["ignore", "pipe", "ignore"]
3086
+ }).trim();
3087
+ } catch {
3088
+ return null;
3089
+ }
3090
+ }
3091
+ async function main(deps = {}) {
3092
+ const runBuildFn = deps.runBuildCommand ?? runBuildCommand;
3093
+ const acquireFn = deps.acquireBuildLock ?? acquireBuildLock;
3094
+ const releaseFn = deps.releaseBuildLock ?? releaseBuildLock;
3095
+ const gateFn = deps.decideGate ?? decideGate;
3096
+ const envDisable = process.env.HIVEMIND_GRAPH_ON_STOP === "0";
3097
+ const ctx = {
3098
+ cwd: process.cwd(),
3099
+ now: Date.now(),
3100
+ intervalMs: tickIntervalMs(),
3101
+ envDisable
3102
+ };
3103
+ let decision;
3104
+ try {
3105
+ decision = gateFn(ctx);
3106
+ } catch (err) {
3107
+ logToFile(ctx.cwd, `decideGate threw: ${err instanceof Error ? err.message : String(err)}`);
3108
+ return;
3109
+ }
3110
+ logToFile(ctx.cwd, `gate: ${decision.fire ? "FIRE" : "SKIP"} (${decision.reason})`);
3111
+ if (!decision.fire)
3112
+ return;
3113
+ const { key: repoKey } = deriveProjectKey(ctx.cwd);
3114
+ const baseDir = repoDir(repoKey);
3115
+ const lock = acquireFn(baseDir);
3116
+ if (!lock.acquired) {
3117
+ logToFile(ctx.cwd, `build skipped: lock ${lock.reason}`);
3118
+ return;
3119
+ }
3120
+ logToFile(ctx.cwd, `lock: ${lock.reason}`);
3121
+ try {
3122
+ await runBuildFn(["--trigger", "session-end"]);
3123
+ } catch (err) {
3124
+ logToFile(ctx.cwd, `build threw: ${err instanceof Error ? err.message : String(err)}`);
3125
+ } finally {
3126
+ releaseFn(baseDir);
3127
+ }
3128
+ }
3129
+ function logToFile(cwd, line) {
3130
+ try {
3131
+ const { key } = deriveProjectKey(cwd);
3132
+ const dir = repoDir(key);
3133
+ mkdirSync13(dir, { recursive: true });
3134
+ appendFileSync3(join18(dir, ".graph-on-stop.log"), `[${(/* @__PURE__ */ new Date()).toISOString()}] ${line}
3135
+ `);
3136
+ } catch {
3137
+ }
3138
+ }
3139
+ if (isDirectRun(import.meta.url)) {
3140
+ main().catch((err) => {
3141
+ console.error(`graph-on-stop fatal: ${err instanceof Error ? err.message : String(err)}`);
3142
+ process.exit(0);
3143
+ });
3144
+ }
3145
+ export {
3146
+ decideGate,
3147
+ main
3148
+ };