@deeplake/hivemind 0.7.44 → 0.7.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/bundle/cli.js +3116 -208
- package/codex/bundle/capture.js +43 -0
- package/codex/bundle/commands/auth-login.js +43 -0
- package/codex/bundle/graph-pull-worker.js +1185 -0
- package/codex/bundle/pre-tool-use.js +43 -0
- package/codex/bundle/session-start-setup.js +43 -0
- package/codex/bundle/session-start.js +70 -4
- package/codex/bundle/shell/deeplake-shell.js +577 -25
- package/codex/bundle/skillify-worker.js +30 -3
- package/codex/bundle/stop.js +97 -50
- package/cursor/bundle/capture.js +97 -50
- package/cursor/bundle/commands/auth-login.js +43 -0
- package/cursor/bundle/graph-pull-worker.js +1185 -0
- package/cursor/bundle/pre-tool-use.js +43 -0
- package/cursor/bundle/session-end.js +49 -44
- package/cursor/bundle/session-start.js +66 -0
- package/cursor/bundle/shell/deeplake-shell.js +577 -25
- package/cursor/bundle/skillify-worker.js +30 -3
- package/hermes/bundle/capture.js +97 -50
- package/hermes/bundle/commands/auth-login.js +43 -0
- package/hermes/bundle/graph-pull-worker.js +1185 -0
- package/hermes/bundle/pre-tool-use.js +43 -0
- package/hermes/bundle/session-end.js +49 -44
- package/hermes/bundle/session-start.js +66 -0
- package/hermes/bundle/shell/deeplake-shell.js +577 -25
- package/hermes/bundle/skillify-worker.js +30 -3
- package/mcp/bundle/server.js +43 -0
- package/openclaw/dist/chunks/{config-XEK4MJJS.js → config-O5PDJQ7Y.js} +1 -0
- package/openclaw/dist/index.js +47 -2
- package/openclaw/dist/skillify-worker.js +30 -3
- package/openclaw/openclaw.plugin.json +1 -1
- package/openclaw/package.json +1 -1
- package/package.json +3 -1
package/bundle/cli.js
CHANGED
|
@@ -144,23 +144,23 @@ function warn(msg) {
|
|
|
144
144
|
function confirm(message, defaultYes = true) {
|
|
145
145
|
const hint = defaultYes ? "[Y/n]" : "[y/N]";
|
|
146
146
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
147
|
-
return new Promise((
|
|
147
|
+
return new Promise((resolve6) => {
|
|
148
148
|
rl.question(`${message} ${hint} `, (answer) => {
|
|
149
149
|
rl.close();
|
|
150
150
|
const a = answer.trim().toLowerCase();
|
|
151
151
|
if (a === "")
|
|
152
|
-
|
|
152
|
+
resolve6(defaultYes);
|
|
153
153
|
else
|
|
154
|
-
|
|
154
|
+
resolve6(a === "y" || a === "yes");
|
|
155
155
|
});
|
|
156
156
|
});
|
|
157
157
|
}
|
|
158
158
|
function promptLine(message) {
|
|
159
159
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
160
|
-
return new Promise((
|
|
160
|
+
return new Promise((resolve6) => {
|
|
161
161
|
rl.question(message, (answer) => {
|
|
162
162
|
rl.close();
|
|
163
|
-
|
|
163
|
+
resolve6(answer.trim());
|
|
164
164
|
});
|
|
165
165
|
});
|
|
166
166
|
}
|
|
@@ -4431,6 +4431,7 @@ function loadConfig() {
|
|
|
4431
4431
|
tableName: process.env.HIVEMIND_TABLE ?? "memory",
|
|
4432
4432
|
sessionsTableName: process.env.HIVEMIND_SESSIONS_TABLE ?? "sessions",
|
|
4433
4433
|
skillsTableName: process.env.HIVEMIND_SKILLS_TABLE ?? "skills",
|
|
4434
|
+
codebaseTableName: process.env.HIVEMIND_CODEBASE_TABLE ?? "codebase",
|
|
4434
4435
|
memoryPath: process.env.HIVEMIND_MEMORY_PATH ?? join16(home, ".deeplake", "memory")
|
|
4435
4436
|
};
|
|
4436
4437
|
}
|
|
@@ -4536,9 +4537,33 @@ function validateSchema(label, cols) {
|
|
|
4536
4537
|
}
|
|
4537
4538
|
}
|
|
4538
4539
|
}
|
|
4540
|
+
var CODEBASE_COLUMNS = Object.freeze([
|
|
4541
|
+
// Identity key (matches the PK below)
|
|
4542
|
+
{ name: "org_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4543
|
+
{ name: "workspace_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4544
|
+
{ name: "repo_slug", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4545
|
+
{ name: "user_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4546
|
+
{ name: "worktree_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4547
|
+
{ name: "commit_sha", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4548
|
+
// Observation metadata
|
|
4549
|
+
{ name: "parent_sha", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4550
|
+
{ name: "branch", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4551
|
+
{ name: "ts", sql: "TIMESTAMP" },
|
|
4552
|
+
{ name: "pushed_by", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4553
|
+
// Snapshot payload
|
|
4554
|
+
{ name: "snapshot_sha256", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4555
|
+
{ name: "snapshot_jsonb", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4556
|
+
{ name: "node_count", sql: "BIGINT NOT NULL DEFAULT 0" },
|
|
4557
|
+
{ name: "edge_count", sql: "BIGINT NOT NULL DEFAULT 0" },
|
|
4558
|
+
// Generator metadata (for drift diagnostics — what hivemind version produced this?)
|
|
4559
|
+
{ name: "generator", sql: "TEXT NOT NULL DEFAULT 'hivemind-graph'" },
|
|
4560
|
+
{ name: "generator_version", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
4561
|
+
{ name: "schema_version", sql: "BIGINT NOT NULL DEFAULT 1" }
|
|
4562
|
+
]);
|
|
4539
4563
|
validateSchema("MEMORY_COLUMNS", MEMORY_COLUMNS);
|
|
4540
4564
|
validateSchema("SESSIONS_COLUMNS", SESSIONS_COLUMNS);
|
|
4541
4565
|
validateSchema("SKILLS_COLUMNS", SKILLS_COLUMNS);
|
|
4566
|
+
validateSchema("CODEBASE_COLUMNS", CODEBASE_COLUMNS);
|
|
4542
4567
|
function buildCreateTableSql(tableName, cols) {
|
|
4543
4568
|
const safe = sqlIdent(tableName);
|
|
4544
4569
|
const colSql = cols.map((c) => `${c.name} ${c.sql}`).join(", ");
|
|
@@ -4745,7 +4770,7 @@ function getQueryTimeoutMs() {
|
|
|
4745
4770
|
return Number(process.env.HIVEMIND_QUERY_TIMEOUT_MS ?? 1e4);
|
|
4746
4771
|
}
|
|
4747
4772
|
function sleep2(ms) {
|
|
4748
|
-
return new Promise((
|
|
4773
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
4749
4774
|
}
|
|
4750
4775
|
function isTimeoutError(error) {
|
|
4751
4776
|
const name = error instanceof Error ? error.name.toLowerCase() : "";
|
|
@@ -4775,7 +4800,7 @@ var Semaphore = class {
|
|
|
4775
4800
|
this.active++;
|
|
4776
4801
|
return;
|
|
4777
4802
|
}
|
|
4778
|
-
await new Promise((
|
|
4803
|
+
await new Promise((resolve6) => this.waiting.push(resolve6));
|
|
4779
4804
|
}
|
|
4780
4805
|
release() {
|
|
4781
4806
|
this.active--;
|
|
@@ -5085,6 +5110,24 @@ var DeeplakeApi = class {
|
|
|
5085
5110
|
* This sidesteps the Deeplake UPDATE-coalescing quirk that bit the wiki
|
|
5086
5111
|
* worker.
|
|
5087
5112
|
*/
|
|
5113
|
+
/**
|
|
5114
|
+
* Create the codebase table. One row per (org, workspace, repo, user,
|
|
5115
|
+
* worktree, commit) — see CODEBASE_COLUMNS for the schema. Healing
|
|
5116
|
+
* + index follow the same pattern as ensureSessionsTable.
|
|
5117
|
+
*/
|
|
5118
|
+
async ensureCodebaseTable(name) {
|
|
5119
|
+
const safe = sqlIdent(name);
|
|
5120
|
+
const tables = await this.listTables();
|
|
5121
|
+
if (!tables.includes(safe)) {
|
|
5122
|
+
log4(`table "${safe}" not found, creating`);
|
|
5123
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, CODEBASE_COLUMNS), safe);
|
|
5124
|
+
log4(`table "${safe}" created`);
|
|
5125
|
+
if (!tables.includes(safe))
|
|
5126
|
+
this._tablesCache = [...tables, safe];
|
|
5127
|
+
}
|
|
5128
|
+
await this.healSchema(safe, CODEBASE_COLUMNS);
|
|
5129
|
+
await this.ensureLookupIndex(safe, "codebase_identity", `("org_id", "workspace_id", "repo_slug", "user_id", "worktree_id", "commit_sha")`);
|
|
5130
|
+
}
|
|
5088
5131
|
async ensureSkillsTable(name) {
|
|
5089
5132
|
const safe = sqlIdent(name);
|
|
5090
5133
|
const tables = await this.listTables();
|
|
@@ -5410,32 +5453,2061 @@ async function runAuthCommand(args) {
|
|
|
5410
5453
|
console.log("Commands: login, logout, whoami, org list, org switch, workspaces, workspace, sessions prune, invite, members, remove, autoupdate");
|
|
5411
5454
|
}
|
|
5412
5455
|
}
|
|
5413
|
-
if (process.argv[1] && process.argv[1].endsWith("auth-login.js")) {
|
|
5414
|
-
runAuthCommand(process.argv.slice(2)).catch((e) => {
|
|
5415
|
-
console.error(e.message);
|
|
5416
|
-
process.exit(1);
|
|
5417
|
-
});
|
|
5456
|
+
if (process.argv[1] && process.argv[1].endsWith("auth-login.js")) {
|
|
5457
|
+
runAuthCommand(process.argv.slice(2)).catch((e) => {
|
|
5458
|
+
console.error(e.message);
|
|
5459
|
+
process.exit(1);
|
|
5460
|
+
});
|
|
5461
|
+
}
|
|
5462
|
+
|
|
5463
|
+
// dist/src/commands/graph.js
|
|
5464
|
+
import { execSync as execSync3 } from "node:child_process";
|
|
5465
|
+
import { readFileSync as readFileSync20, readdirSync as readdirSync2 } from "node:fs";
|
|
5466
|
+
import { join as join27, relative, resolve as resolve4, sep } from "node:path";
|
|
5467
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
5468
|
+
|
|
5469
|
+
// dist/src/graph/cache.js
|
|
5470
|
+
import { createHash } from "node:crypto";
|
|
5471
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync15, renameSync as renameSync4, writeFileSync as writeFileSync11 } from "node:fs";
|
|
5472
|
+
import { dirname as dirname3, join as join20 } from "node:path";
|
|
5473
|
+
var CACHE_SCHEMA_VERSION = 1;
|
|
5474
|
+
function fileContentHash(contents) {
|
|
5475
|
+
return createHash("sha256").update(contents).digest("hex");
|
|
5476
|
+
}
|
|
5477
|
+
function cacheDir(baseDir) {
|
|
5478
|
+
return join20(baseDir, ".cache");
|
|
5479
|
+
}
|
|
5480
|
+
function cachePath(baseDir, contentSha256) {
|
|
5481
|
+
return join20(cacheDir(baseDir), `${contentSha256}.json`);
|
|
5482
|
+
}
|
|
5483
|
+
function readCache(baseDir, contentSha256, relativePath) {
|
|
5484
|
+
const path = cachePath(baseDir, contentSha256);
|
|
5485
|
+
if (!existsSync15(path))
|
|
5486
|
+
return null;
|
|
5487
|
+
let raw;
|
|
5488
|
+
try {
|
|
5489
|
+
raw = readFileSync15(path, "utf8");
|
|
5490
|
+
} catch {
|
|
5491
|
+
return null;
|
|
5492
|
+
}
|
|
5493
|
+
let parsed;
|
|
5494
|
+
try {
|
|
5495
|
+
parsed = JSON.parse(raw);
|
|
5496
|
+
} catch {
|
|
5497
|
+
return null;
|
|
5498
|
+
}
|
|
5499
|
+
if (parsed === null || typeof parsed !== "object" || parsed.schema !== CACHE_SCHEMA_VERSION || parsed.content_sha256 !== contentSha256) {
|
|
5500
|
+
return null;
|
|
5501
|
+
}
|
|
5502
|
+
const cached = parsed.extraction;
|
|
5503
|
+
if (cached === void 0 || typeof cached !== "object" || !Array.isArray(cached.nodes) || !Array.isArray(cached.edges) || !Array.isArray(cached.parse_errors)) {
|
|
5504
|
+
return null;
|
|
5505
|
+
}
|
|
5506
|
+
if (!validateItems(cached)) {
|
|
5507
|
+
return null;
|
|
5508
|
+
}
|
|
5509
|
+
try {
|
|
5510
|
+
return rewriteSourceFile(cached, relativePath);
|
|
5511
|
+
} catch {
|
|
5512
|
+
return null;
|
|
5513
|
+
}
|
|
5514
|
+
}
|
|
5515
|
+
function validateItems(ex) {
|
|
5516
|
+
if (typeof ex.source_file !== "string")
|
|
5517
|
+
return false;
|
|
5518
|
+
if (typeof ex.language !== "string")
|
|
5519
|
+
return false;
|
|
5520
|
+
for (const n of ex.nodes) {
|
|
5521
|
+
if (n === null || typeof n !== "object")
|
|
5522
|
+
return false;
|
|
5523
|
+
if (typeof n.id !== "string")
|
|
5524
|
+
return false;
|
|
5525
|
+
if (typeof n.label !== "string")
|
|
5526
|
+
return false;
|
|
5527
|
+
if (typeof n.kind !== "string")
|
|
5528
|
+
return false;
|
|
5529
|
+
if (typeof n.source_file !== "string")
|
|
5530
|
+
return false;
|
|
5531
|
+
if (typeof n.source_location !== "string")
|
|
5532
|
+
return false;
|
|
5533
|
+
if (typeof n.language !== "string")
|
|
5534
|
+
return false;
|
|
5535
|
+
if (typeof n.exported !== "boolean")
|
|
5536
|
+
return false;
|
|
5537
|
+
}
|
|
5538
|
+
for (const e of ex.edges) {
|
|
5539
|
+
if (e === null || typeof e !== "object")
|
|
5540
|
+
return false;
|
|
5541
|
+
if (typeof e.source !== "string")
|
|
5542
|
+
return false;
|
|
5543
|
+
if (typeof e.target !== "string")
|
|
5544
|
+
return false;
|
|
5545
|
+
if (typeof e.relation !== "string")
|
|
5546
|
+
return false;
|
|
5547
|
+
if (typeof e.confidence !== "string")
|
|
5548
|
+
return false;
|
|
5549
|
+
if (e.ord !== void 0 && typeof e.ord !== "number")
|
|
5550
|
+
return false;
|
|
5551
|
+
}
|
|
5552
|
+
for (const p of ex.parse_errors) {
|
|
5553
|
+
if (p === null || typeof p !== "object")
|
|
5554
|
+
return false;
|
|
5555
|
+
if (typeof p.source_file !== "string")
|
|
5556
|
+
return false;
|
|
5557
|
+
if (typeof p.message !== "string")
|
|
5558
|
+
return false;
|
|
5559
|
+
if (p.location !== void 0 && typeof p.location !== "string")
|
|
5560
|
+
return false;
|
|
5561
|
+
}
|
|
5562
|
+
return true;
|
|
5563
|
+
}
|
|
5564
|
+
function writeCache(baseDir, contentSha256, extraction) {
|
|
5565
|
+
const entry = {
|
|
5566
|
+
schema: CACHE_SCHEMA_VERSION,
|
|
5567
|
+
content_sha256: contentSha256,
|
|
5568
|
+
extraction
|
|
5569
|
+
};
|
|
5570
|
+
const path = cachePath(baseDir, contentSha256);
|
|
5571
|
+
try {
|
|
5572
|
+
mkdirSync7(dirname3(path), { recursive: true });
|
|
5573
|
+
const tmp = `${path}.tmp.${process.pid}.${Date.now()}`;
|
|
5574
|
+
writeFileSync11(tmp, JSON.stringify(entry));
|
|
5575
|
+
renameSync4(tmp, path);
|
|
5576
|
+
} catch {
|
|
5577
|
+
}
|
|
5578
|
+
}
|
|
5579
|
+
function rewriteSourceFile(cached, newPath) {
|
|
5580
|
+
const oldPath = cached.source_file;
|
|
5581
|
+
if (oldPath === newPath) {
|
|
5582
|
+
return cached;
|
|
5583
|
+
}
|
|
5584
|
+
const swap = (id) => {
|
|
5585
|
+
if (id.startsWith(`${oldPath}:`))
|
|
5586
|
+
return `${newPath}${id.slice(oldPath.length)}`;
|
|
5587
|
+
if (id.startsWith(`unresolved:${oldPath}:`)) {
|
|
5588
|
+
return `unresolved:${newPath}${id.slice(`unresolved:${oldPath}`.length)}`;
|
|
5589
|
+
}
|
|
5590
|
+
return id;
|
|
5591
|
+
};
|
|
5592
|
+
return {
|
|
5593
|
+
source_file: newPath,
|
|
5594
|
+
language: cached.language,
|
|
5595
|
+
// The synthetic module node uses source_file as its `label` (see
|
|
5596
|
+
// makeModuleNode in the extractor). On a cache hit after a rename/copy
|
|
5597
|
+
// we already rewrite `id` + `source_file`, but were leaving `label`
|
|
5598
|
+
// pointing at the OLD path — the snapshot then disagreed with a
|
|
5599
|
+
// fresh (non-cached) extraction. Rewrite `label` for module nodes too.
|
|
5600
|
+
// CodeRabbit P1.
|
|
5601
|
+
nodes: cached.nodes.map((n) => ({
|
|
5602
|
+
...n,
|
|
5603
|
+
id: swap(n.id),
|
|
5604
|
+
label: n.kind === "module" ? newPath : n.label,
|
|
5605
|
+
source_file: newPath
|
|
5606
|
+
})),
|
|
5607
|
+
edges: cached.edges.map((e) => ({ ...e, source: swap(e.source), target: swap(e.target) })),
|
|
5608
|
+
parse_errors: cached.parse_errors.map((p) => ({ ...p, source_file: newPath }))
|
|
5609
|
+
};
|
|
5610
|
+
}
|
|
5611
|
+
|
|
5612
|
+
// dist/src/graph/deeplake-push.js
|
|
5613
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
5614
|
+
async function pushSnapshot(snapshot, worktreeId, deps = {}) {
|
|
5615
|
+
if (process.env.HIVEMIND_GRAPH_PUSH === "0") {
|
|
5616
|
+
return { kind: "skipped-disabled" };
|
|
5617
|
+
}
|
|
5618
|
+
const config = (deps.loadConfig ?? loadConfig)();
|
|
5619
|
+
if (config === null) {
|
|
5620
|
+
return { kind: "skipped-no-auth" };
|
|
5621
|
+
}
|
|
5622
|
+
const commitSha = snapshot.graph.commit_sha;
|
|
5623
|
+
if (commitSha === null) {
|
|
5624
|
+
return { kind: "skipped-no-commit" };
|
|
5625
|
+
}
|
|
5626
|
+
const api = (deps.makeApi ?? defaultMakeApi)(config);
|
|
5627
|
+
try {
|
|
5628
|
+
await api.ensureCodebaseTable(config.codebaseTableName);
|
|
5629
|
+
} catch (err) {
|
|
5630
|
+
return errorOutcome("ensureCodebaseTable", err);
|
|
5631
|
+
}
|
|
5632
|
+
const snapshotSha256 = computeSnapshotSha256(snapshot);
|
|
5633
|
+
const tableId = sqlIdent(config.codebaseTableName);
|
|
5634
|
+
const repoSlug = snapshot.graph.repo_key;
|
|
5635
|
+
const userId = config.userName;
|
|
5636
|
+
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)}'`;
|
|
5637
|
+
let existing;
|
|
5638
|
+
try {
|
|
5639
|
+
existing = await api.query(selectSql);
|
|
5640
|
+
} catch (err) {
|
|
5641
|
+
return errorOutcome("SELECT existing", err);
|
|
5642
|
+
}
|
|
5643
|
+
if (existing.length > 0) {
|
|
5644
|
+
const cloudSha = String(existing[0].snapshot_sha256 ?? "");
|
|
5645
|
+
if (cloudSha === snapshotSha256) {
|
|
5646
|
+
return { kind: "already-current", commitSha };
|
|
5647
|
+
}
|
|
5648
|
+
return {
|
|
5649
|
+
kind: "drift",
|
|
5650
|
+
commitSha,
|
|
5651
|
+
localSha256: snapshotSha256,
|
|
5652
|
+
cloudSha256: cloudSha
|
|
5653
|
+
};
|
|
5654
|
+
}
|
|
5655
|
+
const canonical = canonicalJSON(snapshot);
|
|
5656
|
+
const observation = snapshot.observation;
|
|
5657
|
+
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})`;
|
|
5658
|
+
try {
|
|
5659
|
+
await api.query(insertSql);
|
|
5660
|
+
} catch (err) {
|
|
5661
|
+
return errorOutcome("INSERT", err);
|
|
5662
|
+
}
|
|
5663
|
+
try {
|
|
5664
|
+
const verify = await api.query(selectSql);
|
|
5665
|
+
if (verify.length > 1) {
|
|
5666
|
+
return { kind: "inserted-with-duplicate-race", commitSha, rowCount: verify.length };
|
|
5667
|
+
}
|
|
5668
|
+
} catch {
|
|
5669
|
+
}
|
|
5670
|
+
return { kind: "inserted", commitSha };
|
|
5671
|
+
}
|
|
5672
|
+
function defaultMakeApi(config) {
|
|
5673
|
+
return new DeeplakeApi(config.token, config.apiUrl, config.orgId, config.workspaceId, config.tableName);
|
|
5674
|
+
}
|
|
5675
|
+
function errorOutcome(stage, err) {
|
|
5676
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5677
|
+
return { kind: "error", message: `${stage}: ${message}` };
|
|
5678
|
+
}
|
|
5679
|
+
function computeSnapshotSha256(snapshot) {
|
|
5680
|
+
const stable = {
|
|
5681
|
+
directed: snapshot.directed,
|
|
5682
|
+
multigraph: snapshot.multigraph,
|
|
5683
|
+
graph: snapshot.graph,
|
|
5684
|
+
nodes: snapshot.nodes,
|
|
5685
|
+
links: snapshot.links
|
|
5686
|
+
};
|
|
5687
|
+
return createHash2("sha256").update(canonicalJSON(stable)).digest("hex");
|
|
5688
|
+
}
|
|
5689
|
+
function canonicalJSON(value) {
|
|
5690
|
+
return JSON.stringify(value, (_key, v) => {
|
|
5691
|
+
if (v !== null && typeof v === "object" && !Array.isArray(v)) {
|
|
5692
|
+
const sorted = {};
|
|
5693
|
+
for (const k of Object.keys(v).sort()) {
|
|
5694
|
+
sorted[k] = v[k];
|
|
5695
|
+
}
|
|
5696
|
+
return sorted;
|
|
5697
|
+
}
|
|
5698
|
+
return v;
|
|
5699
|
+
});
|
|
5700
|
+
}
|
|
5701
|
+
|
|
5702
|
+
// dist/src/graph/deeplake-pull.js
|
|
5703
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
5704
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
5705
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11, renameSync as renameSync7, writeFileSync as writeFileSync14 } from "node:fs";
|
|
5706
|
+
import { dirname as dirname7, join as join24 } from "node:path";
|
|
5707
|
+
|
|
5708
|
+
// dist/src/utils/repo-identity.js
|
|
5709
|
+
import { execSync as execSync2 } from "node:child_process";
|
|
5710
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
5711
|
+
import { basename, resolve as resolve2 } from "node:path";
|
|
5712
|
+
var DEFAULT_PORTS = {
|
|
5713
|
+
http: "80",
|
|
5714
|
+
https: "443",
|
|
5715
|
+
ssh: "22",
|
|
5716
|
+
git: "9418"
|
|
5717
|
+
};
|
|
5718
|
+
function normalizeGitRemoteUrl(url) {
|
|
5719
|
+
let s = url.trim();
|
|
5720
|
+
const schemeMatch = s.match(/^([a-z][a-z0-9+.-]*):\/\//i);
|
|
5721
|
+
const scheme = schemeMatch ? schemeMatch[1].toLowerCase() : null;
|
|
5722
|
+
if (schemeMatch)
|
|
5723
|
+
s = s.slice(schemeMatch[0].length);
|
|
5724
|
+
if (!scheme) {
|
|
5725
|
+
const scp = s.match(/^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/);
|
|
5726
|
+
if (scp)
|
|
5727
|
+
s = `${scp[1]}/${scp[2]}`;
|
|
5728
|
+
}
|
|
5729
|
+
s = s.replace(/^[^@/]+@/, "");
|
|
5730
|
+
if (scheme && DEFAULT_PORTS[scheme]) {
|
|
5731
|
+
s = s.replace(new RegExp(`^([^/]+):${DEFAULT_PORTS[scheme]}(/|$)`), "$1$2");
|
|
5732
|
+
}
|
|
5733
|
+
s = s.replace(/\.git\/?$/i, "");
|
|
5734
|
+
s = s.replace(/\/+$/, "");
|
|
5735
|
+
return s.toLowerCase();
|
|
5736
|
+
}
|
|
5737
|
+
function deriveProjectKey(cwd) {
|
|
5738
|
+
const absCwd = resolve2(cwd);
|
|
5739
|
+
const project = basename(absCwd) || "unknown";
|
|
5740
|
+
let signature = null;
|
|
5741
|
+
try {
|
|
5742
|
+
const raw = execSync2("git config --get remote.origin.url", {
|
|
5743
|
+
cwd: absCwd,
|
|
5744
|
+
encoding: "utf-8",
|
|
5745
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
5746
|
+
}).trim();
|
|
5747
|
+
signature = raw ? normalizeGitRemoteUrl(raw) : null;
|
|
5748
|
+
} catch {
|
|
5749
|
+
}
|
|
5750
|
+
const input = signature ?? absCwd;
|
|
5751
|
+
const key = createHash3("sha1").update(input).digest("hex").slice(0, 16);
|
|
5752
|
+
return { key, project };
|
|
5753
|
+
}
|
|
5754
|
+
|
|
5755
|
+
// dist/src/graph/last-build.js
|
|
5756
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8, readFileSync as readFileSync16, renameSync as renameSync5, writeFileSync as writeFileSync12 } from "node:fs";
|
|
5757
|
+
import { dirname as dirname4, join as join21 } from "node:path";
|
|
5758
|
+
function lastBuildPath(baseDir, worktreeId) {
|
|
5759
|
+
if (worktreeId !== void 0) {
|
|
5760
|
+
return join21(baseDir, "worktrees", worktreeId, ".last-build.json");
|
|
5761
|
+
}
|
|
5762
|
+
return join21(baseDir, ".last-build.json");
|
|
5763
|
+
}
|
|
5764
|
+
function writeLastBuild(baseDir, state, worktreeId) {
|
|
5765
|
+
const path = lastBuildPath(baseDir, worktreeId);
|
|
5766
|
+
try {
|
|
5767
|
+
mkdirSync8(dirname4(path), { recursive: true });
|
|
5768
|
+
const tmp = `${path}.tmp.${process.pid}.${Date.now()}`;
|
|
5769
|
+
writeFileSync12(tmp, JSON.stringify(state));
|
|
5770
|
+
renameSync5(tmp, path);
|
|
5771
|
+
} catch {
|
|
5772
|
+
}
|
|
5773
|
+
}
|
|
5774
|
+
function readLastBuild(baseDir, worktreeId) {
|
|
5775
|
+
let path = lastBuildPath(baseDir, worktreeId);
|
|
5776
|
+
if (!existsSync16(path)) {
|
|
5777
|
+
if (worktreeId === void 0)
|
|
5778
|
+
return null;
|
|
5779
|
+
const legacy = lastBuildPath(baseDir, void 0);
|
|
5780
|
+
if (!existsSync16(legacy))
|
|
5781
|
+
return null;
|
|
5782
|
+
path = legacy;
|
|
5783
|
+
}
|
|
5784
|
+
let raw;
|
|
5785
|
+
try {
|
|
5786
|
+
raw = readFileSync16(path, "utf8");
|
|
5787
|
+
} catch {
|
|
5788
|
+
return null;
|
|
5789
|
+
}
|
|
5790
|
+
let parsed;
|
|
5791
|
+
try {
|
|
5792
|
+
parsed = JSON.parse(raw);
|
|
5793
|
+
} catch {
|
|
5794
|
+
return null;
|
|
5795
|
+
}
|
|
5796
|
+
if (parsed === null || typeof parsed !== "object")
|
|
5797
|
+
return null;
|
|
5798
|
+
const o = parsed;
|
|
5799
|
+
if (typeof o.ts !== "number" || !Number.isFinite(o.ts))
|
|
5800
|
+
return null;
|
|
5801
|
+
if (o.commit_sha !== null && typeof o.commit_sha !== "string")
|
|
5802
|
+
return null;
|
|
5803
|
+
if (typeof o.snapshot_sha256 !== "string")
|
|
5804
|
+
return null;
|
|
5805
|
+
const out = { ts: o.ts, commit_sha: o.commit_sha, snapshot_sha256: o.snapshot_sha256 };
|
|
5806
|
+
if (typeof o.node_count === "number" && Number.isFinite(o.node_count) && o.node_count >= 0) {
|
|
5807
|
+
out.node_count = o.node_count;
|
|
5808
|
+
}
|
|
5809
|
+
if (typeof o.edge_count === "number" && Number.isFinite(o.edge_count) && o.edge_count >= 0) {
|
|
5810
|
+
out.edge_count = o.edge_count;
|
|
5811
|
+
}
|
|
5812
|
+
return out;
|
|
5813
|
+
}
|
|
5814
|
+
|
|
5815
|
+
// dist/src/graph/history.js
|
|
5816
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync17, mkdirSync as mkdirSync9, readFileSync as readFileSync17 } from "node:fs";
|
|
5817
|
+
import { dirname as dirname5, join as join22 } from "node:path";
|
|
5818
|
+
function historyPath(baseDir) {
|
|
5819
|
+
return join22(baseDir, "history.jsonl");
|
|
5820
|
+
}
|
|
5821
|
+
function appendHistoryEntry(baseDir, entry) {
|
|
5822
|
+
const path = historyPath(baseDir);
|
|
5823
|
+
try {
|
|
5824
|
+
mkdirSync9(dirname5(path), { recursive: true });
|
|
5825
|
+
appendFileSync2(path, JSON.stringify(entry) + "\n");
|
|
5826
|
+
} catch {
|
|
5827
|
+
}
|
|
5828
|
+
}
|
|
5829
|
+
function entryFromSnapshot(snapshot, snapshot_sha256, trigger) {
|
|
5830
|
+
return {
|
|
5831
|
+
ts: snapshot.observation.ts,
|
|
5832
|
+
commit_sha: snapshot.graph.commit_sha,
|
|
5833
|
+
snapshot_sha256,
|
|
5834
|
+
node_count: snapshot.nodes.length,
|
|
5835
|
+
edge_count: snapshot.links.length,
|
|
5836
|
+
trigger
|
|
5837
|
+
};
|
|
5838
|
+
}
|
|
5839
|
+
function readHistoryTail(baseDir, n) {
|
|
5840
|
+
const path = historyPath(baseDir);
|
|
5841
|
+
if (!existsSync17(path))
|
|
5842
|
+
return [];
|
|
5843
|
+
let raw;
|
|
5844
|
+
try {
|
|
5845
|
+
raw = readFileSync17(path, "utf8");
|
|
5846
|
+
} catch {
|
|
5847
|
+
return [];
|
|
5848
|
+
}
|
|
5849
|
+
const lines = raw.split("\n").filter((l) => l.length > 0);
|
|
5850
|
+
const entries = [];
|
|
5851
|
+
for (let i = lines.length - 1; i >= 0 && entries.length < n; i--) {
|
|
5852
|
+
const parsed = parseLine(lines[i]);
|
|
5853
|
+
if (parsed !== null)
|
|
5854
|
+
entries.unshift(parsed);
|
|
5855
|
+
}
|
|
5856
|
+
return entries;
|
|
5857
|
+
}
|
|
5858
|
+
function countHistoryEntries(baseDir) {
|
|
5859
|
+
const path = historyPath(baseDir);
|
|
5860
|
+
if (!existsSync17(path))
|
|
5861
|
+
return 0;
|
|
5862
|
+
try {
|
|
5863
|
+
const raw = readFileSync17(path, "utf8");
|
|
5864
|
+
return raw.split("\n").filter((l) => l.length > 0).length;
|
|
5865
|
+
} catch {
|
|
5866
|
+
return 0;
|
|
5867
|
+
}
|
|
5868
|
+
}
|
|
5869
|
+
function parseLine(line) {
|
|
5870
|
+
let obj;
|
|
5871
|
+
try {
|
|
5872
|
+
obj = JSON.parse(line);
|
|
5873
|
+
} catch {
|
|
5874
|
+
return null;
|
|
5875
|
+
}
|
|
5876
|
+
if (obj === null || typeof obj !== "object")
|
|
5877
|
+
return null;
|
|
5878
|
+
const o = obj;
|
|
5879
|
+
if (typeof o.ts !== "string")
|
|
5880
|
+
return null;
|
|
5881
|
+
if (o.commit_sha !== null && typeof o.commit_sha !== "string")
|
|
5882
|
+
return null;
|
|
5883
|
+
if (typeof o.snapshot_sha256 !== "string")
|
|
5884
|
+
return null;
|
|
5885
|
+
if (typeof o.node_count !== "number")
|
|
5886
|
+
return null;
|
|
5887
|
+
if (typeof o.edge_count !== "number")
|
|
5888
|
+
return null;
|
|
5889
|
+
if (typeof o.trigger !== "string")
|
|
5890
|
+
return null;
|
|
5891
|
+
return {
|
|
5892
|
+
ts: o.ts,
|
|
5893
|
+
commit_sha: o.commit_sha,
|
|
5894
|
+
snapshot_sha256: o.snapshot_sha256,
|
|
5895
|
+
node_count: o.node_count,
|
|
5896
|
+
edge_count: o.edge_count,
|
|
5897
|
+
trigger: o.trigger
|
|
5898
|
+
};
|
|
5899
|
+
}
|
|
5900
|
+
|
|
5901
|
+
// dist/src/graph/snapshot.js
|
|
5902
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
5903
|
+
import { mkdirSync as mkdirSync10, renameSync as renameSync6, writeFileSync as writeFileSync13 } from "node:fs";
|
|
5904
|
+
import { homedir as homedir10 } from "node:os";
|
|
5905
|
+
import { dirname as dirname6, join as join23 } from "node:path";
|
|
5906
|
+
function graphsRoot() {
|
|
5907
|
+
return process.env.HIVEMIND_GRAPHS_HOME ?? join23(homedir10(), ".hivemind", "graphs");
|
|
5908
|
+
}
|
|
5909
|
+
function repoDir(repoKey) {
|
|
5910
|
+
return join23(graphsRoot(), repoKey);
|
|
5911
|
+
}
|
|
5912
|
+
function buildSnapshot(extractions, metadata, observation) {
|
|
5913
|
+
const nodes = [];
|
|
5914
|
+
const links = [];
|
|
5915
|
+
for (const ex of extractions) {
|
|
5916
|
+
for (const n of ex.nodes)
|
|
5917
|
+
nodes.push(n);
|
|
5918
|
+
for (const e of ex.edges)
|
|
5919
|
+
links.push(e);
|
|
5920
|
+
}
|
|
5921
|
+
nodes.sort(compareNodes);
|
|
5922
|
+
links.sort(compareEdges);
|
|
5923
|
+
return {
|
|
5924
|
+
directed: true,
|
|
5925
|
+
multigraph: true,
|
|
5926
|
+
graph: metadata,
|
|
5927
|
+
observation,
|
|
5928
|
+
nodes,
|
|
5929
|
+
links
|
|
5930
|
+
};
|
|
5931
|
+
}
|
|
5932
|
+
function compareNodes(a, b) {
|
|
5933
|
+
return cmp(a.id, b.id);
|
|
5934
|
+
}
|
|
5935
|
+
function compareEdges(a, b) {
|
|
5936
|
+
let c = cmp(a.source, b.source);
|
|
5937
|
+
if (c !== 0)
|
|
5938
|
+
return c;
|
|
5939
|
+
c = cmp(a.target, b.target);
|
|
5940
|
+
if (c !== 0)
|
|
5941
|
+
return c;
|
|
5942
|
+
c = cmp(a.relation, b.relation);
|
|
5943
|
+
if (c !== 0)
|
|
5944
|
+
return c;
|
|
5945
|
+
return (a.ord ?? 0) - (b.ord ?? 0);
|
|
5946
|
+
}
|
|
5947
|
+
function cmp(a, b) {
|
|
5948
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
5949
|
+
}
|
|
5950
|
+
function canonicalSnapshot(snapshot) {
|
|
5951
|
+
return canonicalJSON2(snapshot);
|
|
5952
|
+
}
|
|
5953
|
+
function computeSnapshotSha2562(snapshot) {
|
|
5954
|
+
const stable = {
|
|
5955
|
+
directed: snapshot.directed,
|
|
5956
|
+
multigraph: snapshot.multigraph,
|
|
5957
|
+
graph: snapshot.graph,
|
|
5958
|
+
nodes: snapshot.nodes,
|
|
5959
|
+
links: snapshot.links
|
|
5960
|
+
};
|
|
5961
|
+
return createHash4("sha256").update(canonicalJSON2(stable)).digest("hex");
|
|
5962
|
+
}
|
|
5963
|
+
function canonicalJSON2(value) {
|
|
5964
|
+
return JSON.stringify(value, (_key, v) => {
|
|
5965
|
+
if (v !== null && typeof v === "object" && !Array.isArray(v)) {
|
|
5966
|
+
const sorted = {};
|
|
5967
|
+
for (const k of Object.keys(v).sort()) {
|
|
5968
|
+
sorted[k] = v[k];
|
|
5969
|
+
}
|
|
5970
|
+
return sorted;
|
|
5971
|
+
}
|
|
5972
|
+
return v;
|
|
5973
|
+
});
|
|
5974
|
+
}
|
|
5975
|
+
function writeSnapshot(snapshot, baseDir, trigger = "unknown", worktreeId) {
|
|
5976
|
+
const sha256 = computeSnapshotSha2562(snapshot);
|
|
5977
|
+
const commitSha = snapshot.graph.commit_sha;
|
|
5978
|
+
const fileBase = commitSha ?? sha256;
|
|
5979
|
+
const snapshotsDir = join23(baseDir, "snapshots");
|
|
5980
|
+
const snapshotPath = join23(snapshotsDir, `${fileBase}.json`);
|
|
5981
|
+
const canonical = canonicalSnapshot(snapshot);
|
|
5982
|
+
writeFileAtomic(snapshotPath, canonical);
|
|
5983
|
+
const worktreeRoot = worktreeId !== void 0 ? join23(baseDir, "worktrees", worktreeId) : baseDir;
|
|
5984
|
+
let latestCommitPath = null;
|
|
5985
|
+
if (commitSha !== null) {
|
|
5986
|
+
latestCommitPath = join23(worktreeRoot, "latest-commit.txt");
|
|
5987
|
+
writeFileAtomic(latestCommitPath, `${commitSha}
|
|
5988
|
+
`);
|
|
5989
|
+
}
|
|
5990
|
+
writeLastBuild(baseDir, {
|
|
5991
|
+
ts: Date.now(),
|
|
5992
|
+
commit_sha: commitSha,
|
|
5993
|
+
snapshot_sha256: sha256,
|
|
5994
|
+
node_count: snapshot.nodes.length,
|
|
5995
|
+
edge_count: snapshot.links.length
|
|
5996
|
+
}, worktreeId);
|
|
5997
|
+
appendHistoryEntry(baseDir, entryFromSnapshot(snapshot, sha256, trigger));
|
|
5998
|
+
return { snapshotPath, latestCommitPath, snapshotSha256: sha256 };
|
|
5999
|
+
}
|
|
6000
|
+
function writeFileAtomic(filePath, contents) {
|
|
6001
|
+
mkdirSync10(dirname6(filePath), { recursive: true });
|
|
6002
|
+
const tmp = `${filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
6003
|
+
writeFileSync13(tmp, contents);
|
|
6004
|
+
renameSync6(tmp, filePath);
|
|
6005
|
+
}
|
|
6006
|
+
|
|
6007
|
+
// dist/src/graph/deeplake-pull.js
|
|
6008
|
+
function workTreeIdFor(cwd) {
|
|
6009
|
+
return createHash5("sha256").update(cwd).digest("hex").slice(0, 16);
|
|
6010
|
+
}
|
|
6011
|
+
async function pullSnapshot(cwd, deps = {}) {
|
|
6012
|
+
if (process.env.HIVEMIND_GRAPH_PULL === "0") {
|
|
6013
|
+
return { kind: "skipped-disabled" };
|
|
6014
|
+
}
|
|
6015
|
+
const config = (deps.loadConfig ?? loadConfig)();
|
|
6016
|
+
if (config === null) {
|
|
6017
|
+
return { kind: "skipped-no-auth" };
|
|
6018
|
+
}
|
|
6019
|
+
const head = (deps.readHead ?? defaultReadHead)(cwd);
|
|
6020
|
+
if (head === null) {
|
|
6021
|
+
return { kind: "skipped-no-head" };
|
|
6022
|
+
}
|
|
6023
|
+
const api = (deps.makeApi ?? defaultMakeApi2)(config);
|
|
6024
|
+
try {
|
|
6025
|
+
await api.ensureCodebaseTable(config.codebaseTableName);
|
|
6026
|
+
} catch (err) {
|
|
6027
|
+
return errorOutcome2("ensureCodebaseTable", err);
|
|
6028
|
+
}
|
|
6029
|
+
const tableId = sqlIdent(config.codebaseTableName);
|
|
6030
|
+
const { key: repoKey } = deriveProjectKey(cwd);
|
|
6031
|
+
const selectSql = `SELECT snapshot_jsonb, snapshot_sha256, ts, node_count, edge_count, branch, generator_version, worktree_id FROM "${tableId}" WHERE org_id = '${sqlStr(config.orgId)}' AND workspace_id = '${sqlStr(config.workspaceId)}' AND repo_slug = '${sqlStr(repoKey)}' AND user_id = '${sqlStr(config.userName)}' AND commit_sha = '${sqlStr(head)}' ORDER BY ts DESC LIMIT 1`;
|
|
6032
|
+
let rows;
|
|
6033
|
+
try {
|
|
6034
|
+
rows = await api.query(selectSql);
|
|
6035
|
+
} catch (err) {
|
|
6036
|
+
return errorOutcome2("SELECT cloud row", err);
|
|
6037
|
+
}
|
|
6038
|
+
if (rows.length === 0) {
|
|
6039
|
+
return { kind: "no-cloud-row", commitSha: head };
|
|
6040
|
+
}
|
|
6041
|
+
const row = rows[0];
|
|
6042
|
+
const cloudSha256 = String(row.snapshot_sha256 ?? "").trim();
|
|
6043
|
+
const cloudPayload = coerceSnapshotPayload(row.snapshot_jsonb);
|
|
6044
|
+
if (cloudPayload === null) {
|
|
6045
|
+
return errorOutcome2("SELECT cloud row", new Error("invalid snapshot_jsonb payload"));
|
|
6046
|
+
}
|
|
6047
|
+
if (cloudSha256 !== "") {
|
|
6048
|
+
const computedSha = createHash5("sha256").update(cloudPayload).digest("hex");
|
|
6049
|
+
if (cloudSha256 !== computedSha) {
|
|
6050
|
+
return errorOutcome2("SELECT cloud row", new Error(`snapshot_sha256 mismatch (expected ${cloudSha256}, got ${computedSha})`));
|
|
6051
|
+
}
|
|
6052
|
+
}
|
|
6053
|
+
const cloudTs = parseTs(row.ts);
|
|
6054
|
+
const baseDir = repoDir(repoKey);
|
|
6055
|
+
const worktreeId = workTreeIdFor(cwd);
|
|
6056
|
+
const local = readLastBuild(baseDir, worktreeId);
|
|
6057
|
+
if (local !== null && local.commit_sha === head) {
|
|
6058
|
+
if (cloudSha256 !== "" && local.snapshot_sha256 === cloudSha256) {
|
|
6059
|
+
return { kind: "up-to-date", commitSha: head, snapshotSha256: cloudSha256 };
|
|
6060
|
+
}
|
|
6061
|
+
if (local.ts > cloudTs) {
|
|
6062
|
+
return {
|
|
6063
|
+
kind: "local-newer",
|
|
6064
|
+
commitSha: head,
|
|
6065
|
+
localTs: local.ts,
|
|
6066
|
+
cloudTs
|
|
6067
|
+
};
|
|
6068
|
+
}
|
|
6069
|
+
}
|
|
6070
|
+
const snapshotsDir = join24(baseDir, "snapshots");
|
|
6071
|
+
const snapshotPath = join24(snapshotsDir, `${head}.json`);
|
|
6072
|
+
const worktreeRoot = join24(baseDir, "worktrees", worktreeId);
|
|
6073
|
+
try {
|
|
6074
|
+
writeFileAtomic2(snapshotPath, cloudPayload);
|
|
6075
|
+
writeFileAtomic2(join24(worktreeRoot, "latest-commit.txt"), `${head}
|
|
6076
|
+
`);
|
|
6077
|
+
writeLastBuild(baseDir, {
|
|
6078
|
+
ts: cloudTs,
|
|
6079
|
+
commit_sha: head,
|
|
6080
|
+
snapshot_sha256: cloudSha256,
|
|
6081
|
+
node_count: numOrUndefined(row.node_count),
|
|
6082
|
+
edge_count: numOrUndefined(row.edge_count)
|
|
6083
|
+
}, worktreeId);
|
|
6084
|
+
appendHistoryEntry(baseDir, {
|
|
6085
|
+
ts: new Date(cloudTs).toISOString(),
|
|
6086
|
+
commit_sha: head,
|
|
6087
|
+
snapshot_sha256: cloudSha256,
|
|
6088
|
+
node_count: Number(row.node_count ?? 0),
|
|
6089
|
+
edge_count: Number(row.edge_count ?? 0),
|
|
6090
|
+
trigger: "pull"
|
|
6091
|
+
});
|
|
6092
|
+
} catch (err) {
|
|
6093
|
+
return errorOutcome2("write local files", err);
|
|
6094
|
+
}
|
|
6095
|
+
return {
|
|
6096
|
+
kind: "pulled",
|
|
6097
|
+
commitSha: head,
|
|
6098
|
+
snapshotSha256: cloudSha256,
|
|
6099
|
+
bytes: Buffer.byteLength(cloudPayload, "utf8"),
|
|
6100
|
+
cloudTs,
|
|
6101
|
+
sourceWorktreePath: String(row.worktree_id ?? "")
|
|
6102
|
+
};
|
|
6103
|
+
}
|
|
6104
|
+
function defaultReadHead(cwd) {
|
|
6105
|
+
try {
|
|
6106
|
+
return execFileSync4("git", ["rev-parse", "HEAD"], {
|
|
6107
|
+
cwd,
|
|
6108
|
+
encoding: "utf8",
|
|
6109
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
6110
|
+
}).trim();
|
|
6111
|
+
} catch {
|
|
6112
|
+
return null;
|
|
6113
|
+
}
|
|
6114
|
+
}
|
|
6115
|
+
function defaultMakeApi2(config) {
|
|
6116
|
+
return new DeeplakeApi(config.token, config.apiUrl, config.orgId, config.workspaceId, config.tableName);
|
|
6117
|
+
}
|
|
6118
|
+
function parseTs(raw) {
|
|
6119
|
+
if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
6120
|
+
return raw < 1e12 ? raw * 1e3 : raw;
|
|
6121
|
+
}
|
|
6122
|
+
if (typeof raw === "string") {
|
|
6123
|
+
const parsed = Date.parse(raw);
|
|
6124
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
6125
|
+
}
|
|
6126
|
+
return 0;
|
|
6127
|
+
}
|
|
6128
|
+
function numOrUndefined(raw) {
|
|
6129
|
+
if (typeof raw === "number" && Number.isFinite(raw) && raw >= 0)
|
|
6130
|
+
return raw;
|
|
6131
|
+
if (typeof raw === "string") {
|
|
6132
|
+
const n = Number(raw);
|
|
6133
|
+
if (Number.isFinite(n) && n >= 0)
|
|
6134
|
+
return n;
|
|
6135
|
+
}
|
|
6136
|
+
return void 0;
|
|
6137
|
+
}
|
|
6138
|
+
function coerceSnapshotPayload(raw) {
|
|
6139
|
+
if (typeof raw === "string")
|
|
6140
|
+
return raw;
|
|
6141
|
+
if (raw !== null && typeof raw === "object")
|
|
6142
|
+
return JSON.stringify(raw);
|
|
6143
|
+
return null;
|
|
6144
|
+
}
|
|
6145
|
+
function writeFileAtomic2(filePath, contents) {
|
|
6146
|
+
mkdirSync11(dirname7(filePath), { recursive: true });
|
|
6147
|
+
const tmp = `${filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
6148
|
+
writeFileSync14(tmp, contents);
|
|
6149
|
+
renameSync7(tmp, filePath);
|
|
6150
|
+
}
|
|
6151
|
+
function errorOutcome2(stage, err) {
|
|
6152
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6153
|
+
return { kind: "error", message: `${stage}: ${message}` };
|
|
6154
|
+
}
|
|
6155
|
+
|
|
6156
|
+
// dist/src/graph/diff.js
|
|
6157
|
+
import { existsSync as existsSync19, readFileSync as readFileSync18 } from "node:fs";
|
|
6158
|
+
import { join as join25 } from "node:path";
|
|
6159
|
+
function edgeKey(e) {
|
|
6160
|
+
return `${e.source}${e.target}${e.relation}${e.ord ?? 0}`;
|
|
6161
|
+
}
|
|
6162
|
+
function diffSnapshots(from, to) {
|
|
6163
|
+
const fromNodeIds = new Set(from.nodes.map((n) => n.id));
|
|
6164
|
+
const toNodeIds = new Set(to.nodes.map((n) => n.id));
|
|
6165
|
+
const nodesAdded = to.nodes.filter((n) => !fromNodeIds.has(n.id));
|
|
6166
|
+
const nodesRemoved = from.nodes.filter((n) => !toNodeIds.has(n.id));
|
|
6167
|
+
const fromEdgeKeys = new Set(from.links.map(edgeKey));
|
|
6168
|
+
const toEdgeKeys = new Set(to.links.map(edgeKey));
|
|
6169
|
+
const edgesAdded = to.links.filter((e) => !fromEdgeKeys.has(edgeKey(e)));
|
|
6170
|
+
const edgesRemoved = from.links.filter((e) => !toEdgeKeys.has(edgeKey(e)));
|
|
6171
|
+
return {
|
|
6172
|
+
nodes: { added: nodesAdded, removed: nodesRemoved },
|
|
6173
|
+
edges: { added: edgesAdded, removed: edgesRemoved },
|
|
6174
|
+
counts: {
|
|
6175
|
+
nodes_added: nodesAdded.length,
|
|
6176
|
+
nodes_removed: nodesRemoved.length,
|
|
6177
|
+
edges_added: edgesAdded.length,
|
|
6178
|
+
edges_removed: edgesRemoved.length
|
|
6179
|
+
}
|
|
6180
|
+
};
|
|
6181
|
+
}
|
|
6182
|
+
function loadSnapshotByCommit(baseDir, commitSha) {
|
|
6183
|
+
if (!/^[0-9a-f]{4,64}$/i.test(commitSha))
|
|
6184
|
+
return null;
|
|
6185
|
+
const path = join25(baseDir, "snapshots", `${commitSha}.json`);
|
|
6186
|
+
if (!existsSync19(path))
|
|
6187
|
+
return null;
|
|
6188
|
+
let raw;
|
|
6189
|
+
try {
|
|
6190
|
+
raw = readFileSync18(path, "utf8");
|
|
6191
|
+
} catch {
|
|
6192
|
+
return null;
|
|
6193
|
+
}
|
|
6194
|
+
try {
|
|
6195
|
+
const parsed = JSON.parse(raw);
|
|
6196
|
+
if (!isGraphSnapshotLike(parsed))
|
|
6197
|
+
return null;
|
|
6198
|
+
return parsed;
|
|
6199
|
+
} catch {
|
|
6200
|
+
return null;
|
|
6201
|
+
}
|
|
6202
|
+
}
|
|
6203
|
+
function isGraphSnapshotLike(v) {
|
|
6204
|
+
if (v === null || typeof v !== "object")
|
|
6205
|
+
return false;
|
|
6206
|
+
const s = v;
|
|
6207
|
+
return Array.isArray(s.nodes) && Array.isArray(s.links);
|
|
6208
|
+
}
|
|
6209
|
+
function printDiffHuman(diff, sampleSize = 10) {
|
|
6210
|
+
const { counts } = diff;
|
|
6211
|
+
console.log(`Nodes: +${counts.nodes_added} -${counts.nodes_removed} Edges: +${counts.edges_added} -${counts.edges_removed}`);
|
|
6212
|
+
const showNodes = (label, ns) => {
|
|
6213
|
+
if (ns.length === 0)
|
|
6214
|
+
return;
|
|
6215
|
+
console.log("");
|
|
6216
|
+
console.log(`${label} (${ns.length}, showing up to ${sampleSize}):`);
|
|
6217
|
+
const sorted = [...ns].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
6218
|
+
for (const n of sorted.slice(0, sampleSize)) {
|
|
6219
|
+
console.log(` ${n.id} [${n.kind}]${n.exported ? " (exported)" : ""} ${n.source_file}:${n.source_location}`);
|
|
6220
|
+
}
|
|
6221
|
+
if (sorted.length > sampleSize)
|
|
6222
|
+
console.log(` \u2026 and ${sorted.length - sampleSize} more`);
|
|
6223
|
+
};
|
|
6224
|
+
const showEdges = (label, es) => {
|
|
6225
|
+
if (es.length === 0)
|
|
6226
|
+
return;
|
|
6227
|
+
console.log("");
|
|
6228
|
+
console.log(`${label} (${es.length}, showing up to ${sampleSize}):`);
|
|
6229
|
+
const sorted = [...es].sort((a, b) => edgeKey(a) < edgeKey(b) ? -1 : edgeKey(a) > edgeKey(b) ? 1 : 0);
|
|
6230
|
+
for (const e of sorted.slice(0, sampleSize)) {
|
|
6231
|
+
console.log(` ${e.source} --${e.relation}--> ${e.target}${e.ord !== void 0 ? ` (ord=${e.ord})` : ""}`);
|
|
6232
|
+
}
|
|
6233
|
+
if (sorted.length > sampleSize)
|
|
6234
|
+
console.log(` \u2026 and ${sorted.length - sampleSize} more`);
|
|
6235
|
+
};
|
|
6236
|
+
showNodes("Nodes added", diff.nodes.added);
|
|
6237
|
+
showNodes("Nodes removed", diff.nodes.removed);
|
|
6238
|
+
showEdges("Edges added", diff.edges.added);
|
|
6239
|
+
showEdges("Edges removed", diff.edges.removed);
|
|
6240
|
+
}
|
|
6241
|
+
|
|
6242
|
+
// dist/src/graph/extract/typescript.js
|
|
6243
|
+
import Parser from "tree-sitter";
|
|
6244
|
+
import TypeScript from "tree-sitter-typescript";
|
|
6245
|
+
var _typescriptParser = null;
|
|
6246
|
+
var _tsxParser = null;
|
|
6247
|
+
function getTypescriptParser() {
|
|
6248
|
+
if (_typescriptParser === null) {
|
|
6249
|
+
_typescriptParser = new Parser();
|
|
6250
|
+
_typescriptParser.setLanguage(TypeScript.typescript);
|
|
6251
|
+
}
|
|
6252
|
+
return _typescriptParser;
|
|
6253
|
+
}
|
|
6254
|
+
function getTsxParser() {
|
|
6255
|
+
if (_tsxParser === null) {
|
|
6256
|
+
_tsxParser = new Parser();
|
|
6257
|
+
_tsxParser.setLanguage(TypeScript.tsx);
|
|
6258
|
+
}
|
|
6259
|
+
return _tsxParser;
|
|
6260
|
+
}
|
|
6261
|
+
function pickParserForPath(relativePath) {
|
|
6262
|
+
return relativePath.endsWith(".tsx") || relativePath.endsWith(".jsx") ? getTsxParser() : getTypescriptParser();
|
|
6263
|
+
}
|
|
6264
|
+
function extractTypeScript(sourceCode, relativePath) {
|
|
6265
|
+
const parser = pickParserForPath(relativePath);
|
|
6266
|
+
const CHUNK_BYTES = 16384;
|
|
6267
|
+
const tree = parser.parse((index) => {
|
|
6268
|
+
if (index >= sourceCode.length)
|
|
6269
|
+
return null;
|
|
6270
|
+
return sourceCode.slice(index, index + CHUNK_BYTES);
|
|
6271
|
+
});
|
|
6272
|
+
const root = tree.rootNode;
|
|
6273
|
+
const result = {
|
|
6274
|
+
source_file: relativePath,
|
|
6275
|
+
language: "typescript",
|
|
6276
|
+
nodes: [],
|
|
6277
|
+
edges: [],
|
|
6278
|
+
parse_errors: []
|
|
6279
|
+
};
|
|
6280
|
+
collectParseErrors(root, relativePath, result.parse_errors);
|
|
6281
|
+
const moduleNode = makeModuleNode(relativePath);
|
|
6282
|
+
result.nodes.push(moduleNode);
|
|
6283
|
+
const declByName = /* @__PURE__ */ new Map();
|
|
6284
|
+
extractDeclarations(root, relativePath, result, declByName, moduleNode);
|
|
6285
|
+
extractImports(root, relativePath, result, moduleNode);
|
|
6286
|
+
extractCalls(root, relativePath, result, declByName);
|
|
6287
|
+
return result;
|
|
6288
|
+
}
|
|
6289
|
+
function collectParseErrors(node, relativePath, out) {
|
|
6290
|
+
if (node.isError || node.isMissing) {
|
|
6291
|
+
out.push({
|
|
6292
|
+
source_file: relativePath,
|
|
6293
|
+
message: node.isMissing ? `missing node: ${node.type}` : `parse error at ${locationStr(node)}`,
|
|
6294
|
+
location: locationStr(node)
|
|
6295
|
+
});
|
|
6296
|
+
return;
|
|
6297
|
+
}
|
|
6298
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6299
|
+
const child = node.namedChild(i);
|
|
6300
|
+
if (child !== null)
|
|
6301
|
+
collectParseErrors(child, relativePath, out);
|
|
6302
|
+
}
|
|
6303
|
+
}
|
|
6304
|
+
function extractDeclarations(node, relativePath, result, declByName, moduleNode) {
|
|
6305
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6306
|
+
const child = node.namedChild(i);
|
|
6307
|
+
if (child === null)
|
|
6308
|
+
continue;
|
|
6309
|
+
const { decl, exported } = unwrapExport(child);
|
|
6310
|
+
if (decl !== null) {
|
|
6311
|
+
handleDeclaration(decl, exported, relativePath, result, declByName, moduleNode);
|
|
6312
|
+
}
|
|
6313
|
+
if (child.type === "internal_module" || child.type === "module") {
|
|
6314
|
+
extractDeclarations(child, relativePath, result, declByName, moduleNode);
|
|
6315
|
+
}
|
|
6316
|
+
}
|
|
6317
|
+
}
|
|
6318
|
+
function unwrapExport(node) {
|
|
6319
|
+
if (node.type === "export_statement") {
|
|
6320
|
+
const decl = node.childForFieldName("declaration") ?? firstNamedChildOfTypes(node, [
|
|
6321
|
+
"function_declaration",
|
|
6322
|
+
"class_declaration",
|
|
6323
|
+
"interface_declaration",
|
|
6324
|
+
"type_alias_declaration",
|
|
6325
|
+
"enum_declaration",
|
|
6326
|
+
"lexical_declaration"
|
|
6327
|
+
]);
|
|
6328
|
+
return { decl, exported: true };
|
|
6329
|
+
}
|
|
6330
|
+
return { decl: node, exported: false };
|
|
6331
|
+
}
|
|
6332
|
+
function handleDeclaration(node, exported, relativePath, result, declByName, moduleNode) {
|
|
6333
|
+
switch (node.type) {
|
|
6334
|
+
case "function_declaration": {
|
|
6335
|
+
const name = textOfField(node, "name");
|
|
6336
|
+
if (name === null)
|
|
6337
|
+
return;
|
|
6338
|
+
const decl = makeNode(relativePath, name, "function", node, exported);
|
|
6339
|
+
pushNode(result, declByName, decl);
|
|
6340
|
+
return;
|
|
6341
|
+
}
|
|
6342
|
+
case "class_declaration": {
|
|
6343
|
+
const name = textOfField(node, "name");
|
|
6344
|
+
if (name === null)
|
|
6345
|
+
return;
|
|
6346
|
+
const classNode = makeNode(relativePath, name, "class", node, exported);
|
|
6347
|
+
pushNode(result, declByName, classNode);
|
|
6348
|
+
const heritage = firstNamedChildOfTypes(node, ["class_heritage"]);
|
|
6349
|
+
if (heritage !== null) {
|
|
6350
|
+
for (let i = 0; i < heritage.namedChildCount; i++) {
|
|
6351
|
+
const clause = heritage.namedChild(i);
|
|
6352
|
+
if (clause === null)
|
|
6353
|
+
continue;
|
|
6354
|
+
const relation = clause.type === "extends_clause" ? "extends" : clause.type === "implements_clause" ? "implements" : null;
|
|
6355
|
+
if (relation === null)
|
|
6356
|
+
continue;
|
|
6357
|
+
for (let j = 0; j < clause.namedChildCount; j++) {
|
|
6358
|
+
const base = clause.namedChild(j);
|
|
6359
|
+
if (base === null)
|
|
6360
|
+
continue;
|
|
6361
|
+
const baseName = base.text;
|
|
6362
|
+
if (baseName.length === 0)
|
|
6363
|
+
continue;
|
|
6364
|
+
result.edges.push({
|
|
6365
|
+
source: classNode.id,
|
|
6366
|
+
target: nodeIdUnresolved(relativePath, baseName, relation === "extends" ? "class" : "interface"),
|
|
6367
|
+
relation,
|
|
6368
|
+
confidence: "EXTRACTED"
|
|
6369
|
+
});
|
|
6370
|
+
}
|
|
6371
|
+
}
|
|
6372
|
+
}
|
|
6373
|
+
const body = firstNamedChildOfTypes(node, ["class_body"]);
|
|
6374
|
+
if (body !== null) {
|
|
6375
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
6376
|
+
const member = body.namedChild(i);
|
|
6377
|
+
if (member === null)
|
|
6378
|
+
continue;
|
|
6379
|
+
if (member.type === "method_definition") {
|
|
6380
|
+
const methodName = textOfField(member, "name");
|
|
6381
|
+
if (methodName === null)
|
|
6382
|
+
continue;
|
|
6383
|
+
const accessibility = firstNamedChildOfTypes(member, ["accessibility_modifier"]);
|
|
6384
|
+
const isHardPrivate = firstNamedChildOfTypes(member, ["private_property_identifier"]) !== null;
|
|
6385
|
+
const isPublic = !isHardPrivate && (accessibility === null || accessibility.text === "public");
|
|
6386
|
+
const methodExported = exported && isPublic;
|
|
6387
|
+
const methodKey = `${classNode.label}.${methodName}`;
|
|
6388
|
+
const methodNode = makeNodeWithExplicitLabel(relativePath, methodKey, methodName, "method", member, methodExported);
|
|
6389
|
+
pushNode(result, declByName, methodNode, methodKey);
|
|
6390
|
+
result.edges.push({
|
|
6391
|
+
source: classNode.id,
|
|
6392
|
+
target: methodNode.id,
|
|
6393
|
+
relation: "method_of",
|
|
6394
|
+
confidence: "EXTRACTED"
|
|
6395
|
+
});
|
|
6396
|
+
}
|
|
6397
|
+
}
|
|
6398
|
+
}
|
|
6399
|
+
return;
|
|
6400
|
+
}
|
|
6401
|
+
case "interface_declaration": {
|
|
6402
|
+
const name = textOfField(node, "name");
|
|
6403
|
+
if (name === null)
|
|
6404
|
+
return;
|
|
6405
|
+
const decl = makeNode(relativePath, name, "interface", node, exported);
|
|
6406
|
+
pushNode(result, declByName, decl);
|
|
6407
|
+
return;
|
|
6408
|
+
}
|
|
6409
|
+
case "type_alias_declaration": {
|
|
6410
|
+
const name = textOfField(node, "name");
|
|
6411
|
+
if (name === null)
|
|
6412
|
+
return;
|
|
6413
|
+
const decl = makeNode(relativePath, name, "type_alias", node, exported);
|
|
6414
|
+
pushNode(result, declByName, decl);
|
|
6415
|
+
return;
|
|
6416
|
+
}
|
|
6417
|
+
case "enum_declaration": {
|
|
6418
|
+
const name = textOfField(node, "name");
|
|
6419
|
+
if (name === null)
|
|
6420
|
+
return;
|
|
6421
|
+
const decl = makeNode(relativePath, name, "enum", node, exported);
|
|
6422
|
+
pushNode(result, declByName, decl);
|
|
6423
|
+
return;
|
|
6424
|
+
}
|
|
6425
|
+
case "lexical_declaration": {
|
|
6426
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6427
|
+
const declarator = node.namedChild(i);
|
|
6428
|
+
if (declarator === null || declarator.type !== "variable_declarator")
|
|
6429
|
+
continue;
|
|
6430
|
+
const ident = declarator.childForFieldName("name");
|
|
6431
|
+
if (ident === null || ident.type !== "identifier")
|
|
6432
|
+
continue;
|
|
6433
|
+
const decl = makeNode(relativePath, ident.text, "const", declarator, exported);
|
|
6434
|
+
pushNode(result, declByName, decl);
|
|
6435
|
+
}
|
|
6436
|
+
return;
|
|
6437
|
+
}
|
|
6438
|
+
}
|
|
6439
|
+
}
|
|
6440
|
+
function extractImports(node, relativePath, result, moduleNode) {
|
|
6441
|
+
if (node.type === "import_statement") {
|
|
6442
|
+
const src = firstNamedChildOfTypes(node, ["string"]);
|
|
6443
|
+
if (src !== null) {
|
|
6444
|
+
const frag = firstNamedChildOfTypes(src, ["string_fragment"]);
|
|
6445
|
+
const specifier = (frag !== null ? frag.text : src.text).replace(/^['"]|['"]$/g, "");
|
|
6446
|
+
if (specifier.length > 0) {
|
|
6447
|
+
result.edges.push({
|
|
6448
|
+
source: moduleNode.id,
|
|
6449
|
+
target: `external:${specifier}`,
|
|
6450
|
+
relation: "imports",
|
|
6451
|
+
confidence: "EXTRACTED"
|
|
6452
|
+
});
|
|
6453
|
+
}
|
|
6454
|
+
}
|
|
6455
|
+
return;
|
|
6456
|
+
}
|
|
6457
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6458
|
+
const child = node.namedChild(i);
|
|
6459
|
+
if (child !== null)
|
|
6460
|
+
extractImports(child, relativePath, result, moduleNode);
|
|
6461
|
+
}
|
|
6462
|
+
}
|
|
6463
|
+
function extractCalls(node, relativePath, result, declByName) {
|
|
6464
|
+
if (node.type === "call_expression") {
|
|
6465
|
+
const callee = node.childForFieldName("function");
|
|
6466
|
+
if (callee !== null) {
|
|
6467
|
+
const calleeKey = resolveCalleeKey(callee, declByName);
|
|
6468
|
+
if (calleeKey !== null) {
|
|
6469
|
+
const targetNode = declByName.get(calleeKey);
|
|
6470
|
+
if (targetNode !== void 0) {
|
|
6471
|
+
const callerNode = findEnclosingDeclaration(node, declByName);
|
|
6472
|
+
if (callerNode !== null) {
|
|
6473
|
+
result.edges.push({
|
|
6474
|
+
source: callerNode.id,
|
|
6475
|
+
target: targetNode.id,
|
|
6476
|
+
relation: "calls",
|
|
6477
|
+
confidence: "EXTRACTED"
|
|
6478
|
+
});
|
|
6479
|
+
}
|
|
6480
|
+
}
|
|
6481
|
+
}
|
|
6482
|
+
}
|
|
6483
|
+
}
|
|
6484
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6485
|
+
const child = node.namedChild(i);
|
|
6486
|
+
if (child !== null)
|
|
6487
|
+
extractCalls(child, relativePath, result, declByName);
|
|
6488
|
+
}
|
|
6489
|
+
}
|
|
6490
|
+
function resolveCalleeKey(callee, declByName) {
|
|
6491
|
+
if (callee.type === "identifier")
|
|
6492
|
+
return callee.text;
|
|
6493
|
+
if (callee.type === "member_expression") {
|
|
6494
|
+
const object = callee.childForFieldName("object");
|
|
6495
|
+
const property = callee.childForFieldName("property");
|
|
6496
|
+
if (object !== null && object.type === "this" && property !== null && property.type === "property_identifier") {
|
|
6497
|
+
const className = findEnclosingClassName(callee);
|
|
6498
|
+
if (className !== null)
|
|
6499
|
+
return `${className}.${property.text}`;
|
|
6500
|
+
}
|
|
6501
|
+
}
|
|
6502
|
+
return null;
|
|
6503
|
+
}
|
|
6504
|
+
function findEnclosingDeclaration(node, declByName) {
|
|
6505
|
+
let cur = node.parent;
|
|
6506
|
+
while (cur !== null) {
|
|
6507
|
+
if (cur.type === "function_declaration") {
|
|
6508
|
+
const name = textOfField(cur, "name");
|
|
6509
|
+
if (name !== null) {
|
|
6510
|
+
const n = declByName.get(name);
|
|
6511
|
+
if (n !== void 0)
|
|
6512
|
+
return n;
|
|
6513
|
+
}
|
|
6514
|
+
} else if (cur.type === "method_definition") {
|
|
6515
|
+
const methodName = textOfField(cur, "name");
|
|
6516
|
+
const className = findEnclosingClassName(cur);
|
|
6517
|
+
if (methodName !== null && className !== null) {
|
|
6518
|
+
const n = declByName.get(`${className}.${methodName}`);
|
|
6519
|
+
if (n !== void 0)
|
|
6520
|
+
return n;
|
|
6521
|
+
}
|
|
6522
|
+
} else if (cur.type === "variable_declarator") {
|
|
6523
|
+
const value = cur.childForFieldName("value");
|
|
6524
|
+
if (value?.type === "arrow_function" || value?.type === "function_expression") {
|
|
6525
|
+
const ident = cur.childForFieldName("name");
|
|
6526
|
+
if (ident !== null && ident.type === "identifier") {
|
|
6527
|
+
const n = declByName.get(ident.text);
|
|
6528
|
+
if (n !== void 0)
|
|
6529
|
+
return n;
|
|
6530
|
+
}
|
|
6531
|
+
}
|
|
6532
|
+
}
|
|
6533
|
+
cur = cur.parent;
|
|
6534
|
+
}
|
|
6535
|
+
return null;
|
|
6536
|
+
}
|
|
6537
|
+
function findEnclosingClassName(node) {
|
|
6538
|
+
let cur = node.parent;
|
|
6539
|
+
while (cur !== null) {
|
|
6540
|
+
if (cur.type === "class_declaration") {
|
|
6541
|
+
return textOfField(cur, "name");
|
|
6542
|
+
}
|
|
6543
|
+
cur = cur.parent;
|
|
6544
|
+
}
|
|
6545
|
+
return null;
|
|
6546
|
+
}
|
|
6547
|
+
function makeModuleNode(relativePath) {
|
|
6548
|
+
return {
|
|
6549
|
+
id: `${relativePath}::module`,
|
|
6550
|
+
label: relativePath,
|
|
6551
|
+
kind: "module",
|
|
6552
|
+
source_file: relativePath,
|
|
6553
|
+
source_location: "L1",
|
|
6554
|
+
language: "typescript",
|
|
6555
|
+
exported: false
|
|
6556
|
+
};
|
|
6557
|
+
}
|
|
6558
|
+
function makeNode(relativePath, name, kind, node, exported) {
|
|
6559
|
+
return {
|
|
6560
|
+
id: nodeId(relativePath, name, kind),
|
|
6561
|
+
label: name,
|
|
6562
|
+
kind,
|
|
6563
|
+
source_file: relativePath,
|
|
6564
|
+
source_location: locationStr(node),
|
|
6565
|
+
language: "typescript",
|
|
6566
|
+
exported
|
|
6567
|
+
};
|
|
6568
|
+
}
|
|
6569
|
+
function makeNodeWithExplicitLabel(relativePath, idName, label, kind, node, exported) {
|
|
6570
|
+
return {
|
|
6571
|
+
id: nodeId(relativePath, idName, kind),
|
|
6572
|
+
label,
|
|
6573
|
+
kind,
|
|
6574
|
+
source_file: relativePath,
|
|
6575
|
+
source_location: locationStr(node),
|
|
6576
|
+
language: "typescript",
|
|
6577
|
+
exported
|
|
6578
|
+
};
|
|
6579
|
+
}
|
|
6580
|
+
function pushNode(result, declByName, node, lookupKey) {
|
|
6581
|
+
if (result.nodes.some((n) => n.id === node.id)) {
|
|
6582
|
+
if (!declByName.has(lookupKey ?? node.label)) {
|
|
6583
|
+
declByName.set(lookupKey ?? node.label, node);
|
|
6584
|
+
}
|
|
6585
|
+
return;
|
|
6586
|
+
}
|
|
6587
|
+
result.nodes.push(node);
|
|
6588
|
+
declByName.set(lookupKey ?? node.label, node);
|
|
6589
|
+
}
|
|
6590
|
+
function nodeId(relativePath, name, kind) {
|
|
6591
|
+
return `${relativePath}:${name}:${kind}`;
|
|
6592
|
+
}
|
|
6593
|
+
function nodeIdUnresolved(relativePath, name, kind) {
|
|
6594
|
+
return `unresolved:${relativePath}:${name}:${kind}`;
|
|
6595
|
+
}
|
|
6596
|
+
function locationStr(node) {
|
|
6597
|
+
const start = node.startPosition.row + 1;
|
|
6598
|
+
const end = node.endPosition.row + 1;
|
|
6599
|
+
return start === end ? `L${start}` : `L${start}-${end}`;
|
|
6600
|
+
}
|
|
6601
|
+
function textOfField(node, fieldName) {
|
|
6602
|
+
const child = node.childForFieldName(fieldName);
|
|
6603
|
+
if (child === null)
|
|
6604
|
+
return null;
|
|
6605
|
+
const text = child.text;
|
|
6606
|
+
return text.length > 0 ? text : null;
|
|
6607
|
+
}
|
|
6608
|
+
function firstNamedChildOfTypes(node, types) {
|
|
6609
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6610
|
+
const child = node.namedChild(i);
|
|
6611
|
+
if (child !== null && types.includes(child.type))
|
|
6612
|
+
return child;
|
|
6613
|
+
}
|
|
6614
|
+
return null;
|
|
6615
|
+
}
|
|
6616
|
+
|
|
6617
|
+
// dist/src/graph/git-hook-install.js
|
|
6618
|
+
import { chmodSync as chmodSync2, existsSync as existsSync20, mkdirSync as mkdirSync12, readFileSync as readFileSync19, unlinkSync as unlinkSync8, writeFileSync as writeFileSync15 } from "node:fs";
|
|
6619
|
+
import { dirname as dirname8, join as join26, resolve as resolve3 } from "node:path";
|
|
6620
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
6621
|
+
var HOOK_BEGIN_MARKER = "# HIVEMIND_GRAPH_HOOK_BEGIN \u2014 managed by `hivemind graph init`";
|
|
6622
|
+
var HOOK_END_MARKER = "# HIVEMIND_GRAPH_HOOK_END";
|
|
6623
|
+
var SHEBANG = "#!/bin/sh";
|
|
6624
|
+
function hookBodyLines(hivemindPath) {
|
|
6625
|
+
return [
|
|
6626
|
+
"# Async-detached so commits never wait. Threshold-gate + cache make",
|
|
6627
|
+
"# typical re-runs ~85ms. Logs go to ~/.hivemind/post-commit.log",
|
|
6628
|
+
"# mkdir is robust against first-run: $HOME/.hivemind may not exist yet,",
|
|
6629
|
+
"# in which case the > redirect would fail and the build would never start.",
|
|
6630
|
+
'mkdir -p "$HOME/.hivemind" 2>/dev/null || true',
|
|
6631
|
+
`nohup ${quoteForShell(hivemindPath)} graph build --trigger post-commit >> "$HOME/.hivemind/post-commit.log" 2>&1 &`
|
|
6632
|
+
];
|
|
6633
|
+
}
|
|
6634
|
+
function quoteForShell(path) {
|
|
6635
|
+
return `'${path.replace(/'/g, `'\\''`)}'`;
|
|
6636
|
+
}
|
|
6637
|
+
function gitHooksDir(cwd) {
|
|
6638
|
+
const configured = tryGitConfig(cwd, "core.hooksPath");
|
|
6639
|
+
if (configured !== null) {
|
|
6640
|
+
const top = tryGitTopLevel(cwd);
|
|
6641
|
+
return top !== null ? resolve3(top, configured) : resolve3(cwd, configured);
|
|
6642
|
+
}
|
|
6643
|
+
try {
|
|
6644
|
+
const out = execFileSync5("git", ["rev-parse", "--git-path", "hooks"], {
|
|
6645
|
+
cwd,
|
|
6646
|
+
encoding: "utf8",
|
|
6647
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
6648
|
+
}).trim();
|
|
6649
|
+
if (out === "")
|
|
6650
|
+
return null;
|
|
6651
|
+
return resolve3(cwd, out);
|
|
6652
|
+
} catch {
|
|
6653
|
+
return null;
|
|
6654
|
+
}
|
|
6655
|
+
}
|
|
6656
|
+
function tryGitConfig(cwd, key) {
|
|
6657
|
+
try {
|
|
6658
|
+
const out = execFileSync5("git", ["config", "--get", key], {
|
|
6659
|
+
cwd,
|
|
6660
|
+
encoding: "utf8",
|
|
6661
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
6662
|
+
}).trim();
|
|
6663
|
+
return out === "" ? null : out;
|
|
6664
|
+
} catch {
|
|
6665
|
+
return null;
|
|
6666
|
+
}
|
|
6667
|
+
}
|
|
6668
|
+
function tryGitTopLevel(cwd) {
|
|
6669
|
+
try {
|
|
6670
|
+
const out = execFileSync5("git", ["rev-parse", "--show-toplevel"], {
|
|
6671
|
+
cwd,
|
|
6672
|
+
encoding: "utf8",
|
|
6673
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
6674
|
+
}).trim();
|
|
6675
|
+
return out === "" ? null : out;
|
|
6676
|
+
} catch {
|
|
6677
|
+
return null;
|
|
6678
|
+
}
|
|
6679
|
+
}
|
|
6680
|
+
function postCommitHookPath(cwd) {
|
|
6681
|
+
const hooksDir = gitHooksDir(cwd);
|
|
6682
|
+
return hooksDir === null ? null : join26(hooksDir, "post-commit");
|
|
6683
|
+
}
|
|
6684
|
+
function installPostCommitHook(cwd, opts = {}) {
|
|
6685
|
+
const path = postCommitHookPath(cwd);
|
|
6686
|
+
if (path === null) {
|
|
6687
|
+
return { kind: "foreign-hook", path: "", hint: "not in a git repo (no .git directory found)" };
|
|
6688
|
+
}
|
|
6689
|
+
const existed = existsSync20(path);
|
|
6690
|
+
if (existed) {
|
|
6691
|
+
const content = readFileSync19(path, "utf8");
|
|
6692
|
+
if (containsOurMarkers(content)) {
|
|
6693
|
+
return { kind: "already-ours", path };
|
|
6694
|
+
}
|
|
6695
|
+
if (!opts.force) {
|
|
6696
|
+
return {
|
|
6697
|
+
kind: "foreign-hook",
|
|
6698
|
+
path,
|
|
6699
|
+
hint: `existing hook at ${path} is not managed by hivemind; pass --force to overwrite, or merge our block manually (between '${HOOK_BEGIN_MARKER}' and '${HOOK_END_MARKER}')`
|
|
6700
|
+
};
|
|
6701
|
+
}
|
|
6702
|
+
}
|
|
6703
|
+
const hivemindPath = resolveHivemindPath();
|
|
6704
|
+
if (hivemindPath === null) {
|
|
6705
|
+
return {
|
|
6706
|
+
kind: "foreign-hook",
|
|
6707
|
+
path,
|
|
6708
|
+
hint: "hivemind binary not found on PATH. Install hivemind globally (`npm install -g @deeplake/hivemind`) before running `hivemind graph init`, so the hook can find a stable absolute path to call."
|
|
6709
|
+
};
|
|
6710
|
+
}
|
|
6711
|
+
mkdirSync12(dirname8(path), { recursive: true });
|
|
6712
|
+
writeFileSync15(path, buildHookFile(hivemindPath), { mode: 493 });
|
|
6713
|
+
try {
|
|
6714
|
+
chmodSync2(path, 493);
|
|
6715
|
+
} catch {
|
|
6716
|
+
}
|
|
6717
|
+
return { kind: "installed", path, wasNew: !existed };
|
|
6718
|
+
}
|
|
6719
|
+
function resolveHivemindPath() {
|
|
6720
|
+
try {
|
|
6721
|
+
const out = execFileSync5("which", ["hivemind"], {
|
|
6722
|
+
encoding: "utf8",
|
|
6723
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
6724
|
+
}).trim();
|
|
6725
|
+
if (out !== "" && out.includes("hivemind"))
|
|
6726
|
+
return out.split("\n")[0].trim();
|
|
6727
|
+
} catch {
|
|
6728
|
+
}
|
|
6729
|
+
return null;
|
|
6730
|
+
}
|
|
6731
|
+
function uninstallPostCommitHook(cwd) {
|
|
6732
|
+
const path = postCommitHookPath(cwd);
|
|
6733
|
+
if (path === null) {
|
|
6734
|
+
return { kind: "no-hook", path: "" };
|
|
6735
|
+
}
|
|
6736
|
+
if (!existsSync20(path)) {
|
|
6737
|
+
return { kind: "no-hook", path };
|
|
6738
|
+
}
|
|
6739
|
+
const content = readFileSync19(path, "utf8");
|
|
6740
|
+
if (!containsOurMarkers(content)) {
|
|
6741
|
+
return {
|
|
6742
|
+
kind: "not-ours",
|
|
6743
|
+
path,
|
|
6744
|
+
hint: `existing hook at ${path} is not managed by hivemind; remove it manually if you want it gone`
|
|
6745
|
+
};
|
|
6746
|
+
}
|
|
6747
|
+
const stripped = stripOurBlock(content);
|
|
6748
|
+
const meaningful = stripped.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith("#!"));
|
|
6749
|
+
if (meaningful.length === 0) {
|
|
6750
|
+
unlinkSync8(path);
|
|
6751
|
+
return { kind: "removed", path, wholeFileDeleted: true };
|
|
6752
|
+
}
|
|
6753
|
+
writeFileSync15(path, stripped);
|
|
6754
|
+
return { kind: "removed", path, wholeFileDeleted: false };
|
|
6755
|
+
}
|
|
6756
|
+
function containsOurMarkers(content) {
|
|
6757
|
+
return content.includes(HOOK_BEGIN_MARKER) && content.includes(HOOK_END_MARKER);
|
|
6758
|
+
}
|
|
6759
|
+
function stripOurBlock(content) {
|
|
6760
|
+
const beginIdx = content.indexOf(HOOK_BEGIN_MARKER);
|
|
6761
|
+
const endIdx = content.indexOf(HOOK_END_MARKER);
|
|
6762
|
+
if (beginIdx === -1 || endIdx === -1 || endIdx < beginIdx)
|
|
6763
|
+
return content;
|
|
6764
|
+
const blockEnd = endIdx + HOOK_END_MARKER.length;
|
|
6765
|
+
return content.slice(0, beginIdx) + content.slice(blockEnd);
|
|
6766
|
+
}
|
|
6767
|
+
function buildHookFile(hivemindPath) {
|
|
6768
|
+
return [
|
|
6769
|
+
SHEBANG,
|
|
6770
|
+
"",
|
|
6771
|
+
HOOK_BEGIN_MARKER,
|
|
6772
|
+
...hookBodyLines(hivemindPath),
|
|
6773
|
+
HOOK_END_MARKER,
|
|
6774
|
+
""
|
|
6775
|
+
].join("\n");
|
|
6776
|
+
}
|
|
6777
|
+
|
|
6778
|
+
// dist/src/commands/graph.js
|
|
6779
|
+
var USAGE = `hivemind graph \u2014 codebase-graph commands (Phase 1 \u2014 TypeScript only)
|
|
6780
|
+
|
|
6781
|
+
Usage:
|
|
6782
|
+
hivemind graph build [--cwd <path>]
|
|
6783
|
+
Walk the project for TypeScript source files, extract symbols + edges,
|
|
6784
|
+
and write a snapshot to ~/.hivemind/graphs/<repo-key>/snapshots/<commit-sha>.json.
|
|
6785
|
+
Also updates ~/.hivemind/graphs/<repo-key>/latest-commit.txt and the
|
|
6786
|
+
per-repo .last-build.json (consumed by the SessionEnd auto-build hook).
|
|
6787
|
+
|
|
6788
|
+
hivemind graph diff <sha1> <sha2> [--cwd <path>] [--json] [--limit N]
|
|
6789
|
+
Diff two snapshots by their git commit SHA. Prints added/removed
|
|
6790
|
+
counts for nodes and edges, plus up to N=10 (default) examples of each.
|
|
6791
|
+
--json: emit machine-readable JSON instead of the human format.
|
|
6792
|
+
--limit N: cap the per-category examples (human format only).
|
|
6793
|
+
|
|
6794
|
+
hivemind graph history [--cwd <path>] [-n N] [--json]
|
|
6795
|
+
Print the last N (default 20) entries from the per-repo history.jsonl,
|
|
6796
|
+
newest last. Each entry shows ts, commit_sha (short), snapshot_sha256
|
|
6797
|
+
(short), node/edge counts, and the trigger that fired the build.
|
|
6798
|
+
--json: emit raw JSONL (one parsed entry per line, full fields).
|
|
6799
|
+
|
|
6800
|
+
hivemind graph init [--cwd <path>] [--force] [--no-initial-build]
|
|
6801
|
+
Install a managed block in .git/hooks/post-commit that fires
|
|
6802
|
+
\`hivemind graph build --trigger post-commit\` after each commit
|
|
6803
|
+
(async, non-blocking, exit 0 always). Idempotent: re-running on
|
|
6804
|
+
an already-installed hook is a no-op. Refuses to clobber an
|
|
6805
|
+
existing non-managed hook unless --force is passed.
|
|
6806
|
+
Also runs an initial \`hivemind graph build\` unless
|
|
6807
|
+
--no-initial-build is passed.
|
|
6808
|
+
|
|
6809
|
+
hivemind graph uninstall [--cwd <path>]
|
|
6810
|
+
Remove our managed block from .git/hooks/post-commit. If our block
|
|
6811
|
+
was the only content, deletes the file; otherwise leaves the rest
|
|
6812
|
+
intact. Snapshots and history are NOT touched (\`rm -rf
|
|
6813
|
+
~/.hivemind/graphs/<key>\` if you really want them gone).
|
|
6814
|
+
|
|
6815
|
+
hivemind graph pull [--cwd <path>]
|
|
6816
|
+
Download the freshest cloud snapshot for HEAD into the local graph
|
|
6817
|
+
dir (any worktree of this user counts). No-op if local already
|
|
6818
|
+
matches cloud sha256 or local was built later than cloud. Requires
|
|
6819
|
+
\`hivemind login\`. Best-effort: any network/auth failure leaves
|
|
6820
|
+
the local files untouched. Disable via HIVEMIND_GRAPH_PULL=0.
|
|
6821
|
+
|
|
6822
|
+
hivemind graph --help
|
|
6823
|
+
Show this message.
|
|
6824
|
+
|
|
6825
|
+
Future subcommands (Phase 1.5+): daemon, search, latest, push, pull, prune.
|
|
6826
|
+
`;
|
|
6827
|
+
var DEFAULT_IGNORES = /* @__PURE__ */ new Set([
|
|
6828
|
+
"node_modules",
|
|
6829
|
+
".git",
|
|
6830
|
+
"bundle",
|
|
6831
|
+
"dist",
|
|
6832
|
+
"coverage",
|
|
6833
|
+
".cache",
|
|
6834
|
+
".nyc_output"
|
|
6835
|
+
]);
|
|
6836
|
+
function runGraphCommand(args) {
|
|
6837
|
+
const sub = args[0];
|
|
6838
|
+
if (sub === void 0 || sub === "--help" || sub === "-h" || sub === "help") {
|
|
6839
|
+
console.log(USAGE);
|
|
6840
|
+
return;
|
|
6841
|
+
}
|
|
6842
|
+
if (sub === "build") {
|
|
6843
|
+
return runBuildCommand(args.slice(1));
|
|
6844
|
+
}
|
|
6845
|
+
if (sub === "diff") {
|
|
6846
|
+
runDiffCommand(args.slice(1));
|
|
6847
|
+
return;
|
|
6848
|
+
}
|
|
6849
|
+
if (sub === "history") {
|
|
6850
|
+
runHistoryCommand(args.slice(1));
|
|
6851
|
+
return;
|
|
6852
|
+
}
|
|
6853
|
+
if (sub === "init") {
|
|
6854
|
+
return runInitCommand(args.slice(1));
|
|
6855
|
+
}
|
|
6856
|
+
if (sub === "uninstall") {
|
|
6857
|
+
runUninstallCommand(args.slice(1));
|
|
6858
|
+
return;
|
|
6859
|
+
}
|
|
6860
|
+
if (sub === "pull") {
|
|
6861
|
+
return runPullCommand(args.slice(1));
|
|
6862
|
+
}
|
|
6863
|
+
console.error(`hivemind graph: unknown subcommand '${sub}'`);
|
|
6864
|
+
console.error(USAGE);
|
|
6865
|
+
process.exit(2);
|
|
6866
|
+
}
|
|
6867
|
+
function parseInitArgs(args) {
|
|
6868
|
+
let cwd = process.cwd();
|
|
6869
|
+
let force = false;
|
|
6870
|
+
let initialBuild = true;
|
|
6871
|
+
for (let i = 0; i < args.length; i++) {
|
|
6872
|
+
const a = args[i];
|
|
6873
|
+
if (a === "--cwd" && i + 1 < args.length) {
|
|
6874
|
+
cwd = args[i + 1];
|
|
6875
|
+
i += 1;
|
|
6876
|
+
} else if (a === "--force") {
|
|
6877
|
+
force = true;
|
|
6878
|
+
} else if (a === "--no-initial-build") {
|
|
6879
|
+
initialBuild = false;
|
|
6880
|
+
} else if (a === "--help" || a === "-h") {
|
|
6881
|
+
console.log(USAGE);
|
|
6882
|
+
process.exit(0);
|
|
6883
|
+
} else {
|
|
6884
|
+
console.error(`hivemind graph init: unknown argument '${a}'`);
|
|
6885
|
+
console.error(USAGE);
|
|
6886
|
+
process.exit(2);
|
|
6887
|
+
}
|
|
6888
|
+
}
|
|
6889
|
+
return { cwd, force, initialBuild };
|
|
6890
|
+
}
|
|
6891
|
+
async function runInitCommand(args) {
|
|
6892
|
+
const opts = parseInitArgs(args);
|
|
6893
|
+
const status = installPostCommitHook(opts.cwd, { force: opts.force });
|
|
6894
|
+
switch (status.kind) {
|
|
6895
|
+
case "installed":
|
|
6896
|
+
console.log(`Installed post-commit hook at ${status.path}`);
|
|
6897
|
+
break;
|
|
6898
|
+
case "already-ours":
|
|
6899
|
+
console.log(`Post-commit hook already managed by hivemind (no change): ${status.path}`);
|
|
6900
|
+
break;
|
|
6901
|
+
case "foreign-hook":
|
|
6902
|
+
console.error(`hivemind graph init: ${status.hint}`);
|
|
6903
|
+
process.exit(1);
|
|
6904
|
+
}
|
|
6905
|
+
if (opts.initialBuild) {
|
|
6906
|
+
console.log("");
|
|
6907
|
+
console.log("Running initial build...");
|
|
6908
|
+
await runBuildCommand(["--cwd", opts.cwd, "--trigger", "manual"]);
|
|
6909
|
+
} else {
|
|
6910
|
+
console.log("");
|
|
6911
|
+
console.log("Skipped initial build (--no-initial-build). Run `hivemind graph build` when ready.");
|
|
6912
|
+
}
|
|
6913
|
+
}
|
|
6914
|
+
function parseUninstallArgs(args) {
|
|
6915
|
+
let cwd = process.cwd();
|
|
6916
|
+
for (let i = 0; i < args.length; i++) {
|
|
6917
|
+
const a = args[i];
|
|
6918
|
+
if (a === "--cwd" && i + 1 < args.length) {
|
|
6919
|
+
cwd = args[i + 1];
|
|
6920
|
+
i += 1;
|
|
6921
|
+
} else if (a === "--help" || a === "-h") {
|
|
6922
|
+
console.log(USAGE);
|
|
6923
|
+
process.exit(0);
|
|
6924
|
+
} else {
|
|
6925
|
+
console.error(`hivemind graph uninstall: unknown argument '${a}'`);
|
|
6926
|
+
console.error(USAGE);
|
|
6927
|
+
process.exit(2);
|
|
6928
|
+
}
|
|
6929
|
+
}
|
|
6930
|
+
return { cwd };
|
|
6931
|
+
}
|
|
6932
|
+
function runUninstallCommand(args) {
|
|
6933
|
+
const opts = parseUninstallArgs(args);
|
|
6934
|
+
const status = uninstallPostCommitHook(opts.cwd);
|
|
6935
|
+
switch (status.kind) {
|
|
6936
|
+
case "removed":
|
|
6937
|
+
if (status.wholeFileDeleted) {
|
|
6938
|
+
console.log(`Removed post-commit hook (file deleted): ${status.path}`);
|
|
6939
|
+
} else {
|
|
6940
|
+
console.log(`Removed managed block from post-commit hook (other content preserved): ${status.path}`);
|
|
6941
|
+
}
|
|
6942
|
+
console.log("Local snapshots + history.jsonl are untouched.");
|
|
6943
|
+
break;
|
|
6944
|
+
case "no-hook":
|
|
6945
|
+
console.log(status.path === "" ? "No git repo here (nothing to uninstall)." : `No post-commit hook at ${status.path} (nothing to uninstall).`);
|
|
6946
|
+
break;
|
|
6947
|
+
case "not-ours":
|
|
6948
|
+
console.error(`hivemind graph uninstall: ${status.hint}`);
|
|
6949
|
+
process.exit(1);
|
|
6950
|
+
}
|
|
6951
|
+
}
|
|
6952
|
+
function parseHistoryArgs(args) {
|
|
6953
|
+
let cwd = process.cwd();
|
|
6954
|
+
let n = 20;
|
|
6955
|
+
let json2 = false;
|
|
6956
|
+
for (let i = 0; i < args.length; i++) {
|
|
6957
|
+
const a = args[i];
|
|
6958
|
+
if (a === "--cwd" && i + 1 < args.length) {
|
|
6959
|
+
cwd = args[i + 1];
|
|
6960
|
+
i += 1;
|
|
6961
|
+
} else if (a === "-n" && i + 1 < args.length) {
|
|
6962
|
+
const raw = args[i + 1];
|
|
6963
|
+
if (!/^\d+$/.test(raw)) {
|
|
6964
|
+
console.error("hivemind graph history: -n must be a non-negative integer");
|
|
6965
|
+
process.exit(2);
|
|
6966
|
+
}
|
|
6967
|
+
n = Number(raw);
|
|
6968
|
+
i += 1;
|
|
6969
|
+
} else if (a === "--json") {
|
|
6970
|
+
json2 = true;
|
|
6971
|
+
} else if (a === "--help" || a === "-h") {
|
|
6972
|
+
console.log(USAGE);
|
|
6973
|
+
process.exit(0);
|
|
6974
|
+
} else {
|
|
6975
|
+
console.error(`hivemind graph history: unknown argument '${a}'`);
|
|
6976
|
+
console.error(USAGE);
|
|
6977
|
+
process.exit(2);
|
|
6978
|
+
}
|
|
6979
|
+
}
|
|
6980
|
+
return { cwd, n, json: json2 };
|
|
6981
|
+
}
|
|
6982
|
+
function runHistoryCommand(args) {
|
|
6983
|
+
const opts = parseHistoryArgs(args);
|
|
6984
|
+
const { key: repoKey } = deriveProjectKey(opts.cwd);
|
|
6985
|
+
const baseDir = repoDir(repoKey);
|
|
6986
|
+
const total = countHistoryEntries(baseDir);
|
|
6987
|
+
const entries = readHistoryTail(baseDir, opts.n);
|
|
6988
|
+
if (opts.json) {
|
|
6989
|
+
for (const e of entries)
|
|
6990
|
+
console.log(JSON.stringify(e));
|
|
6991
|
+
return;
|
|
6992
|
+
}
|
|
6993
|
+
if (total === 0) {
|
|
6994
|
+
console.log("No history yet. Run `hivemind graph build` to record one.");
|
|
6995
|
+
return;
|
|
6996
|
+
}
|
|
6997
|
+
console.log(`history.jsonl: ${total} total entries; showing last ${entries.length}`);
|
|
6998
|
+
console.log("");
|
|
6999
|
+
for (const e of entries) {
|
|
7000
|
+
const commit = e.commit_sha === null ? "(no-git)" : e.commit_sha.slice(0, 7);
|
|
7001
|
+
const snap = e.snapshot_sha256.slice(0, 7);
|
|
7002
|
+
console.log(` ${e.ts} commit=${commit} snap=${snap} nodes=${e.node_count} edges=${e.edge_count} trigger=${e.trigger}`);
|
|
7003
|
+
}
|
|
7004
|
+
}
|
|
7005
|
+
function parseDiffArgs(args) {
|
|
7006
|
+
let cwd = process.cwd();
|
|
7007
|
+
let json2 = false;
|
|
7008
|
+
let limit = 10;
|
|
7009
|
+
const positional = [];
|
|
7010
|
+
for (let i = 0; i < args.length; i++) {
|
|
7011
|
+
const a = args[i];
|
|
7012
|
+
if (a === "--cwd" && i + 1 < args.length) {
|
|
7013
|
+
cwd = args[i + 1];
|
|
7014
|
+
i += 1;
|
|
7015
|
+
} else if (a === "--json") {
|
|
7016
|
+
json2 = true;
|
|
7017
|
+
} else if (a === "--limit" && i + 1 < args.length) {
|
|
7018
|
+
const raw = args[i + 1];
|
|
7019
|
+
if (!/^\d+$/.test(raw)) {
|
|
7020
|
+
console.error("hivemind graph diff: --limit must be a non-negative integer");
|
|
7021
|
+
process.exit(2);
|
|
7022
|
+
}
|
|
7023
|
+
limit = Number(raw);
|
|
7024
|
+
i += 1;
|
|
7025
|
+
} else if (a === "--help" || a === "-h") {
|
|
7026
|
+
console.log(USAGE);
|
|
7027
|
+
process.exit(0);
|
|
7028
|
+
} else if (a !== void 0 && !a.startsWith("--")) {
|
|
7029
|
+
positional.push(a);
|
|
7030
|
+
} else {
|
|
7031
|
+
console.error(`hivemind graph diff: unknown argument '${a}'`);
|
|
7032
|
+
console.error(USAGE);
|
|
7033
|
+
process.exit(2);
|
|
7034
|
+
}
|
|
7035
|
+
}
|
|
7036
|
+
if (positional.length !== 2) {
|
|
7037
|
+
console.error("hivemind graph diff: expected exactly two commit SHAs");
|
|
7038
|
+
console.error(USAGE);
|
|
7039
|
+
process.exit(2);
|
|
7040
|
+
}
|
|
7041
|
+
return { cwd, sha1: positional[0], sha2: positional[1], json: json2, limit };
|
|
7042
|
+
}
|
|
7043
|
+
function runDiffCommand(args) {
|
|
7044
|
+
const opts = parseDiffArgs(args);
|
|
7045
|
+
const { key: repoKey } = deriveProjectKey(opts.cwd);
|
|
7046
|
+
const baseDir = repoDir(repoKey);
|
|
7047
|
+
const from = loadSnapshotByCommit(baseDir, opts.sha1);
|
|
7048
|
+
if (from === null) {
|
|
7049
|
+
console.error(`hivemind graph diff: snapshot not found for ${opts.sha1}`);
|
|
7050
|
+
console.error(` expected: ${baseDir}/snapshots/${opts.sha1}.json`);
|
|
7051
|
+
console.error(" hint: run 'hivemind graph build' on the relevant commit, or check the commit sha");
|
|
7052
|
+
process.exit(1);
|
|
7053
|
+
}
|
|
7054
|
+
const to = loadSnapshotByCommit(baseDir, opts.sha2);
|
|
7055
|
+
if (to === null) {
|
|
7056
|
+
console.error(`hivemind graph diff: snapshot not found for ${opts.sha2}`);
|
|
7057
|
+
console.error(` expected: ${baseDir}/snapshots/${opts.sha2}.json`);
|
|
7058
|
+
process.exit(1);
|
|
7059
|
+
}
|
|
7060
|
+
const diff = diffSnapshots(from, to);
|
|
7061
|
+
if (opts.json) {
|
|
7062
|
+
console.log(JSON.stringify(diff, null, 2));
|
|
7063
|
+
return;
|
|
7064
|
+
}
|
|
7065
|
+
console.log(`Diff: ${opts.sha1} \u2192 ${opts.sha2}`);
|
|
7066
|
+
console.log("");
|
|
7067
|
+
printDiffHuman(diff, opts.limit);
|
|
7068
|
+
}
|
|
7069
|
+
function parseBuildArgs(args) {
|
|
7070
|
+
let cwd = process.cwd();
|
|
7071
|
+
let trigger = "manual";
|
|
7072
|
+
for (let i = 0; i < args.length; i++) {
|
|
7073
|
+
const a = args[i];
|
|
7074
|
+
if (a === "--cwd" && i + 1 < args.length) {
|
|
7075
|
+
cwd = args[i + 1];
|
|
7076
|
+
i += 1;
|
|
7077
|
+
} else if (a === "--trigger" && i + 1 < args.length) {
|
|
7078
|
+
const v = args[i + 1];
|
|
7079
|
+
if (v === "manual" || v === "session-end" || v === "post-commit" || v === "unknown") {
|
|
7080
|
+
trigger = v;
|
|
7081
|
+
} else {
|
|
7082
|
+
console.error(`hivemind graph build: --trigger must be one of manual|session-end|post-commit|unknown (got '${v}')`);
|
|
7083
|
+
process.exit(2);
|
|
7084
|
+
}
|
|
7085
|
+
i += 1;
|
|
7086
|
+
} else if (a === "--help" || a === "-h") {
|
|
7087
|
+
console.log(USAGE);
|
|
7088
|
+
process.exit(0);
|
|
7089
|
+
} else {
|
|
7090
|
+
console.error(`hivemind graph build: unknown argument '${a}'`);
|
|
7091
|
+
console.error(USAGE);
|
|
7092
|
+
process.exit(2);
|
|
7093
|
+
}
|
|
7094
|
+
}
|
|
7095
|
+
return { cwd, trigger };
|
|
7096
|
+
}
|
|
7097
|
+
async function runBuildCommand(args) {
|
|
7098
|
+
const opts = parseBuildArgs(args);
|
|
7099
|
+
const cwd = resolve4(opts.cwd);
|
|
7100
|
+
const { key: repoKey, project } = deriveProjectKey(cwd);
|
|
7101
|
+
const baseDir = repoDir(repoKey);
|
|
7102
|
+
const commitSha = readGitCommit(cwd);
|
|
7103
|
+
const branch = readGitBranch(cwd);
|
|
7104
|
+
const version = getVersion();
|
|
7105
|
+
console.log(`Building codebase graph for ${project}`);
|
|
7106
|
+
console.log(` repo_key: ${repoKey}`);
|
|
7107
|
+
console.log(` commit_sha: ${commitSha ?? "(not in a git repo)"}`);
|
|
7108
|
+
console.log(` branch: ${branch ?? "(none / detached)"}`);
|
|
7109
|
+
console.log(` output: ${baseDir}`);
|
|
7110
|
+
console.log("");
|
|
7111
|
+
const sourceFiles = discoverSourceFiles(cwd);
|
|
7112
|
+
console.log(`Discovered ${sourceFiles.length} TypeScript source files. Extracting...`);
|
|
7113
|
+
const extractions = [];
|
|
7114
|
+
let skipped = 0;
|
|
7115
|
+
let totalParseErrors = 0;
|
|
7116
|
+
let cacheHits = 0;
|
|
7117
|
+
for (const abs of sourceFiles) {
|
|
7118
|
+
const rel = toForwardSlash(relative(cwd, abs));
|
|
7119
|
+
try {
|
|
7120
|
+
const content = readFileSync20(abs, "utf8");
|
|
7121
|
+
const contentSha = fileContentHash(content);
|
|
7122
|
+
let extraction = readCache(baseDir, contentSha, rel);
|
|
7123
|
+
if (extraction === null) {
|
|
7124
|
+
extraction = extractTypeScript(content, rel);
|
|
7125
|
+
writeCache(baseDir, contentSha, extraction);
|
|
7126
|
+
} else {
|
|
7127
|
+
cacheHits += 1;
|
|
7128
|
+
}
|
|
7129
|
+
if (extraction.parse_errors.length > 0) {
|
|
7130
|
+
totalParseErrors += extraction.parse_errors.length;
|
|
7131
|
+
for (const err of extraction.parse_errors) {
|
|
7132
|
+
console.warn(` warn: parse issue in ${err.source_file} ${err.location ?? ""}: ${err.message}`);
|
|
7133
|
+
}
|
|
7134
|
+
}
|
|
7135
|
+
extractions.push(extraction);
|
|
7136
|
+
} catch (err) {
|
|
7137
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7138
|
+
console.warn(` warn: skipping ${rel}: ${msg}`);
|
|
7139
|
+
skipped += 1;
|
|
7140
|
+
}
|
|
7141
|
+
}
|
|
7142
|
+
const metadata = {
|
|
7143
|
+
schema_version: 1,
|
|
7144
|
+
generator: "hivemind-graph",
|
|
7145
|
+
commit_sha: commitSha,
|
|
7146
|
+
repo_key: repoKey
|
|
7147
|
+
};
|
|
7148
|
+
const observation = {
|
|
7149
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7150
|
+
branch,
|
|
7151
|
+
worktree_path: cwd,
|
|
7152
|
+
repo_project: project,
|
|
7153
|
+
generator_version: version,
|
|
7154
|
+
source_files_extracted: extractions.length,
|
|
7155
|
+
source_files_skipped: skipped
|
|
7156
|
+
};
|
|
7157
|
+
const snapshot = buildSnapshot(extractions, metadata, observation);
|
|
7158
|
+
const worktreeId = workTreeIdFor2(cwd);
|
|
7159
|
+
const result = writeSnapshot(snapshot, baseDir, opts.trigger, worktreeId);
|
|
7160
|
+
console.log("");
|
|
7161
|
+
console.log(`Snapshot: ${result.snapshotPath}`);
|
|
7162
|
+
console.log(`Latest: ${result.latestCommitPath ?? "(no commit context \u2014 latest-commit.txt not updated)"}`);
|
|
7163
|
+
console.log(`SHA-256: ${result.snapshotSha256}`);
|
|
7164
|
+
console.log(`Nodes: ${snapshot.nodes.length}`);
|
|
7165
|
+
console.log(`Edges: ${snapshot.links.length}`);
|
|
7166
|
+
console.log(`Files extracted: ${extractions.length} (skipped: ${skipped}, parse warnings: ${totalParseErrors}, cache hits: ${cacheHits}/${sourceFiles.length})`);
|
|
7167
|
+
const pushOutcome = await pushSnapshot(snapshot, worktreeId);
|
|
7168
|
+
switch (pushOutcome.kind) {
|
|
7169
|
+
case "inserted":
|
|
7170
|
+
console.log(`Cloud: pushed to codebase table (commit ${pushOutcome.commitSha.slice(0, 7)})`);
|
|
7171
|
+
break;
|
|
7172
|
+
case "inserted-with-duplicate-race":
|
|
7173
|
+
console.warn(`Cloud: pushed (commit ${pushOutcome.commitSha.slice(0, 7)}) but ${pushOutcome.rowCount} rows now share`);
|
|
7174
|
+
console.warn(` this identity key \u2014 a concurrent writer raced. v1.1 adds a server-side`);
|
|
7175
|
+
console.warn(` UNIQUE constraint; until then, the older row(s) should be deleted manually.`);
|
|
7176
|
+
break;
|
|
7177
|
+
case "already-current":
|
|
7178
|
+
console.log(`Cloud: already up-to-date (commit ${pushOutcome.commitSha.slice(0, 7)})`);
|
|
7179
|
+
break;
|
|
7180
|
+
case "skipped-no-auth":
|
|
7181
|
+
console.log(`Cloud: skipped (not authenticated; run \`hivemind login\` to enable cloud sync)`);
|
|
7182
|
+
break;
|
|
7183
|
+
case "skipped-no-commit":
|
|
7184
|
+
console.log(`Cloud: skipped (no commit context \u2014 not in a git repo)`);
|
|
7185
|
+
break;
|
|
7186
|
+
case "skipped-disabled":
|
|
7187
|
+
console.log(`Cloud: skipped (HIVEMIND_GRAPH_PUSH=0)`);
|
|
7188
|
+
break;
|
|
7189
|
+
case "drift":
|
|
7190
|
+
console.warn(`Cloud: DRIFT \u2014 commit ${pushOutcome.commitSha.slice(0, 7)} is in cloud with`);
|
|
7191
|
+
console.warn(` sha256=${pushOutcome.cloudSha256.slice(0, 12)}... but local rebuild produced`);
|
|
7192
|
+
console.warn(` sha256=${pushOutcome.localSha256.slice(0, 12)}...`);
|
|
7193
|
+
console.warn(` (probably extractor version drift; investigate before forcing.)`);
|
|
7194
|
+
break;
|
|
7195
|
+
case "error":
|
|
7196
|
+
console.warn(`Cloud: push error (non-fatal): ${pushOutcome.message}`);
|
|
7197
|
+
break;
|
|
7198
|
+
}
|
|
7199
|
+
}
|
|
7200
|
+
function parsePullArgs(args) {
|
|
7201
|
+
let cwd = process.cwd();
|
|
7202
|
+
for (let i = 0; i < args.length; i++) {
|
|
7203
|
+
const a = args[i];
|
|
7204
|
+
if (a === "--cwd" && i + 1 < args.length) {
|
|
7205
|
+
cwd = args[i + 1];
|
|
7206
|
+
i += 1;
|
|
7207
|
+
} else if (a === "--help" || a === "-h") {
|
|
7208
|
+
console.log(USAGE);
|
|
7209
|
+
process.exit(0);
|
|
7210
|
+
} else {
|
|
7211
|
+
console.error(`hivemind graph pull: unknown argument '${a}'`);
|
|
7212
|
+
console.error(USAGE);
|
|
7213
|
+
process.exit(2);
|
|
7214
|
+
}
|
|
7215
|
+
}
|
|
7216
|
+
return { cwd };
|
|
7217
|
+
}
|
|
7218
|
+
async function runPullCommand(args) {
|
|
7219
|
+
const opts = parsePullArgs(args);
|
|
7220
|
+
const outcome = await pullSnapshot(opts.cwd);
|
|
7221
|
+
switch (outcome.kind) {
|
|
7222
|
+
case "pulled":
|
|
7223
|
+
console.log(`Pulled commit ${outcome.commitSha.slice(0, 7)}`);
|
|
7224
|
+
console.log(` sha256: ${outcome.snapshotSha256.slice(0, 12)}...`);
|
|
7225
|
+
console.log(` bytes: ${outcome.bytes}`);
|
|
7226
|
+
console.log(` origin: worktree_id=${outcome.sourceWorktreePath}`);
|
|
7227
|
+
console.log(` cloud ts: ${new Date(outcome.cloudTs).toISOString()}`);
|
|
7228
|
+
break;
|
|
7229
|
+
case "up-to-date":
|
|
7230
|
+
console.log(`Already up-to-date (commit ${outcome.commitSha.slice(0, 7)}, sha256 ${outcome.snapshotSha256.slice(0, 12)}...)`);
|
|
7231
|
+
break;
|
|
7232
|
+
case "local-newer":
|
|
7233
|
+
console.log(`Local is newer than cloud \u2014 not pulling.`);
|
|
7234
|
+
console.log(` commit: ${outcome.commitSha.slice(0, 7)}`);
|
|
7235
|
+
console.log(` local ts: ${new Date(outcome.localTs).toISOString()}`);
|
|
7236
|
+
console.log(` cloud ts: ${new Date(outcome.cloudTs).toISOString()}`);
|
|
7237
|
+
break;
|
|
7238
|
+
case "no-cloud-row":
|
|
7239
|
+
console.log(`No cloud snapshot for commit ${outcome.commitSha.slice(0, 7)} \u2014 run \`hivemind graph build\` to create one.`);
|
|
7240
|
+
break;
|
|
7241
|
+
case "skipped-no-auth":
|
|
7242
|
+
console.log(`Skipped: not authenticated (run \`hivemind login\`).`);
|
|
7243
|
+
break;
|
|
7244
|
+
case "skipped-disabled":
|
|
7245
|
+
console.log(`Skipped: HIVEMIND_GRAPH_PULL=0.`);
|
|
7246
|
+
break;
|
|
7247
|
+
case "skipped-no-head":
|
|
7248
|
+
console.log(`Skipped: not in a git repo (\`git rev-parse HEAD\` failed).`);
|
|
7249
|
+
break;
|
|
7250
|
+
case "error":
|
|
7251
|
+
console.warn(`Pull error (non-fatal): ${outcome.message}`);
|
|
7252
|
+
process.exitCode = 1;
|
|
7253
|
+
break;
|
|
7254
|
+
}
|
|
7255
|
+
}
|
|
7256
|
+
function workTreeIdFor2(cwd) {
|
|
7257
|
+
return createHash6("sha256").update(cwd).digest("hex").slice(0, 16);
|
|
7258
|
+
}
|
|
7259
|
+
function discoverSourceFiles(rootDir) {
|
|
7260
|
+
const out = [];
|
|
7261
|
+
walk(rootDir, out);
|
|
7262
|
+
out.sort();
|
|
7263
|
+
return out;
|
|
7264
|
+
}
|
|
7265
|
+
function walk(dir, out) {
|
|
7266
|
+
let entries;
|
|
7267
|
+
try {
|
|
7268
|
+
entries = readdirSync2(dir, { withFileTypes: true });
|
|
7269
|
+
} catch {
|
|
7270
|
+
return;
|
|
7271
|
+
}
|
|
7272
|
+
for (const entry of entries) {
|
|
7273
|
+
if (DEFAULT_IGNORES.has(entry.name))
|
|
7274
|
+
continue;
|
|
7275
|
+
if (entry.name.startsWith("."))
|
|
7276
|
+
continue;
|
|
7277
|
+
const abs = join27(dir, entry.name);
|
|
7278
|
+
if (entry.isDirectory()) {
|
|
7279
|
+
walk(abs, out);
|
|
7280
|
+
} else if (entry.isFile() && isSourceFile(entry.name)) {
|
|
7281
|
+
out.push(abs);
|
|
7282
|
+
}
|
|
7283
|
+
}
|
|
7284
|
+
}
|
|
7285
|
+
function isSourceFile(name) {
|
|
7286
|
+
if (name.endsWith(".d.ts"))
|
|
7287
|
+
return false;
|
|
7288
|
+
return name.endsWith(".ts") || name.endsWith(".tsx");
|
|
7289
|
+
}
|
|
7290
|
+
function toForwardSlash(p) {
|
|
7291
|
+
return sep === "\\" ? p.replace(/\\/g, "/") : p;
|
|
7292
|
+
}
|
|
7293
|
+
function readGitCommit(cwd) {
|
|
7294
|
+
try {
|
|
7295
|
+
return execSync3("git rev-parse HEAD", {
|
|
7296
|
+
cwd,
|
|
7297
|
+
encoding: "utf8",
|
|
7298
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
7299
|
+
}).trim();
|
|
7300
|
+
} catch {
|
|
7301
|
+
return null;
|
|
7302
|
+
}
|
|
7303
|
+
}
|
|
7304
|
+
function readGitBranch(cwd) {
|
|
7305
|
+
try {
|
|
7306
|
+
const out = execSync3("git rev-parse --abbrev-ref HEAD", {
|
|
7307
|
+
cwd,
|
|
7308
|
+
encoding: "utf8",
|
|
7309
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
7310
|
+
}).trim();
|
|
7311
|
+
return out === "" || out === "HEAD" ? null : out;
|
|
7312
|
+
} catch {
|
|
7313
|
+
return null;
|
|
7314
|
+
}
|
|
7315
|
+
}
|
|
7316
|
+
|
|
7317
|
+
// dist/src/commands/dashboard.js
|
|
7318
|
+
import { mkdirSync as mkdirSync16, writeFileSync as writeFileSync18 } from "node:fs";
|
|
7319
|
+
import { homedir as homedir15 } from "node:os";
|
|
7320
|
+
import { dirname as dirname12, join as join35, resolve as resolve5 } from "node:path";
|
|
7321
|
+
|
|
7322
|
+
// dist/src/dashboard/data.js
|
|
7323
|
+
import { existsSync as existsSync25, readFileSync as readFileSync24, readdirSync as readdirSync4, statSync as statSync3 } from "node:fs";
|
|
7324
|
+
import { homedir as homedir14 } from "node:os";
|
|
7325
|
+
import { join as join33 } from "node:path";
|
|
7326
|
+
|
|
7327
|
+
// dist/src/notifications/sources/org-stats.js
|
|
7328
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync13, readFileSync as readFileSync21, writeFileSync as writeFileSync16 } from "node:fs";
|
|
7329
|
+
import { homedir as homedir11 } from "node:os";
|
|
7330
|
+
import { dirname as dirname9, join as join28 } from "node:path";
|
|
7331
|
+
var log5 = (msg) => log2("notifications-org-stats", msg);
|
|
7332
|
+
var FETCH_TIMEOUT_MS = 1500;
|
|
7333
|
+
var DEFAULT_API_URL3 = "https://api.deeplake.ai";
|
|
7334
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
7335
|
+
function cacheFilePath() {
|
|
7336
|
+
return join28(homedir11(), ".deeplake", "hivemind-stats-cache.json");
|
|
7337
|
+
}
|
|
7338
|
+
function cacheScopeKey(creds) {
|
|
7339
|
+
return JSON.stringify({
|
|
7340
|
+
apiUrl: creds.apiUrl ?? DEFAULT_API_URL3,
|
|
7341
|
+
orgId: creds.orgId ?? "",
|
|
7342
|
+
userName: creds.userName ?? ""
|
|
7343
|
+
});
|
|
7344
|
+
}
|
|
7345
|
+
function scopeFromServer(s) {
|
|
7346
|
+
const n = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
7347
|
+
return {
|
|
7348
|
+
sessionsCount: n(s?.sessions_count),
|
|
7349
|
+
memoryRecallCount: n(s?.memory_recall_count),
|
|
7350
|
+
memorySearchBytes: n(s?.memory_search_bytes)
|
|
7351
|
+
};
|
|
7352
|
+
}
|
|
7353
|
+
function readCache2(scopeKey) {
|
|
7354
|
+
if (!existsSync21(cacheFilePath()))
|
|
7355
|
+
return {};
|
|
7356
|
+
try {
|
|
7357
|
+
const parsed = JSON.parse(readFileSync21(cacheFilePath(), "utf-8"));
|
|
7358
|
+
if (!parsed || typeof parsed !== "object")
|
|
7359
|
+
return {};
|
|
7360
|
+
if (parsed.scopeKey !== scopeKey)
|
|
7361
|
+
return {};
|
|
7362
|
+
if (typeof parsed.fetchedAt !== "number")
|
|
7363
|
+
return {};
|
|
7364
|
+
const age = Date.now() - parsed.fetchedAt;
|
|
7365
|
+
const data = parsed.data;
|
|
7366
|
+
if (!data || typeof data !== "object" || !data.org || !data.user)
|
|
7367
|
+
return {};
|
|
7368
|
+
if (age >= 0 && age < CACHE_TTL_MS)
|
|
7369
|
+
return { fresh: data };
|
|
7370
|
+
return { stale: data };
|
|
7371
|
+
} catch (e) {
|
|
7372
|
+
log5(`cache read failed: ${e?.message ?? String(e)}`);
|
|
7373
|
+
return {};
|
|
7374
|
+
}
|
|
7375
|
+
}
|
|
7376
|
+
function writeCache2(scopeKey, data) {
|
|
7377
|
+
try {
|
|
7378
|
+
mkdirSync13(dirname9(cacheFilePath()), { recursive: true });
|
|
7379
|
+
const body = { fetchedAt: Date.now(), scopeKey, data };
|
|
7380
|
+
writeFileSync16(cacheFilePath(), JSON.stringify(body), "utf-8");
|
|
7381
|
+
} catch (e) {
|
|
7382
|
+
log5(`cache write failed: ${e?.message ?? String(e)}`);
|
|
7383
|
+
}
|
|
7384
|
+
}
|
|
7385
|
+
async function fetchOrgStats(creds) {
|
|
7386
|
+
if (!creds?.token)
|
|
7387
|
+
return null;
|
|
7388
|
+
const apiUrl = creds.apiUrl ?? DEFAULT_API_URL3;
|
|
7389
|
+
const scopeKey = cacheScopeKey(creds);
|
|
7390
|
+
const { fresh, stale } = readCache2(scopeKey);
|
|
7391
|
+
if (fresh) {
|
|
7392
|
+
log5("cache hit \u2014 returning fresh org stats");
|
|
7393
|
+
return fresh;
|
|
7394
|
+
}
|
|
7395
|
+
const url = `${apiUrl}/me/hivemind-stats`;
|
|
7396
|
+
const ctrl = new AbortController();
|
|
7397
|
+
const timeoutHandle = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
|
|
7398
|
+
try {
|
|
7399
|
+
const resp = await fetch(url, {
|
|
7400
|
+
headers: {
|
|
7401
|
+
Authorization: `Bearer ${creds.token}`,
|
|
7402
|
+
...creds.orgId ? { "X-Activeloop-Org-Id": creds.orgId } : {}
|
|
7403
|
+
},
|
|
7404
|
+
signal: ctrl.signal
|
|
7405
|
+
});
|
|
7406
|
+
if (!resp.ok) {
|
|
7407
|
+
log5(`fetch ${url} returned ${resp.status}`);
|
|
7408
|
+
return stale ?? null;
|
|
7409
|
+
}
|
|
7410
|
+
const body = await resp.json();
|
|
7411
|
+
if (!body || typeof body !== "object") {
|
|
7412
|
+
log5(`fetch ${url} returned malformed body`);
|
|
7413
|
+
return stale ?? null;
|
|
7414
|
+
}
|
|
7415
|
+
const data = {
|
|
7416
|
+
org: scopeFromServer(body.org),
|
|
7417
|
+
user: scopeFromServer(body.user)
|
|
7418
|
+
};
|
|
7419
|
+
writeCache2(scopeKey, data);
|
|
7420
|
+
log5(`fetched org stats from ${apiUrl}`);
|
|
7421
|
+
return data;
|
|
7422
|
+
} catch (e) {
|
|
7423
|
+
log5(`fetch ${url} failed: ${e?.message ?? String(e)}`);
|
|
7424
|
+
return stale ?? null;
|
|
7425
|
+
} finally {
|
|
7426
|
+
clearTimeout(timeoutHandle);
|
|
7427
|
+
}
|
|
7428
|
+
}
|
|
7429
|
+
|
|
7430
|
+
// dist/src/notifications/usage-tracker.js
|
|
7431
|
+
import { appendFileSync as appendFileSync3, existsSync as existsSync22, mkdirSync as mkdirSync14, readFileSync as readFileSync22, readdirSync as readdirSync3 } from "node:fs";
|
|
7432
|
+
import { dirname as dirname10, join as join29 } from "node:path";
|
|
7433
|
+
import { homedir as homedir12 } from "node:os";
|
|
7434
|
+
var log6 = (msg) => log2("usage-tracker", msg);
|
|
7435
|
+
function statsFilePath() {
|
|
7436
|
+
return join29(homedir12(), ".deeplake", "usage-stats.jsonl");
|
|
7437
|
+
}
|
|
7438
|
+
function readUsageRecords() {
|
|
7439
|
+
try {
|
|
7440
|
+
if (!existsSync22(statsFilePath()))
|
|
7441
|
+
return [];
|
|
7442
|
+
const raw = readFileSync22(statsFilePath(), "utf-8");
|
|
7443
|
+
const out = [];
|
|
7444
|
+
for (const line of raw.split("\n")) {
|
|
7445
|
+
const trimmed = line.trim();
|
|
7446
|
+
if (!trimmed)
|
|
7447
|
+
continue;
|
|
7448
|
+
try {
|
|
7449
|
+
const rec = JSON.parse(trimmed);
|
|
7450
|
+
if (typeof rec.endedAt === "string" && typeof rec.sessionId === "string") {
|
|
7451
|
+
out.push({
|
|
7452
|
+
endedAt: rec.endedAt,
|
|
7453
|
+
sessionId: rec.sessionId,
|
|
7454
|
+
memorySearchBytes: typeof rec.memorySearchBytes === "number" ? rec.memorySearchBytes : 0,
|
|
7455
|
+
memorySearchCount: typeof rec.memorySearchCount === "number" ? rec.memorySearchCount : 0
|
|
7456
|
+
});
|
|
7457
|
+
}
|
|
7458
|
+
} catch {
|
|
7459
|
+
}
|
|
7460
|
+
}
|
|
7461
|
+
return out;
|
|
7462
|
+
} catch (e) {
|
|
7463
|
+
log6(`readUsageRecords failed: ${e?.message ?? String(e)}`);
|
|
7464
|
+
return [];
|
|
7465
|
+
}
|
|
7466
|
+
}
|
|
7467
|
+
function sumMetric(records, key) {
|
|
7468
|
+
let total = 0;
|
|
7469
|
+
for (const r of records) {
|
|
7470
|
+
const v = r[key];
|
|
7471
|
+
if (typeof v === "number" && Number.isFinite(v))
|
|
7472
|
+
total += v;
|
|
7473
|
+
}
|
|
7474
|
+
return total;
|
|
7475
|
+
}
|
|
7476
|
+
function countUserGeneratedSkills(userName) {
|
|
7477
|
+
if (!userName)
|
|
7478
|
+
return 0;
|
|
7479
|
+
const dir = join29(homedir12(), ".claude", "skills");
|
|
7480
|
+
if (!existsSync22(dir))
|
|
7481
|
+
return 0;
|
|
7482
|
+
const suffix = `--${userName}`;
|
|
7483
|
+
try {
|
|
7484
|
+
let count = 0;
|
|
7485
|
+
for (const name of readdirSync3(dir)) {
|
|
7486
|
+
const idx = name.lastIndexOf(suffix);
|
|
7487
|
+
if (idx > 0 && idx + suffix.length === name.length)
|
|
7488
|
+
count += 1;
|
|
7489
|
+
}
|
|
7490
|
+
return count;
|
|
7491
|
+
} catch (e) {
|
|
7492
|
+
log6(`countUserGeneratedSkills readdir failed: ${e?.message ?? String(e)}`);
|
|
7493
|
+
return 0;
|
|
7494
|
+
}
|
|
5418
7495
|
}
|
|
5419
7496
|
|
|
5420
|
-
// dist/src/
|
|
5421
|
-
import {
|
|
5422
|
-
import {
|
|
5423
|
-
import { dirname as dirname8, join as join32 } from "node:path";
|
|
5424
|
-
|
|
5425
|
-
// dist/src/skillify/scope-config.js
|
|
5426
|
-
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync15, writeFileSync as writeFileSync11 } from "node:fs";
|
|
5427
|
-
import { join as join22 } from "node:path";
|
|
7497
|
+
// dist/src/skillify/state.js
|
|
7498
|
+
import { readFileSync as readFileSync23, writeFileSync as writeFileSync17, writeSync, mkdirSync as mkdirSync15, renameSync as renameSync9, rmdirSync, existsSync as existsSync24, lstatSync as lstatSync3, unlinkSync as unlinkSync9, openSync as openSync2, closeSync as closeSync2 } from "node:fs";
|
|
7499
|
+
import { join as join32 } from "node:path";
|
|
5428
7500
|
|
|
5429
7501
|
// dist/src/skillify/legacy-migration.js
|
|
5430
|
-
import { existsSync as
|
|
5431
|
-
import { dirname as
|
|
7502
|
+
import { existsSync as existsSync23, renameSync as renameSync8 } from "node:fs";
|
|
7503
|
+
import { dirname as dirname11, join as join31 } from "node:path";
|
|
5432
7504
|
|
|
5433
7505
|
// dist/src/skillify/state-dir.js
|
|
5434
|
-
import { homedir as
|
|
5435
|
-
import { join as
|
|
7506
|
+
import { homedir as homedir13 } from "node:os";
|
|
7507
|
+
import { join as join30 } from "node:path";
|
|
5436
7508
|
function getStateDir() {
|
|
5437
7509
|
const override = process.env.HIVEMIND_STATE_DIR?.trim();
|
|
5438
|
-
return override && override.length > 0 ? override :
|
|
7510
|
+
return override && override.length > 0 ? override : join30(homedir13(), ".deeplake", "state", "skillify");
|
|
5439
7511
|
}
|
|
5440
7512
|
|
|
5441
7513
|
// dist/src/skillify/legacy-migration.js
|
|
@@ -5448,13 +7520,13 @@ function migrateLegacyStateDir() {
|
|
|
5448
7520
|
return;
|
|
5449
7521
|
attempted = true;
|
|
5450
7522
|
const current = getStateDir();
|
|
5451
|
-
const legacy =
|
|
5452
|
-
if (!
|
|
7523
|
+
const legacy = join31(dirname11(current), "skilify");
|
|
7524
|
+
if (!existsSync23(legacy))
|
|
5453
7525
|
return;
|
|
5454
|
-
if (
|
|
7526
|
+
if (existsSync23(current))
|
|
5455
7527
|
return;
|
|
5456
7528
|
try {
|
|
5457
|
-
|
|
7529
|
+
renameSync8(legacy, current);
|
|
5458
7530
|
dlog(`migrated ${legacy} -> ${current}`);
|
|
5459
7531
|
} catch (err) {
|
|
5460
7532
|
const code = err.code;
|
|
@@ -5466,18 +7538,818 @@ function migrateLegacyStateDir() {
|
|
|
5466
7538
|
}
|
|
5467
7539
|
}
|
|
5468
7540
|
|
|
7541
|
+
// dist/src/skillify/state.js
|
|
7542
|
+
var YIELD_BUF = new Int32Array(new SharedArrayBuffer(4));
|
|
7543
|
+
var TRIGGER_THRESHOLD = (() => {
|
|
7544
|
+
const n = Number(process.env.HIVEMIND_SKILLIFY_EVERY_N_TURNS ?? "");
|
|
7545
|
+
return Number.isInteger(n) && n > 0 ? n : 20;
|
|
7546
|
+
})();
|
|
7547
|
+
|
|
7548
|
+
// dist/src/dashboard/data.js
|
|
7549
|
+
var log7 = (msg) => log2("dashboard-data", msg);
|
|
7550
|
+
var BYTES_PER_TOKEN = 4;
|
|
7551
|
+
var SAVINGS_MULTIPLIER = 1.7;
|
|
7552
|
+
function graphsRoot2() {
|
|
7553
|
+
return process.env.HIVEMIND_GRAPHS_HOME ?? join33(homedir14(), ".hivemind", "graphs");
|
|
7554
|
+
}
|
|
7555
|
+
function bytesToSavedTokens(bytes) {
|
|
7556
|
+
if (!Number.isFinite(bytes) || bytes <= 0)
|
|
7557
|
+
return 0;
|
|
7558
|
+
const delivered = bytes / BYTES_PER_TOKEN;
|
|
7559
|
+
return (SAVINGS_MULTIPLIER - 1) * delivered;
|
|
7560
|
+
}
|
|
7561
|
+
function resolveSnapshot(repoDir2) {
|
|
7562
|
+
const snapshotsDir = join33(repoDir2, "snapshots");
|
|
7563
|
+
if (!existsSync25(snapshotsDir))
|
|
7564
|
+
return null;
|
|
7565
|
+
let snapshotPath = null;
|
|
7566
|
+
const pointer = join33(repoDir2, "latest-commit.txt");
|
|
7567
|
+
if (existsSync25(pointer)) {
|
|
7568
|
+
try {
|
|
7569
|
+
const sha = readFileSync24(pointer, "utf-8").trim();
|
|
7570
|
+
if (sha) {
|
|
7571
|
+
const candidate = join33(snapshotsDir, `${sha}.json`);
|
|
7572
|
+
if (existsSync25(candidate))
|
|
7573
|
+
snapshotPath = candidate;
|
|
7574
|
+
else
|
|
7575
|
+
log7(`latest-commit.txt points at missing ${sha}.json \u2014 scanning snapshots/`);
|
|
7576
|
+
}
|
|
7577
|
+
} catch (e) {
|
|
7578
|
+
log7(`latest-commit.txt read failed: ${e?.message ?? String(e)}`);
|
|
7579
|
+
}
|
|
7580
|
+
}
|
|
7581
|
+
if (!snapshotPath) {
|
|
7582
|
+
try {
|
|
7583
|
+
const candidates = readdirSync4(snapshotsDir).filter((name) => name.endsWith(".json")).map((name) => {
|
|
7584
|
+
const full = join33(snapshotsDir, name);
|
|
7585
|
+
return { full, mtime: statSync3(full).mtimeMs };
|
|
7586
|
+
}).sort((a, b) => b.mtime - a.mtime);
|
|
7587
|
+
if (candidates.length > 0)
|
|
7588
|
+
snapshotPath = candidates[0].full;
|
|
7589
|
+
} catch (e) {
|
|
7590
|
+
log7(`snapshots/ scan failed: ${e?.message ?? String(e)}`);
|
|
7591
|
+
}
|
|
7592
|
+
}
|
|
7593
|
+
if (!snapshotPath)
|
|
7594
|
+
return null;
|
|
7595
|
+
let raw;
|
|
7596
|
+
try {
|
|
7597
|
+
raw = readFileSync24(snapshotPath, "utf-8");
|
|
7598
|
+
} catch (e) {
|
|
7599
|
+
log7(`snapshot read failed: ${e?.message ?? String(e)}`);
|
|
7600
|
+
return null;
|
|
7601
|
+
}
|
|
7602
|
+
let parsed;
|
|
7603
|
+
try {
|
|
7604
|
+
parsed = JSON.parse(raw);
|
|
7605
|
+
} catch (e) {
|
|
7606
|
+
log7(`snapshot parse failed: ${e?.message ?? String(e)}`);
|
|
7607
|
+
return null;
|
|
7608
|
+
}
|
|
7609
|
+
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.nodes) || !Array.isArray(parsed.links)) {
|
|
7610
|
+
log7("snapshot shape invalid (missing nodes/links arrays)");
|
|
7611
|
+
return null;
|
|
7612
|
+
}
|
|
7613
|
+
return {
|
|
7614
|
+
commitSha: parsed.graph?.commit_sha ?? null,
|
|
7615
|
+
snapshotPath,
|
|
7616
|
+
nodeCount: parsed.nodes.length,
|
|
7617
|
+
edgeCount: parsed.links.length,
|
|
7618
|
+
snapshot: parsed
|
|
7619
|
+
};
|
|
7620
|
+
}
|
|
7621
|
+
async function loadKpis(creds) {
|
|
7622
|
+
const userName = creds?.userName;
|
|
7623
|
+
const skillsCreated = countUserGeneratedSkills(userName);
|
|
7624
|
+
const records = readUsageRecords();
|
|
7625
|
+
const localBytes = sumMetric(records, "memorySearchBytes");
|
|
7626
|
+
const localCount = sumMetric(records, "memorySearchCount");
|
|
7627
|
+
let orgStats = null;
|
|
7628
|
+
if (creds?.token) {
|
|
7629
|
+
try {
|
|
7630
|
+
orgStats = await fetchOrgStats(creds);
|
|
7631
|
+
} catch (e) {
|
|
7632
|
+
log7(`fetchOrgStats threw: ${e?.message ?? String(e)}`);
|
|
7633
|
+
}
|
|
7634
|
+
}
|
|
7635
|
+
if (orgStats) {
|
|
7636
|
+
return {
|
|
7637
|
+
tokensSaved: bytesToSavedTokens(orgStats.org.memorySearchBytes),
|
|
7638
|
+
tokensSource: "org",
|
|
7639
|
+
skillsCreated,
|
|
7640
|
+
memorySearches: orgStats.org.memoryRecallCount,
|
|
7641
|
+
sessionsCount: orgStats.org.sessionsCount,
|
|
7642
|
+
userTokensSaved: bytesToSavedTokens(orgStats.user.memorySearchBytes)
|
|
7643
|
+
};
|
|
7644
|
+
}
|
|
7645
|
+
if (records.length > 0) {
|
|
7646
|
+
return {
|
|
7647
|
+
tokensSaved: bytesToSavedTokens(localBytes),
|
|
7648
|
+
tokensSource: "local",
|
|
7649
|
+
skillsCreated,
|
|
7650
|
+
memorySearches: localCount,
|
|
7651
|
+
sessionsCount: records.length,
|
|
7652
|
+
userTokensSaved: bytesToSavedTokens(localBytes)
|
|
7653
|
+
};
|
|
7654
|
+
}
|
|
7655
|
+
return {
|
|
7656
|
+
tokensSaved: null,
|
|
7657
|
+
tokensSource: "none",
|
|
7658
|
+
skillsCreated,
|
|
7659
|
+
memorySearches: 0,
|
|
7660
|
+
sessionsCount: null,
|
|
7661
|
+
userTokensSaved: null
|
|
7662
|
+
};
|
|
7663
|
+
}
|
|
7664
|
+
async function loadDashboardData(opts = {}) {
|
|
7665
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
7666
|
+
const { key: repoKey, project: repoProject } = deriveProjectKey(cwd);
|
|
7667
|
+
const repoDir2 = join33(opts.graphsHome ?? graphsRoot2(), repoKey);
|
|
7668
|
+
const graph = resolveSnapshot(repoDir2);
|
|
7669
|
+
const creds = opts.creds === void 0 ? loadCredentials() : opts.creds;
|
|
7670
|
+
const kpis = await loadKpis(creds);
|
|
7671
|
+
return {
|
|
7672
|
+
repoKey,
|
|
7673
|
+
repoProject,
|
|
7674
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7675
|
+
kpis,
|
|
7676
|
+
graph
|
|
7677
|
+
};
|
|
7678
|
+
}
|
|
7679
|
+
|
|
7680
|
+
// dist/src/dashboard/open.js
|
|
7681
|
+
import { spawn } from "node:child_process";
|
|
7682
|
+
import { accessSync, constants as fsConstants, statSync as statSync4 } from "node:fs";
|
|
7683
|
+
import { platform as nodePlatform } from "node:os";
|
|
7684
|
+
import { delimiter, join as join34 } from "node:path";
|
|
7685
|
+
function resolveOpenPlatform() {
|
|
7686
|
+
const p = nodePlatform();
|
|
7687
|
+
if (p === "linux" || p === "darwin" || p === "win32")
|
|
7688
|
+
return p;
|
|
7689
|
+
return null;
|
|
7690
|
+
}
|
|
7691
|
+
function openCommandFor(p, path) {
|
|
7692
|
+
switch (p) {
|
|
7693
|
+
case "linux":
|
|
7694
|
+
return { command: "xdg-open", args: [path] };
|
|
7695
|
+
case "darwin":
|
|
7696
|
+
return { command: "open", args: [path] };
|
|
7697
|
+
case "win32":
|
|
7698
|
+
return { command: "cmd", args: ["/c", "start", "", path] };
|
|
7699
|
+
}
|
|
7700
|
+
}
|
|
7701
|
+
function findBinaryOnPath(name) {
|
|
7702
|
+
const PATH = process.env.PATH ?? "";
|
|
7703
|
+
if (!PATH)
|
|
7704
|
+
return null;
|
|
7705
|
+
const isWin = nodePlatform() === "win32";
|
|
7706
|
+
const exts = isWin ? (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((e) => e.trim()).filter(Boolean) : [""];
|
|
7707
|
+
for (const dir of PATH.split(delimiter)) {
|
|
7708
|
+
if (!dir)
|
|
7709
|
+
continue;
|
|
7710
|
+
for (const ext of exts) {
|
|
7711
|
+
const candidate = join34(dir, name + ext);
|
|
7712
|
+
try {
|
|
7713
|
+
const st = statSync4(candidate);
|
|
7714
|
+
if (!st.isFile())
|
|
7715
|
+
continue;
|
|
7716
|
+
if (isWin)
|
|
7717
|
+
return candidate;
|
|
7718
|
+
try {
|
|
7719
|
+
accessSync(candidate, fsConstants.X_OK);
|
|
7720
|
+
return candidate;
|
|
7721
|
+
} catch {
|
|
7722
|
+
}
|
|
7723
|
+
} catch {
|
|
7724
|
+
}
|
|
7725
|
+
}
|
|
7726
|
+
}
|
|
7727
|
+
return null;
|
|
7728
|
+
}
|
|
7729
|
+
function openInBrowser(path, opts = {}) {
|
|
7730
|
+
const p = opts.platformOverride === void 0 ? resolveOpenPlatform() : opts.platformOverride;
|
|
7731
|
+
if (!p)
|
|
7732
|
+
return { attempted: false };
|
|
7733
|
+
const { command, args } = openCommandFor(p, path);
|
|
7734
|
+
const exists = opts.binaryExists ?? ((cmd) => findBinaryOnPath(cmd) !== null);
|
|
7735
|
+
if (!exists(command))
|
|
7736
|
+
return { attempted: false };
|
|
7737
|
+
const useSpawn = opts.spawner ?? spawn;
|
|
7738
|
+
try {
|
|
7739
|
+
const child = useSpawn(command, args, { stdio: "ignore", detached: true });
|
|
7740
|
+
child.on("error", () => {
|
|
7741
|
+
});
|
|
7742
|
+
if (typeof child.unref === "function") {
|
|
7743
|
+
child.unref();
|
|
7744
|
+
}
|
|
7745
|
+
return { attempted: true, command };
|
|
7746
|
+
} catch {
|
|
7747
|
+
return { attempted: false };
|
|
7748
|
+
}
|
|
7749
|
+
}
|
|
7750
|
+
|
|
7751
|
+
// dist/src/dashboard/render.js
|
|
7752
|
+
var VIS_NETWORK_CDN = "https://unpkg.com/vis-network@9.1.9/standalone/umd/vis-network.min.js";
|
|
7753
|
+
var KIND_COLORS = {
|
|
7754
|
+
function: "#7aa2f7",
|
|
7755
|
+
// soft blue
|
|
7756
|
+
class: "#bb9af7",
|
|
7757
|
+
// purple
|
|
7758
|
+
method: "#9ece6a",
|
|
7759
|
+
// green
|
|
7760
|
+
interface: "#e0af68",
|
|
7761
|
+
// amber
|
|
7762
|
+
type_alias: "#7dcfff",
|
|
7763
|
+
// cyan
|
|
7764
|
+
enum: "#f7768e",
|
|
7765
|
+
// pink
|
|
7766
|
+
const: "#9d7cd8",
|
|
7767
|
+
// muted purple
|
|
7768
|
+
module: "#565f89"
|
|
7769
|
+
// slate
|
|
7770
|
+
};
|
|
7771
|
+
var DEFAULT_NODE_COLOR = "#565f89";
|
|
7772
|
+
function isObject2(v) {
|
|
7773
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
7774
|
+
}
|
|
7775
|
+
function asString(v) {
|
|
7776
|
+
return typeof v === "string" ? v : null;
|
|
7777
|
+
}
|
|
7778
|
+
function transformSnapshotToVis(snapshot) {
|
|
7779
|
+
if (!isObject2(snapshot))
|
|
7780
|
+
return { nodes: [], edges: [] };
|
|
7781
|
+
const raw = snapshot;
|
|
7782
|
+
const visNodes = [];
|
|
7783
|
+
const ids = /* @__PURE__ */ new Set();
|
|
7784
|
+
if (Array.isArray(raw.nodes)) {
|
|
7785
|
+
for (const n of raw.nodes) {
|
|
7786
|
+
if (!isObject2(n))
|
|
7787
|
+
continue;
|
|
7788
|
+
const node = n;
|
|
7789
|
+
const id = asString(node.id);
|
|
7790
|
+
if (!id)
|
|
7791
|
+
continue;
|
|
7792
|
+
if (ids.has(id))
|
|
7793
|
+
continue;
|
|
7794
|
+
ids.add(id);
|
|
7795
|
+
const label = asString(node.label) ?? id;
|
|
7796
|
+
const kind = asString(node.kind);
|
|
7797
|
+
const sourceFile = asString(node.source_file);
|
|
7798
|
+
const sourceLoc = asString(node.source_location);
|
|
7799
|
+
const titleParts = [];
|
|
7800
|
+
if (kind)
|
|
7801
|
+
titleParts.push(kind);
|
|
7802
|
+
if (sourceFile) {
|
|
7803
|
+
const loc = sourceLoc ? `${sourceFile}:${sourceLoc}` : sourceFile;
|
|
7804
|
+
titleParts.push(loc);
|
|
7805
|
+
}
|
|
7806
|
+
const color = kind && KIND_COLORS[kind] ? KIND_COLORS[kind] : DEFAULT_NODE_COLOR;
|
|
7807
|
+
visNodes.push({
|
|
7808
|
+
id,
|
|
7809
|
+
label,
|
|
7810
|
+
title: titleParts.length > 0 ? titleParts.map(escHtml).join(" \xB7 ") : escHtml(id),
|
|
7811
|
+
group: kind ?? void 0,
|
|
7812
|
+
color: { background: color, border: color }
|
|
7813
|
+
});
|
|
7814
|
+
}
|
|
7815
|
+
}
|
|
7816
|
+
const visEdges = [];
|
|
7817
|
+
if (Array.isArray(raw.links)) {
|
|
7818
|
+
for (const l of raw.links) {
|
|
7819
|
+
if (!isObject2(l))
|
|
7820
|
+
continue;
|
|
7821
|
+
const edge = l;
|
|
7822
|
+
const from = asString(edge.source);
|
|
7823
|
+
const to = asString(edge.target);
|
|
7824
|
+
if (!from || !to)
|
|
7825
|
+
continue;
|
|
7826
|
+
const relation = asString(edge.relation);
|
|
7827
|
+
const confidence = asString(edge.confidence);
|
|
7828
|
+
const titleParts = [];
|
|
7829
|
+
if (relation)
|
|
7830
|
+
titleParts.push(relation);
|
|
7831
|
+
if (confidence)
|
|
7832
|
+
titleParts.push(`[${confidence}]`);
|
|
7833
|
+
visEdges.push({
|
|
7834
|
+
from,
|
|
7835
|
+
to,
|
|
7836
|
+
title: titleParts.length > 0 ? titleParts.map(escHtml).join(" ") : `${escHtml(from)} \u2192 ${escHtml(to)}`
|
|
7837
|
+
});
|
|
7838
|
+
}
|
|
7839
|
+
}
|
|
7840
|
+
return { nodes: visNodes, edges: visEdges };
|
|
7841
|
+
}
|
|
7842
|
+
function escHtml(s) {
|
|
7843
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
7844
|
+
}
|
|
7845
|
+
function safeJsonForScript(value) {
|
|
7846
|
+
return JSON.stringify(value).replace(/<\//g, "<\\/").replace(/<!--/g, "<\\u0021--").replace(/-->/g, "--\\u003e");
|
|
7847
|
+
}
|
|
7848
|
+
function formatTokensCompact(n) {
|
|
7849
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
7850
|
+
return "0";
|
|
7851
|
+
if (n < 1e3)
|
|
7852
|
+
return `${Math.round(n)}`;
|
|
7853
|
+
if (n < 1e5)
|
|
7854
|
+
return `${(n / 1e3).toFixed(1)}k`;
|
|
7855
|
+
if (n < 1e6)
|
|
7856
|
+
return `${Math.round(n / 1e3)}k`;
|
|
7857
|
+
return `${(n / 1e6).toFixed(1)}M`;
|
|
7858
|
+
}
|
|
7859
|
+
function formatInt(n) {
|
|
7860
|
+
if (!Number.isFinite(n))
|
|
7861
|
+
return "0";
|
|
7862
|
+
return Math.round(n).toLocaleString("en-US");
|
|
7863
|
+
}
|
|
7864
|
+
function renderKpiCards(kpis) {
|
|
7865
|
+
const tokensValue = kpis.tokensSaved == null ? "\u2014" : `~${formatTokensCompact(kpis.tokensSaved)}`;
|
|
7866
|
+
const tokensSub = (() => {
|
|
7867
|
+
if (kpis.tokensSource === "org") {
|
|
7868
|
+
return kpis.userTokensSaved != null ? `Org-wide \xB7 you ~${formatTokensCompact(kpis.userTokensSaved)}` : "Org-wide";
|
|
7869
|
+
}
|
|
7870
|
+
if (kpis.tokensSource === "local")
|
|
7871
|
+
return "Local (this machine)";
|
|
7872
|
+
return "Run a session to start tracking";
|
|
7873
|
+
})();
|
|
7874
|
+
const memoryValue = kpis.memorySearches > 0 ? formatInt(kpis.memorySearches) : kpis.tokensSource === "none" ? "\u2014" : "0";
|
|
7875
|
+
const sessionsValue = kpis.sessionsCount == null ? "\u2014" : formatInt(kpis.sessionsCount);
|
|
7876
|
+
const cards = [
|
|
7877
|
+
{
|
|
7878
|
+
label: "Tokens saved",
|
|
7879
|
+
value: tokensValue,
|
|
7880
|
+
sub: tokensSub
|
|
7881
|
+
},
|
|
7882
|
+
{
|
|
7883
|
+
label: "Skills created",
|
|
7884
|
+
value: formatInt(kpis.skillsCreated),
|
|
7885
|
+
sub: "~/.claude/skills/"
|
|
7886
|
+
},
|
|
7887
|
+
{
|
|
7888
|
+
label: "Memory recalls",
|
|
7889
|
+
value: memoryValue,
|
|
7890
|
+
sub: kpis.tokensSource === "org" ? "Org-wide" : kpis.tokensSource === "local" ? "Local" : ""
|
|
7891
|
+
},
|
|
7892
|
+
{
|
|
7893
|
+
label: "Sessions",
|
|
7894
|
+
value: sessionsValue,
|
|
7895
|
+
sub: kpis.tokensSource === "org" ? "Org-wide" : kpis.tokensSource === "local" ? "Local" : ""
|
|
7896
|
+
}
|
|
7897
|
+
];
|
|
7898
|
+
return cards.map((c) => `
|
|
7899
|
+
<div class="kpi">
|
|
7900
|
+
<div class="kpi-label">${escHtml(c.label)}</div>
|
|
7901
|
+
<div class="kpi-value">${escHtml(c.value)}</div>
|
|
7902
|
+
<div class="kpi-sub">${escHtml(c.sub)}</div>
|
|
7903
|
+
</div>`).join("");
|
|
7904
|
+
}
|
|
7905
|
+
function renderGraphSection(data) {
|
|
7906
|
+
if (data.graph == null) {
|
|
7907
|
+
return `
|
|
7908
|
+
<div class="graph-card">
|
|
7909
|
+
<h2>Codebase graph</h2>
|
|
7910
|
+
<div class="empty">
|
|
7911
|
+
No graph snapshot yet for this repo.<br>
|
|
7912
|
+
Run <code>hivemind graph build</code> to generate one.
|
|
7913
|
+
</div>
|
|
7914
|
+
</div>`;
|
|
7915
|
+
}
|
|
7916
|
+
const visPayload = transformSnapshotToVis(data.graph.snapshot);
|
|
7917
|
+
const commitLabel = data.graph.commitSha ? `commit ${data.graph.commitSha.slice(0, 12)}` : "no commit (loose dir)";
|
|
7918
|
+
const meta = `${formatInt(data.graph.nodeCount)} nodes \xB7 ${formatInt(data.graph.edgeCount)} edges \xB7 ${commitLabel}`;
|
|
7919
|
+
return `
|
|
7920
|
+
<div class="graph-card">
|
|
7921
|
+
<h2>Codebase graph</h2>
|
|
7922
|
+
<div class="graph-meta">${escHtml(meta)}</div>
|
|
7923
|
+
<div id="graph"></div>
|
|
7924
|
+
</div>
|
|
7925
|
+
<script type="application/json" id="hm-graph-data">${safeJsonForScript(visPayload)}</script>
|
|
7926
|
+
<script src="${VIS_NETWORK_CDN}"></script>
|
|
7927
|
+
<script>
|
|
7928
|
+
(function () {
|
|
7929
|
+
var holder = document.getElementById('hm-graph-data');
|
|
7930
|
+
var container = document.getElementById('graph');
|
|
7931
|
+
if (!holder || !container || typeof vis === 'undefined') return;
|
|
7932
|
+
var payload;
|
|
7933
|
+
try { payload = JSON.parse(holder.textContent); }
|
|
7934
|
+
catch (e) { container.textContent = 'graph payload parse failed'; return; }
|
|
7935
|
+
if (!payload || !Array.isArray(payload.nodes) || payload.nodes.length === 0) {
|
|
7936
|
+
container.textContent = 'snapshot has no nodes';
|
|
7937
|
+
return;
|
|
7938
|
+
}
|
|
7939
|
+
new vis.Network(container, payload, {
|
|
7940
|
+
nodes: {
|
|
7941
|
+
shape: 'dot',
|
|
7942
|
+
size: 9,
|
|
7943
|
+
font: { color: '#e8eaed', size: 11, face: 'system-ui, sans-serif' },
|
|
7944
|
+
borderWidth: 1,
|
|
7945
|
+
},
|
|
7946
|
+
edges: {
|
|
7947
|
+
color: { color: 'rgba(120, 130, 150, 0.45)', highlight: '#f5b80a', hover: '#e8eaed' },
|
|
7948
|
+
arrows: { to: { enabled: true, scaleFactor: 0.45 } },
|
|
7949
|
+
smooth: { enabled: true, type: 'continuous', roundness: 0.2 },
|
|
7950
|
+
width: 1,
|
|
7951
|
+
},
|
|
7952
|
+
physics: {
|
|
7953
|
+
stabilization: { iterations: 120 },
|
|
7954
|
+
barnesHut: { gravitationalConstant: -2200, springLength: 80, springConstant: 0.04 },
|
|
7955
|
+
},
|
|
7956
|
+
interaction: { hover: true, dragNodes: true, tooltipDelay: 120 },
|
|
7957
|
+
});
|
|
7958
|
+
}());
|
|
7959
|
+
</script>`;
|
|
7960
|
+
}
|
|
7961
|
+
var STYLES = `
|
|
7962
|
+
:root {
|
|
7963
|
+
color-scheme: dark;
|
|
7964
|
+
--bg: #0b0d10;
|
|
7965
|
+
--fg: #e8eaed;
|
|
7966
|
+
--muted: #8b9099;
|
|
7967
|
+
--accent: #f5b80a;
|
|
7968
|
+
--card: #15181d;
|
|
7969
|
+
--border: #22272e;
|
|
7970
|
+
}
|
|
7971
|
+
* { box-sizing: border-box; }
|
|
7972
|
+
html, body { margin: 0; padding: 0; }
|
|
7973
|
+
body {
|
|
7974
|
+
font: 14px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
7975
|
+
background: var(--bg);
|
|
7976
|
+
color: var(--fg);
|
|
7977
|
+
padding: 24px;
|
|
7978
|
+
}
|
|
7979
|
+
.header { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 24px; gap: 16px; flex-wrap: wrap; }
|
|
7980
|
+
.brand { font-weight: 600; font-size: 18px; }
|
|
7981
|
+
.brand .bee { color: var(--accent); margin-right: 4px; }
|
|
7982
|
+
.brand .repo { color: var(--muted); font-weight: 400; margin-left: 8px; }
|
|
7983
|
+
.header .ts { color: var(--muted); font-size: 12px; }
|
|
7984
|
+
.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; margin-bottom: 32px; }
|
|
7985
|
+
.kpi { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 16px 20px; }
|
|
7986
|
+
.kpi-label { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; }
|
|
7987
|
+
.kpi-value { font-size: 28px; font-weight: 600; margin-top: 6px; line-height: 1.1; }
|
|
7988
|
+
.kpi-sub { color: var(--muted); font-size: 12px; margin-top: 4px; }
|
|
7989
|
+
.graph-card { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 16px; }
|
|
7990
|
+
.graph-card h2 { margin: 0 0 8px; font-size: 15px; font-weight: 500; }
|
|
7991
|
+
.graph-meta { color: var(--muted); font-size: 12px; margin-bottom: 12px; }
|
|
7992
|
+
#graph { height: 70vh; border: 1px solid var(--border); border-radius: 4px; background: #0e1116; }
|
|
7993
|
+
.empty { padding: 48px 16px; text-align: center; color: var(--muted); }
|
|
7994
|
+
.empty code { background: #1c2128; padding: 2px 6px; border-radius: 3px; color: var(--fg); font-family: ui-monospace, "SFMono-Regular", monospace; }
|
|
7995
|
+
.footer { color: var(--muted); font-size: 11px; margin-top: 24px; text-align: right; }
|
|
7996
|
+
`;
|
|
7997
|
+
function renderDashboardHtml(data) {
|
|
7998
|
+
const title = `Hivemind Dashboard \xB7 ${data.repoProject}`;
|
|
7999
|
+
return `<!DOCTYPE html>
|
|
8000
|
+
<html lang="en">
|
|
8001
|
+
<head>
|
|
8002
|
+
<meta charset="utf-8">
|
|
8003
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8004
|
+
<title>${escHtml(title)}</title>
|
|
8005
|
+
<style>${STYLES}</style>
|
|
8006
|
+
</head>
|
|
8007
|
+
<body>
|
|
8008
|
+
<div class="header">
|
|
8009
|
+
<div class="brand">
|
|
8010
|
+
<span class="bee">\u{1F41D}</span>hivemind dashboard
|
|
8011
|
+
<span class="repo">/ ${escHtml(data.repoProject)}</span>
|
|
8012
|
+
</div>
|
|
8013
|
+
<div class="ts">${escHtml(data.generatedAt)}</div>
|
|
8014
|
+
</div>
|
|
8015
|
+
<div class="kpi-grid">${renderKpiCards(data.kpis)}
|
|
8016
|
+
</div>
|
|
8017
|
+
${renderGraphSection(data)}
|
|
8018
|
+
<div class="footer">repo_key ${escHtml(data.repoKey)}</div>
|
|
8019
|
+
</body>
|
|
8020
|
+
</html>
|
|
8021
|
+
`;
|
|
8022
|
+
}
|
|
8023
|
+
|
|
8024
|
+
// dist/src/dashboard/serve.js
|
|
8025
|
+
import { createServer } from "node:http";
|
|
8026
|
+
var DEFAULT_PORT = 8123;
|
|
8027
|
+
var DEFAULT_HOST = "127.0.0.1";
|
|
8028
|
+
function handleRequest(html) {
|
|
8029
|
+
return (req, res) => {
|
|
8030
|
+
const url = req.url ?? "/";
|
|
8031
|
+
const path = url.split("?")[0];
|
|
8032
|
+
if (req.method === "GET" && (path === "/" || path === "/index.html")) {
|
|
8033
|
+
res.statusCode = 200;
|
|
8034
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
8035
|
+
res.setHeader("Cache-Control", "no-store");
|
|
8036
|
+
res.end(html);
|
|
8037
|
+
return;
|
|
8038
|
+
}
|
|
8039
|
+
if (req.method === "GET" && path === "/health") {
|
|
8040
|
+
res.statusCode = 204;
|
|
8041
|
+
res.end();
|
|
8042
|
+
return;
|
|
8043
|
+
}
|
|
8044
|
+
res.statusCode = 404;
|
|
8045
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
8046
|
+
res.end("Not found. The dashboard lives at /.\n");
|
|
8047
|
+
};
|
|
8048
|
+
}
|
|
8049
|
+
function tryListen(server, host, port) {
|
|
8050
|
+
return new Promise((resolve6, reject) => {
|
|
8051
|
+
const onError = (err) => {
|
|
8052
|
+
server.off("listening", onListening);
|
|
8053
|
+
reject(err);
|
|
8054
|
+
};
|
|
8055
|
+
const onListening = () => {
|
|
8056
|
+
server.off("error", onError);
|
|
8057
|
+
const addr = server.address();
|
|
8058
|
+
if (!addr || typeof addr === "string") {
|
|
8059
|
+
reject(new Error("server bound to a non-IP address"));
|
|
8060
|
+
return;
|
|
8061
|
+
}
|
|
8062
|
+
resolve6(addr.port);
|
|
8063
|
+
};
|
|
8064
|
+
server.once("error", onError);
|
|
8065
|
+
server.once("listening", onListening);
|
|
8066
|
+
server.listen(port, host);
|
|
8067
|
+
});
|
|
8068
|
+
}
|
|
8069
|
+
async function serveDashboardHtml(opts) {
|
|
8070
|
+
const host = opts.host ?? DEFAULT_HOST;
|
|
8071
|
+
const requested = opts.port === void 0 || !Number.isFinite(opts.port) || opts.port < 0 ? DEFAULT_PORT : opts.port;
|
|
8072
|
+
const server = createServer(handleRequest(opts.html));
|
|
8073
|
+
let bound;
|
|
8074
|
+
try {
|
|
8075
|
+
bound = await tryListen(server, host, requested);
|
|
8076
|
+
} catch (e) {
|
|
8077
|
+
if (e?.code !== "EADDRINUSE")
|
|
8078
|
+
throw e;
|
|
8079
|
+
const fallback = createServer(handleRequest(opts.html));
|
|
8080
|
+
bound = await tryListen(fallback, host, 0);
|
|
8081
|
+
server.removeAllListeners();
|
|
8082
|
+
return makeHandle(fallback, host, bound);
|
|
8083
|
+
}
|
|
8084
|
+
return makeHandle(server, host, bound);
|
|
8085
|
+
}
|
|
8086
|
+
function makeHandle(server, host, port) {
|
|
8087
|
+
let resolveStopped;
|
|
8088
|
+
const stopped = new Promise((resolve6) => {
|
|
8089
|
+
resolveStopped = resolve6;
|
|
8090
|
+
});
|
|
8091
|
+
server.on("close", () => resolveStopped());
|
|
8092
|
+
return {
|
|
8093
|
+
host,
|
|
8094
|
+
port,
|
|
8095
|
+
stopped,
|
|
8096
|
+
close: () => new Promise((resolve6, reject) => {
|
|
8097
|
+
server.close((err) => err ? reject(err) : resolve6());
|
|
8098
|
+
})
|
|
8099
|
+
};
|
|
8100
|
+
}
|
|
8101
|
+
|
|
8102
|
+
// dist/src/commands/dashboard.js
|
|
8103
|
+
var USAGE2 = `hivemind dashboard \u2014 codebase graph + KPI dashboard (HTML)
|
|
8104
|
+
|
|
8105
|
+
Usage:
|
|
8106
|
+
hivemind dashboard [--cwd <path>] [--out <path>] [--no-open]
|
|
8107
|
+
[--serve] [--port <n>]
|
|
8108
|
+
Build a self-contained HTML dashboard for this repo, write it
|
|
8109
|
+
to disk, and either open it in the default browser or serve
|
|
8110
|
+
it over loopback HTTP for headless / SSH workflows.
|
|
8111
|
+
|
|
8112
|
+
--cwd <path> Use a different project root (defaults to cwd).
|
|
8113
|
+
--out <path> Write to a custom path (defaults to
|
|
8114
|
+
~/.hivemind/dashboards/<repo-key>/index.html).
|
|
8115
|
+
--no-open Don't open the browser. Combine with --serve
|
|
8116
|
+
to start the server without auto-launching.
|
|
8117
|
+
--serve Start a loopback HTTP server (127.0.0.1) so the
|
|
8118
|
+
dashboard is reachable at a URL. Stays alive
|
|
8119
|
+
until Ctrl+C. Ideal for VS Code / Cursor
|
|
8120
|
+
Remote-SSH (auto-forwards the port \u2192 click to
|
|
8121
|
+
open in the integrated browser tab).
|
|
8122
|
+
--port <n> Port for --serve (default 8123). Falls back to
|
|
8123
|
+
a kernel-assigned port if <n> is in use.
|
|
8124
|
+
|
|
8125
|
+
hivemind dashboard --help
|
|
8126
|
+
Show this message.
|
|
8127
|
+
|
|
8128
|
+
Data sources (all read-only):
|
|
8129
|
+
- Graph snapshot at ~/.hivemind/graphs/<repo-key>/ (produced by
|
|
8130
|
+
\`hivemind graph build\`; the dashboard works without it and shows
|
|
8131
|
+
an empty-state until the producer has run)
|
|
8132
|
+
- KPIs via the org stats endpoint (cached) with a local fallback
|
|
8133
|
+
to ~/.deeplake/usage-stats.jsonl
|
|
8134
|
+
- Skills created from ~/.claude/skills/<name>--<author>/ directories
|
|
8135
|
+
`;
|
|
8136
|
+
function parsePort(raw) {
|
|
8137
|
+
if (raw === void 0 || raw === "")
|
|
8138
|
+
return { error: "--port requires a value" };
|
|
8139
|
+
const n = Number(raw);
|
|
8140
|
+
if (!Number.isInteger(n) || n < 0 || n > 65535) {
|
|
8141
|
+
return { error: `--port must be an integer in [0, 65535], got '${raw}'` };
|
|
8142
|
+
}
|
|
8143
|
+
return n;
|
|
8144
|
+
}
|
|
8145
|
+
function parseDashboardArgs(args) {
|
|
8146
|
+
let cwd;
|
|
8147
|
+
let outPath = "";
|
|
8148
|
+
let open = true;
|
|
8149
|
+
let serve = false;
|
|
8150
|
+
let port;
|
|
8151
|
+
for (let i = 0; i < args.length; i++) {
|
|
8152
|
+
const a = args[i];
|
|
8153
|
+
if (a === "--help" || a === "-h")
|
|
8154
|
+
return { help: true };
|
|
8155
|
+
if (a === "--no-open") {
|
|
8156
|
+
open = false;
|
|
8157
|
+
continue;
|
|
8158
|
+
}
|
|
8159
|
+
if (a === "--serve") {
|
|
8160
|
+
serve = true;
|
|
8161
|
+
continue;
|
|
8162
|
+
}
|
|
8163
|
+
if (a === "--cwd") {
|
|
8164
|
+
const v = args[++i];
|
|
8165
|
+
if (v === void 0 || v.startsWith("-")) {
|
|
8166
|
+
return { error: "--cwd requires a value" };
|
|
8167
|
+
}
|
|
8168
|
+
cwd = v;
|
|
8169
|
+
continue;
|
|
8170
|
+
}
|
|
8171
|
+
if (a.startsWith("--cwd=")) {
|
|
8172
|
+
cwd = a.slice("--cwd=".length);
|
|
8173
|
+
continue;
|
|
8174
|
+
}
|
|
8175
|
+
if (a === "--out") {
|
|
8176
|
+
const v = args[++i];
|
|
8177
|
+
if (v === void 0 || v.startsWith("-")) {
|
|
8178
|
+
return { error: "--out requires a value" };
|
|
8179
|
+
}
|
|
8180
|
+
outPath = v;
|
|
8181
|
+
continue;
|
|
8182
|
+
}
|
|
8183
|
+
if (a.startsWith("--out=")) {
|
|
8184
|
+
outPath = a.slice("--out=".length);
|
|
8185
|
+
continue;
|
|
8186
|
+
}
|
|
8187
|
+
if (a === "--port") {
|
|
8188
|
+
const v = args[++i];
|
|
8189
|
+
if (v === void 0 || v.startsWith("-")) {
|
|
8190
|
+
return { error: "--port requires a value" };
|
|
8191
|
+
}
|
|
8192
|
+
const parsed = parsePort(v);
|
|
8193
|
+
if (typeof parsed === "object")
|
|
8194
|
+
return { error: parsed.error };
|
|
8195
|
+
port = parsed;
|
|
8196
|
+
continue;
|
|
8197
|
+
}
|
|
8198
|
+
if (a.startsWith("--port=")) {
|
|
8199
|
+
const parsed = parsePort(a.slice("--port=".length));
|
|
8200
|
+
if (typeof parsed === "object")
|
|
8201
|
+
return { error: parsed.error };
|
|
8202
|
+
port = parsed;
|
|
8203
|
+
continue;
|
|
8204
|
+
}
|
|
8205
|
+
return { error: `unknown arg '${a}'` };
|
|
8206
|
+
}
|
|
8207
|
+
if (port !== void 0 && !serve) {
|
|
8208
|
+
return { error: "--port requires --serve" };
|
|
8209
|
+
}
|
|
8210
|
+
return {
|
|
8211
|
+
args: {
|
|
8212
|
+
cwd: cwd ?? process.cwd(),
|
|
8213
|
+
outPath,
|
|
8214
|
+
open,
|
|
8215
|
+
serve,
|
|
8216
|
+
port
|
|
8217
|
+
}
|
|
8218
|
+
};
|
|
8219
|
+
}
|
|
8220
|
+
function defaultDashboardOutPath(repoKey) {
|
|
8221
|
+
return join35(homedir15(), ".hivemind", "dashboards", repoKey, "index.html");
|
|
8222
|
+
}
|
|
8223
|
+
async function runDashboardCommand(rawArgs, runOpts = {}) {
|
|
8224
|
+
const out = runOpts.out ?? ((s) => {
|
|
8225
|
+
process.stdout.write(s);
|
|
8226
|
+
});
|
|
8227
|
+
const err = runOpts.err ?? ((s) => {
|
|
8228
|
+
process.stderr.write(s);
|
|
8229
|
+
});
|
|
8230
|
+
const opener = runOpts.opener ?? openInBrowser;
|
|
8231
|
+
const parsed = parseDashboardArgs(rawArgs);
|
|
8232
|
+
if (parsed.help) {
|
|
8233
|
+
out(USAGE2);
|
|
8234
|
+
return 0;
|
|
8235
|
+
}
|
|
8236
|
+
if (parsed.error || !parsed.args) {
|
|
8237
|
+
err(`hivemind dashboard: ${parsed.error ?? "invalid arguments"}
|
|
8238
|
+
`);
|
|
8239
|
+
err(USAGE2);
|
|
8240
|
+
return 2;
|
|
8241
|
+
}
|
|
8242
|
+
const { cwd, outPath, open } = parsed.args;
|
|
8243
|
+
let data;
|
|
8244
|
+
try {
|
|
8245
|
+
data = await loadDashboardData({ cwd });
|
|
8246
|
+
} catch (e) {
|
|
8247
|
+
err(`hivemind dashboard: failed to load data: ${e?.message ?? String(e)}
|
|
8248
|
+
`);
|
|
8249
|
+
return 1;
|
|
8250
|
+
}
|
|
8251
|
+
const html = renderDashboardHtml(data);
|
|
8252
|
+
const finalOut = outPath || defaultDashboardOutPath(data.repoKey);
|
|
8253
|
+
const absOut = resolve5(finalOut);
|
|
8254
|
+
try {
|
|
8255
|
+
mkdirSync16(dirname12(absOut), { recursive: true });
|
|
8256
|
+
writeFileSync18(absOut, html, "utf-8");
|
|
8257
|
+
} catch (e) {
|
|
8258
|
+
err(`hivemind dashboard: failed to write ${absOut}: ${e?.message ?? String(e)}
|
|
8259
|
+
`);
|
|
8260
|
+
return 1;
|
|
8261
|
+
}
|
|
8262
|
+
out(`Wrote ${absOut}
|
|
8263
|
+
`);
|
|
8264
|
+
if (data.graph == null) {
|
|
8265
|
+
out(`(no codebase graph yet \u2014 run 'hivemind graph build' to populate)
|
|
8266
|
+
`);
|
|
8267
|
+
}
|
|
8268
|
+
if (parsed.args.serve) {
|
|
8269
|
+
return await runServeLoop(html, parsed.args, runOpts, out, err);
|
|
8270
|
+
}
|
|
8271
|
+
if (open) {
|
|
8272
|
+
const result = opener(absOut);
|
|
8273
|
+
if (result.attempted) {
|
|
8274
|
+
out(`Opening via ${result.command}
|
|
8275
|
+
`);
|
|
8276
|
+
} else {
|
|
8277
|
+
out(`(no opener for this platform; open the file above manually)
|
|
8278
|
+
`);
|
|
8279
|
+
}
|
|
8280
|
+
}
|
|
8281
|
+
return 0;
|
|
8282
|
+
}
|
|
8283
|
+
async function runServeLoop(html, args, runOpts, out, err) {
|
|
8284
|
+
const server = runOpts.server ?? serveDashboardHtml;
|
|
8285
|
+
const opener = runOpts.opener ?? openInBrowser;
|
|
8286
|
+
const onSignal = runOpts.onSignal ?? defaultOnSignal;
|
|
8287
|
+
let handle;
|
|
8288
|
+
try {
|
|
8289
|
+
handle = await server({ html, port: args.port });
|
|
8290
|
+
} catch (e) {
|
|
8291
|
+
err(`hivemind dashboard: failed to start server: ${e?.message ?? String(e)}
|
|
8292
|
+
`);
|
|
8293
|
+
return 1;
|
|
8294
|
+
}
|
|
8295
|
+
const url = `http://${handle.host}:${handle.port}/`;
|
|
8296
|
+
out(`Serving dashboard at ${url} (Ctrl+C to stop)
|
|
8297
|
+
`);
|
|
8298
|
+
if (args.open) {
|
|
8299
|
+
const result = opener(url);
|
|
8300
|
+
if (result.attempted) {
|
|
8301
|
+
out(`Opening via ${result.command}
|
|
8302
|
+
`);
|
|
8303
|
+
} else {
|
|
8304
|
+
out(`(no opener for this platform; click the URL above or open it manually)
|
|
8305
|
+
`);
|
|
8306
|
+
}
|
|
8307
|
+
}
|
|
8308
|
+
let resolveDone;
|
|
8309
|
+
const done = new Promise((r) => {
|
|
8310
|
+
resolveDone = r;
|
|
8311
|
+
});
|
|
8312
|
+
const shutdown = async () => {
|
|
8313
|
+
try {
|
|
8314
|
+
await handle.close();
|
|
8315
|
+
} catch {
|
|
8316
|
+
}
|
|
8317
|
+
resolveDone(0);
|
|
8318
|
+
};
|
|
8319
|
+
const offInt = onSignal("SIGINT", shutdown);
|
|
8320
|
+
const offTerm = onSignal("SIGTERM", shutdown);
|
|
8321
|
+
handle.stopped.then(() => resolveDone(0));
|
|
8322
|
+
try {
|
|
8323
|
+
return await done;
|
|
8324
|
+
} finally {
|
|
8325
|
+
offInt();
|
|
8326
|
+
offTerm();
|
|
8327
|
+
}
|
|
8328
|
+
}
|
|
8329
|
+
function defaultOnSignal(signal, handler) {
|
|
8330
|
+
process.on(signal, handler);
|
|
8331
|
+
return () => process.off(signal, handler);
|
|
8332
|
+
}
|
|
8333
|
+
|
|
8334
|
+
// dist/src/commands/skillify.js
|
|
8335
|
+
import { readdirSync as readdirSync8, existsSync as existsSync36, readFileSync as readFileSync32, mkdirSync as mkdirSync23, renameSync as renameSync12 } from "node:fs";
|
|
8336
|
+
import { homedir as homedir24 } from "node:os";
|
|
8337
|
+
import { dirname as dirname17, join as join46 } from "node:path";
|
|
8338
|
+
|
|
5469
8339
|
// dist/src/skillify/scope-config.js
|
|
8340
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync25, writeFileSync as writeFileSync19 } from "node:fs";
|
|
8341
|
+
import { join as join36 } from "node:path";
|
|
5470
8342
|
function configPath() {
|
|
5471
|
-
return
|
|
8343
|
+
return join36(getStateDir(), "config.json");
|
|
5472
8344
|
}
|
|
5473
8345
|
var DEFAULT = { scope: "me", team: [], install: "project" };
|
|
5474
8346
|
function loadScopeConfig() {
|
|
5475
8347
|
migrateLegacyStateDir();
|
|
5476
8348
|
const CONFIG_PATH2 = configPath();
|
|
5477
|
-
if (!
|
|
8349
|
+
if (!existsSync26(CONFIG_PATH2))
|
|
5478
8350
|
return DEFAULT;
|
|
5479
8351
|
try {
|
|
5480
|
-
const raw = JSON.parse(
|
|
8352
|
+
const raw = JSON.parse(readFileSync25(CONFIG_PATH2, "utf-8"));
|
|
5481
8353
|
const scope = raw.scope === "team" ? "team" : raw.scope === "org" ? "team" : "me";
|
|
5482
8354
|
const team = Array.isArray(raw.team) ? raw.team.filter((s) => typeof s === "string") : [];
|
|
5483
8355
|
const install = raw.install === "global" ? "global" : "project";
|
|
@@ -5488,19 +8360,19 @@ function loadScopeConfig() {
|
|
|
5488
8360
|
}
|
|
5489
8361
|
function saveScopeConfig(cfg) {
|
|
5490
8362
|
migrateLegacyStateDir();
|
|
5491
|
-
|
|
5492
|
-
|
|
8363
|
+
mkdirSync17(getStateDir(), { recursive: true });
|
|
8364
|
+
writeFileSync19(configPath(), JSON.stringify(cfg, null, 2));
|
|
5493
8365
|
}
|
|
5494
8366
|
|
|
5495
8367
|
// dist/src/skillify/pull.js
|
|
5496
|
-
import { existsSync as
|
|
5497
|
-
import { homedir as
|
|
5498
|
-
import { dirname as
|
|
8368
|
+
import { existsSync as existsSync30, readFileSync as readFileSync28, writeFileSync as writeFileSync22, mkdirSync as mkdirSync20, renameSync as renameSync11, lstatSync as lstatSync5, readlinkSync as readlinkSync2, symlinkSync as symlinkSync2, unlinkSync as unlinkSync11 } from "node:fs";
|
|
8369
|
+
import { homedir as homedir18 } from "node:os";
|
|
8370
|
+
import { dirname as dirname14, join as join40 } from "node:path";
|
|
5499
8371
|
|
|
5500
8372
|
// dist/src/skillify/skill-writer.js
|
|
5501
|
-
import { existsSync as
|
|
5502
|
-
import { homedir as
|
|
5503
|
-
import { join as
|
|
8373
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync18, readFileSync as readFileSync26, readdirSync as readdirSync5, statSync as statSync5, writeFileSync as writeFileSync20 } from "node:fs";
|
|
8374
|
+
import { homedir as homedir16 } from "node:os";
|
|
8375
|
+
import { join as join37 } from "node:path";
|
|
5504
8376
|
function assertValidSkillName(name) {
|
|
5505
8377
|
if (typeof name !== "string" || name.length === 0) {
|
|
5506
8378
|
throw new Error(`invalid skill name: empty or non-string`);
|
|
@@ -5516,10 +8388,10 @@ function assertValidSkillName(name) {
|
|
|
5516
8388
|
}
|
|
5517
8389
|
}
|
|
5518
8390
|
function skillDir(skillsRoot, name) {
|
|
5519
|
-
return
|
|
8391
|
+
return join37(skillsRoot, name);
|
|
5520
8392
|
}
|
|
5521
8393
|
function skillPath(skillsRoot, name) {
|
|
5522
|
-
return
|
|
8394
|
+
return join37(skillDir(skillsRoot, name), "SKILL.md");
|
|
5523
8395
|
}
|
|
5524
8396
|
function renderFrontmatter(fm) {
|
|
5525
8397
|
const lines = ["---"];
|
|
@@ -5597,10 +8469,10 @@ function writeNewSkill(args) {
|
|
|
5597
8469
|
assertValidSkillName(args.name);
|
|
5598
8470
|
const dir = skillDir(args.skillsRoot, args.name);
|
|
5599
8471
|
const path = skillPath(args.skillsRoot, args.name);
|
|
5600
|
-
if (
|
|
8472
|
+
if (existsSync27(path)) {
|
|
5601
8473
|
throw new Error(`skill already exists at ${path}; use mergeSkill`);
|
|
5602
8474
|
}
|
|
5603
|
-
|
|
8475
|
+
mkdirSync18(dir, { recursive: true });
|
|
5604
8476
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5605
8477
|
const author = args.author && args.author.length > 0 ? args.author : void 0;
|
|
5606
8478
|
const contributors = author ? [author] : [];
|
|
@@ -5620,7 +8492,7 @@ function writeNewSkill(args) {
|
|
|
5620
8492
|
|
|
5621
8493
|
${args.body.trim()}
|
|
5622
8494
|
`;
|
|
5623
|
-
|
|
8495
|
+
writeFileSync20(path, text);
|
|
5624
8496
|
return {
|
|
5625
8497
|
path,
|
|
5626
8498
|
action: "created",
|
|
@@ -5632,40 +8504,40 @@ ${args.body.trim()}
|
|
|
5632
8504
|
};
|
|
5633
8505
|
}
|
|
5634
8506
|
function listSkills(skillsRoot) {
|
|
5635
|
-
if (!
|
|
8507
|
+
if (!existsSync27(skillsRoot))
|
|
5636
8508
|
return [];
|
|
5637
8509
|
const out = [];
|
|
5638
|
-
for (const name of
|
|
5639
|
-
const skillFile =
|
|
5640
|
-
if (
|
|
5641
|
-
out.push({ name, body:
|
|
8510
|
+
for (const name of readdirSync5(skillsRoot)) {
|
|
8511
|
+
const skillFile = join37(skillsRoot, name, "SKILL.md");
|
|
8512
|
+
if (existsSync27(skillFile) && statSync5(skillFile).isFile()) {
|
|
8513
|
+
out.push({ name, body: readFileSync26(skillFile, "utf-8") });
|
|
5642
8514
|
}
|
|
5643
8515
|
}
|
|
5644
8516
|
return out;
|
|
5645
8517
|
}
|
|
5646
8518
|
function resolveSkillsRoot(install, cwd) {
|
|
5647
8519
|
if (install === "global") {
|
|
5648
|
-
return
|
|
8520
|
+
return join37(homedir16(), ".claude", "skills");
|
|
5649
8521
|
}
|
|
5650
|
-
return
|
|
8522
|
+
return join37(cwd, ".claude", "skills");
|
|
5651
8523
|
}
|
|
5652
8524
|
|
|
5653
8525
|
// dist/src/skillify/manifest.js
|
|
5654
|
-
import { existsSync as
|
|
5655
|
-
import { dirname as
|
|
8526
|
+
import { existsSync as existsSync28, lstatSync as lstatSync4, mkdirSync as mkdirSync19, readFileSync as readFileSync27, renameSync as renameSync10, unlinkSync as unlinkSync10, writeFileSync as writeFileSync21 } from "node:fs";
|
|
8527
|
+
import { dirname as dirname13, join as join38 } from "node:path";
|
|
5656
8528
|
function emptyManifest() {
|
|
5657
8529
|
return { version: 1, entries: [] };
|
|
5658
8530
|
}
|
|
5659
8531
|
function manifestPath() {
|
|
5660
|
-
return
|
|
8532
|
+
return join38(getStateDir(), "pulled.json");
|
|
5661
8533
|
}
|
|
5662
8534
|
function loadManifest(path = manifestPath()) {
|
|
5663
8535
|
migrateLegacyStateDir();
|
|
5664
|
-
if (!
|
|
8536
|
+
if (!existsSync28(path))
|
|
5665
8537
|
return emptyManifest();
|
|
5666
8538
|
let raw;
|
|
5667
8539
|
try {
|
|
5668
|
-
raw =
|
|
8540
|
+
raw = readFileSync27(path, "utf-8");
|
|
5669
8541
|
} catch {
|
|
5670
8542
|
return emptyManifest();
|
|
5671
8543
|
}
|
|
@@ -5712,10 +8584,10 @@ function loadManifest(path = manifestPath()) {
|
|
|
5712
8584
|
}
|
|
5713
8585
|
function saveManifest(m, path = manifestPath()) {
|
|
5714
8586
|
migrateLegacyStateDir();
|
|
5715
|
-
|
|
8587
|
+
mkdirSync19(dirname13(path), { recursive: true });
|
|
5716
8588
|
const tmp = `${path}.tmp`;
|
|
5717
|
-
|
|
5718
|
-
|
|
8589
|
+
writeFileSync21(tmp, JSON.stringify(m, null, 2) + "\n", { mode: 384 });
|
|
8590
|
+
renameSync10(tmp, path);
|
|
5719
8591
|
}
|
|
5720
8592
|
function recordPull(entry, path = manifestPath()) {
|
|
5721
8593
|
const m = loadManifest(path);
|
|
@@ -5740,14 +8612,14 @@ function unlinkSymlinks(paths) {
|
|
|
5740
8612
|
for (const path of paths) {
|
|
5741
8613
|
let st;
|
|
5742
8614
|
try {
|
|
5743
|
-
st =
|
|
8615
|
+
st = lstatSync4(path);
|
|
5744
8616
|
} catch {
|
|
5745
8617
|
continue;
|
|
5746
8618
|
}
|
|
5747
8619
|
if (!st.isSymbolicLink())
|
|
5748
8620
|
continue;
|
|
5749
8621
|
try {
|
|
5750
|
-
|
|
8622
|
+
unlinkSync10(path);
|
|
5751
8623
|
} catch {
|
|
5752
8624
|
}
|
|
5753
8625
|
}
|
|
@@ -5757,7 +8629,7 @@ function pruneOrphanedEntries(path = manifestPath()) {
|
|
|
5757
8629
|
const live = [];
|
|
5758
8630
|
let pruned = 0;
|
|
5759
8631
|
for (const e of m.entries) {
|
|
5760
|
-
if (
|
|
8632
|
+
if (existsSync28(join38(e.installRoot, e.dirName))) {
|
|
5761
8633
|
live.push(e);
|
|
5762
8634
|
continue;
|
|
5763
8635
|
}
|
|
@@ -5770,26 +8642,26 @@ function pruneOrphanedEntries(path = manifestPath()) {
|
|
|
5770
8642
|
}
|
|
5771
8643
|
|
|
5772
8644
|
// dist/src/skillify/agent-roots.js
|
|
5773
|
-
import { existsSync as
|
|
5774
|
-
import { homedir as
|
|
5775
|
-
import { join as
|
|
8645
|
+
import { existsSync as existsSync29 } from "node:fs";
|
|
8646
|
+
import { homedir as homedir17 } from "node:os";
|
|
8647
|
+
import { join as join39 } from "node:path";
|
|
5776
8648
|
function resolveDetected(home) {
|
|
5777
8649
|
const out = [];
|
|
5778
|
-
const codexInstalled =
|
|
5779
|
-
const piInstalled =
|
|
5780
|
-
const hermesInstalled =
|
|
8650
|
+
const codexInstalled = existsSync29(join39(home, ".codex"));
|
|
8651
|
+
const piInstalled = existsSync29(join39(home, ".pi", "agent"));
|
|
8652
|
+
const hermesInstalled = existsSync29(join39(home, ".hermes"));
|
|
5781
8653
|
if (codexInstalled || piInstalled) {
|
|
5782
|
-
out.push(
|
|
8654
|
+
out.push(join39(home, ".agents", "skills"));
|
|
5783
8655
|
}
|
|
5784
8656
|
if (hermesInstalled) {
|
|
5785
|
-
out.push(
|
|
8657
|
+
out.push(join39(home, ".hermes", "skills"));
|
|
5786
8658
|
}
|
|
5787
8659
|
if (piInstalled) {
|
|
5788
|
-
out.push(
|
|
8660
|
+
out.push(join39(home, ".pi", "agent", "skills"));
|
|
5789
8661
|
}
|
|
5790
8662
|
return out;
|
|
5791
8663
|
}
|
|
5792
|
-
function detectAgentSkillsRoots(canonicalRoot, home =
|
|
8664
|
+
function detectAgentSkillsRoots(canonicalRoot, home = homedir17()) {
|
|
5793
8665
|
return resolveDetected(home).filter((p) => p !== canonicalRoot);
|
|
5794
8666
|
}
|
|
5795
8667
|
|
|
@@ -5833,18 +8705,18 @@ function isMissingTableError(message) {
|
|
|
5833
8705
|
}
|
|
5834
8706
|
function resolvePullDestination(install, cwd) {
|
|
5835
8707
|
if (install === "global")
|
|
5836
|
-
return
|
|
8708
|
+
return join40(homedir18(), ".claude", "skills");
|
|
5837
8709
|
if (!cwd)
|
|
5838
8710
|
throw new Error("install=project requires a cwd");
|
|
5839
|
-
return
|
|
8711
|
+
return join40(cwd, ".claude", "skills");
|
|
5840
8712
|
}
|
|
5841
8713
|
function fanOutSymlinks(canonicalDir, dirName, agentRoots) {
|
|
5842
8714
|
const out = [];
|
|
5843
8715
|
for (const root of agentRoots) {
|
|
5844
|
-
const link =
|
|
8716
|
+
const link = join40(root, dirName);
|
|
5845
8717
|
let existing;
|
|
5846
8718
|
try {
|
|
5847
|
-
existing =
|
|
8719
|
+
existing = lstatSync5(link);
|
|
5848
8720
|
} catch {
|
|
5849
8721
|
existing = null;
|
|
5850
8722
|
}
|
|
@@ -5863,13 +8735,13 @@ function fanOutSymlinks(canonicalDir, dirName, agentRoots) {
|
|
|
5863
8735
|
continue;
|
|
5864
8736
|
}
|
|
5865
8737
|
try {
|
|
5866
|
-
|
|
8738
|
+
unlinkSync11(link);
|
|
5867
8739
|
} catch {
|
|
5868
8740
|
continue;
|
|
5869
8741
|
}
|
|
5870
8742
|
}
|
|
5871
8743
|
try {
|
|
5872
|
-
|
|
8744
|
+
mkdirSync20(dirname14(link), { recursive: true });
|
|
5873
8745
|
symlinkSync2(canonicalDir, link, "dir");
|
|
5874
8746
|
out.push(link);
|
|
5875
8747
|
} catch {
|
|
@@ -5884,8 +8756,8 @@ function backfillSymlinks(installRoot) {
|
|
|
5884
8756
|
return;
|
|
5885
8757
|
const detected = detectAgentSkillsRoots(installRoot);
|
|
5886
8758
|
for (const entry of entries) {
|
|
5887
|
-
const canonical =
|
|
5888
|
-
if (!
|
|
8759
|
+
const canonical = join40(entry.installRoot, entry.dirName);
|
|
8760
|
+
if (!existsSync30(canonical))
|
|
5889
8761
|
continue;
|
|
5890
8762
|
const fresh = fanOutSymlinks(canonical, entry.dirName, detected);
|
|
5891
8763
|
if (sameSorted(fresh, entry.symlinks))
|
|
@@ -5995,10 +8867,10 @@ function renderFrontmatter2(fm) {
|
|
|
5995
8867
|
return lines.join("\n");
|
|
5996
8868
|
}
|
|
5997
8869
|
function readLocalVersion(path) {
|
|
5998
|
-
if (!
|
|
8870
|
+
if (!existsSync30(path))
|
|
5999
8871
|
return null;
|
|
6000
8872
|
try {
|
|
6001
|
-
const text =
|
|
8873
|
+
const text = readFileSync28(path, "utf-8");
|
|
6002
8874
|
const parsed = parseFrontmatter(text);
|
|
6003
8875
|
if (!parsed)
|
|
6004
8876
|
return null;
|
|
@@ -6093,8 +8965,8 @@ async function runPull(opts) {
|
|
|
6093
8965
|
summary.skipped++;
|
|
6094
8966
|
continue;
|
|
6095
8967
|
}
|
|
6096
|
-
const skillDir2 =
|
|
6097
|
-
const skillFile =
|
|
8968
|
+
const skillDir2 = join40(root, dirName);
|
|
8969
|
+
const skillFile = join40(skillDir2, "SKILL.md");
|
|
6098
8970
|
const remoteVersion = Number(row.version ?? 1);
|
|
6099
8971
|
const localVersion = readLocalVersion(skillFile);
|
|
6100
8972
|
const action = decideAction({
|
|
@@ -6105,14 +8977,14 @@ async function runPull(opts) {
|
|
|
6105
8977
|
});
|
|
6106
8978
|
let manifestError;
|
|
6107
8979
|
if (action === "wrote") {
|
|
6108
|
-
|
|
6109
|
-
if (
|
|
8980
|
+
mkdirSync20(skillDir2, { recursive: true });
|
|
8981
|
+
if (existsSync30(skillFile)) {
|
|
6110
8982
|
try {
|
|
6111
|
-
|
|
8983
|
+
renameSync11(skillFile, `${skillFile}.bak`);
|
|
6112
8984
|
} catch {
|
|
6113
8985
|
}
|
|
6114
8986
|
}
|
|
6115
|
-
|
|
8987
|
+
writeFileSync22(skillFile, renderSkillFile(row));
|
|
6116
8988
|
const symlinks = opts.install === "global" ? fanOutSymlinks(skillDir2, dirName, detectAgentSkillsRoots(root)) : [];
|
|
6117
8989
|
try {
|
|
6118
8990
|
recordPull({
|
|
@@ -6154,15 +9026,15 @@ async function runPull(opts) {
|
|
|
6154
9026
|
}
|
|
6155
9027
|
|
|
6156
9028
|
// dist/src/skillify/unpull.js
|
|
6157
|
-
import { existsSync as
|
|
6158
|
-
import { homedir as
|
|
6159
|
-
import { join as
|
|
9029
|
+
import { existsSync as existsSync31, readdirSync as readdirSync6, rmSync as rmSync5, statSync as statSync6 } from "node:fs";
|
|
9030
|
+
import { homedir as homedir19 } from "node:os";
|
|
9031
|
+
import { join as join41 } from "node:path";
|
|
6160
9032
|
function resolveUnpullRoot(install, cwd) {
|
|
6161
9033
|
if (install === "global")
|
|
6162
|
-
return
|
|
9034
|
+
return join41(homedir19(), ".claude", "skills");
|
|
6163
9035
|
if (!cwd)
|
|
6164
9036
|
throw new Error("cwd required when install === 'project'");
|
|
6165
|
-
return
|
|
9037
|
+
return join41(cwd, ".claude", "skills");
|
|
6166
9038
|
}
|
|
6167
9039
|
function runUnpull(opts) {
|
|
6168
9040
|
const root = resolveUnpullRoot(opts.install, opts.cwd);
|
|
@@ -6185,8 +9057,8 @@ function runUnpull(opts) {
|
|
|
6185
9057
|
const entries = entriesForRoot(manifest, opts.install, root);
|
|
6186
9058
|
for (const entry of entries) {
|
|
6187
9059
|
summary.scanned++;
|
|
6188
|
-
const path =
|
|
6189
|
-
if (!
|
|
9060
|
+
const path = join41(root, entry.dirName);
|
|
9061
|
+
if (!existsSync31(path)) {
|
|
6190
9062
|
if (!opts.dryRun) {
|
|
6191
9063
|
unlinkSymlinks(entry.symlinks);
|
|
6192
9064
|
removePullEntry(opts.install, entry.installRoot, entry.dirName);
|
|
@@ -6239,15 +9111,15 @@ function runUnpull(opts) {
|
|
|
6239
9111
|
}
|
|
6240
9112
|
summary.entries.push(result);
|
|
6241
9113
|
}
|
|
6242
|
-
if (
|
|
9114
|
+
if (existsSync31(root) && (opts.all || opts.legacyCleanup)) {
|
|
6243
9115
|
const manifestDirNames = new Set(entries.map((e) => e.dirName));
|
|
6244
|
-
for (const dirName of
|
|
9116
|
+
for (const dirName of readdirSync6(root)) {
|
|
6245
9117
|
if (manifestDirNames.has(dirName))
|
|
6246
9118
|
continue;
|
|
6247
|
-
const path =
|
|
9119
|
+
const path = join41(root, dirName);
|
|
6248
9120
|
let st;
|
|
6249
9121
|
try {
|
|
6250
|
-
st =
|
|
9122
|
+
st = statSync6(path);
|
|
6251
9123
|
} catch {
|
|
6252
9124
|
continue;
|
|
6253
9125
|
}
|
|
@@ -6322,31 +9194,31 @@ function decideTargetForManifestEntry(entry, opts, userFilter, haveUserFilter) {
|
|
|
6322
9194
|
}
|
|
6323
9195
|
|
|
6324
9196
|
// dist/src/commands/mine-local.js
|
|
6325
|
-
import { spawn } from "node:child_process";
|
|
6326
|
-
import { existsSync as
|
|
6327
|
-
import { homedir as
|
|
6328
|
-
import { basename, dirname as
|
|
9197
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
9198
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync22, readFileSync as readFileSync31, writeFileSync as writeFileSync24 } from "node:fs";
|
|
9199
|
+
import { homedir as homedir23 } from "node:os";
|
|
9200
|
+
import { basename as basename2, dirname as dirname16, join as join45 } from "node:path";
|
|
6329
9201
|
|
|
6330
9202
|
// dist/src/skillify/local-source.js
|
|
6331
|
-
import { readdirSync as
|
|
6332
|
-
import { homedir as
|
|
6333
|
-
import { join as
|
|
6334
|
-
var HOME2 =
|
|
9203
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync29, existsSync as existsSync32, statSync as statSync7 } from "node:fs";
|
|
9204
|
+
import { homedir as homedir20 } from "node:os";
|
|
9205
|
+
import { join as join42 } from "node:path";
|
|
9206
|
+
var HOME2 = homedir20();
|
|
6335
9207
|
function encodeCwdClaudeCode(cwd) {
|
|
6336
9208
|
return cwd.replace(/[/_]/g, "-");
|
|
6337
9209
|
}
|
|
6338
9210
|
function detectInstalledAgents() {
|
|
6339
9211
|
const installs = [];
|
|
6340
|
-
const claudeRoot =
|
|
6341
|
-
if (
|
|
9212
|
+
const claudeRoot = join42(HOME2, ".claude", "projects");
|
|
9213
|
+
if (existsSync32(claudeRoot)) {
|
|
6342
9214
|
installs.push({
|
|
6343
9215
|
agent: "claude_code",
|
|
6344
9216
|
sessionRoot: claudeRoot,
|
|
6345
9217
|
encodeCwd: encodeCwdClaudeCode
|
|
6346
9218
|
});
|
|
6347
9219
|
}
|
|
6348
|
-
const codexRoot =
|
|
6349
|
-
if (
|
|
9220
|
+
const codexRoot = join42(HOME2, ".codex", "sessions");
|
|
9221
|
+
if (existsSync32(codexRoot)) {
|
|
6350
9222
|
installs.push({
|
|
6351
9223
|
agent: "codex",
|
|
6352
9224
|
sessionRoot: codexRoot,
|
|
@@ -6368,14 +9240,14 @@ function listLocalSessions(installs, cwd) {
|
|
|
6368
9240
|
const cwdEncoded = install.encodeCwd(cwd);
|
|
6369
9241
|
let subdirs = [];
|
|
6370
9242
|
try {
|
|
6371
|
-
subdirs =
|
|
9243
|
+
subdirs = readdirSync7(install.sessionRoot);
|
|
6372
9244
|
} catch {
|
|
6373
9245
|
continue;
|
|
6374
9246
|
}
|
|
6375
9247
|
for (const sub of subdirs) {
|
|
6376
|
-
const subdirPath =
|
|
9248
|
+
const subdirPath = join42(install.sessionRoot, sub);
|
|
6377
9249
|
try {
|
|
6378
|
-
if (!
|
|
9250
|
+
if (!statSync7(subdirPath).isDirectory())
|
|
6379
9251
|
continue;
|
|
6380
9252
|
} catch {
|
|
6381
9253
|
continue;
|
|
@@ -6383,17 +9255,17 @@ function listLocalSessions(installs, cwd) {
|
|
|
6383
9255
|
const inCwd = sub === cwdEncoded;
|
|
6384
9256
|
let files = [];
|
|
6385
9257
|
try {
|
|
6386
|
-
files =
|
|
9258
|
+
files = readdirSync7(subdirPath);
|
|
6387
9259
|
} catch {
|
|
6388
9260
|
continue;
|
|
6389
9261
|
}
|
|
6390
9262
|
for (const f of files) {
|
|
6391
9263
|
if (!f.endsWith(".jsonl"))
|
|
6392
9264
|
continue;
|
|
6393
|
-
const fullPath =
|
|
9265
|
+
const fullPath = join42(subdirPath, f);
|
|
6394
9266
|
let stats;
|
|
6395
9267
|
try {
|
|
6396
|
-
stats =
|
|
9268
|
+
stats = statSync7(fullPath);
|
|
6397
9269
|
} catch {
|
|
6398
9270
|
continue;
|
|
6399
9271
|
}
|
|
@@ -6451,7 +9323,7 @@ function pickSessions(candidates, opts) {
|
|
|
6451
9323
|
function nativeJsonlToRows(filePath, sessionId, agent) {
|
|
6452
9324
|
let raw;
|
|
6453
9325
|
try {
|
|
6454
|
-
raw =
|
|
9326
|
+
raw = readFileSync29(filePath, "utf-8");
|
|
6455
9327
|
} catch {
|
|
6456
9328
|
return [];
|
|
6457
9329
|
}
|
|
@@ -6541,22 +9413,22 @@ function extractPairs(rows) {
|
|
|
6541
9413
|
}
|
|
6542
9414
|
|
|
6543
9415
|
// dist/src/skillify/gate-runner.js
|
|
6544
|
-
import { existsSync as
|
|
9416
|
+
import { existsSync as existsSync33 } from "node:fs";
|
|
6545
9417
|
import { createRequire } from "node:module";
|
|
6546
|
-
import { homedir as
|
|
6547
|
-
import { join as
|
|
9418
|
+
import { homedir as homedir21 } from "node:os";
|
|
9419
|
+
import { join as join43 } from "node:path";
|
|
6548
9420
|
var requireForCp = createRequire(import.meta.url);
|
|
6549
9421
|
var { execFileSync: runChildProcess } = requireForCp("node:child_process");
|
|
6550
9422
|
var inheritedEnv = process;
|
|
6551
9423
|
function firstExistingPath(candidates) {
|
|
6552
9424
|
for (const c of candidates) {
|
|
6553
|
-
if (
|
|
9425
|
+
if (existsSync33(c))
|
|
6554
9426
|
return c;
|
|
6555
9427
|
}
|
|
6556
9428
|
return null;
|
|
6557
9429
|
}
|
|
6558
9430
|
function findAgentBin(agent) {
|
|
6559
|
-
const home =
|
|
9431
|
+
const home = homedir21();
|
|
6560
9432
|
switch (agent) {
|
|
6561
9433
|
// /usr/bin/<name> is included in every candidate list — that's the
|
|
6562
9434
|
// common Linux package-manager install path (apt, dnf, pacman). Old
|
|
@@ -6565,45 +9437,45 @@ function findAgentBin(agent) {
|
|
|
6565
9437
|
// #170 caught the gap.
|
|
6566
9438
|
case "claude_code":
|
|
6567
9439
|
return firstExistingPath([
|
|
6568
|
-
|
|
9440
|
+
join43(home, ".claude", "local", "claude"),
|
|
6569
9441
|
"/usr/local/bin/claude",
|
|
6570
9442
|
"/usr/bin/claude",
|
|
6571
|
-
|
|
6572
|
-
|
|
9443
|
+
join43(home, ".npm-global", "bin", "claude"),
|
|
9444
|
+
join43(home, ".local", "bin", "claude"),
|
|
6573
9445
|
"/opt/homebrew/bin/claude"
|
|
6574
|
-
]) ??
|
|
9446
|
+
]) ?? join43(home, ".claude", "local", "claude");
|
|
6575
9447
|
case "codex":
|
|
6576
9448
|
return firstExistingPath([
|
|
6577
9449
|
"/usr/local/bin/codex",
|
|
6578
9450
|
"/usr/bin/codex",
|
|
6579
|
-
|
|
6580
|
-
|
|
9451
|
+
join43(home, ".npm-global", "bin", "codex"),
|
|
9452
|
+
join43(home, ".local", "bin", "codex"),
|
|
6581
9453
|
"/opt/homebrew/bin/codex"
|
|
6582
9454
|
]) ?? "/usr/local/bin/codex";
|
|
6583
9455
|
case "cursor":
|
|
6584
9456
|
return firstExistingPath([
|
|
6585
9457
|
"/usr/local/bin/cursor-agent",
|
|
6586
9458
|
"/usr/bin/cursor-agent",
|
|
6587
|
-
|
|
6588
|
-
|
|
9459
|
+
join43(home, ".npm-global", "bin", "cursor-agent"),
|
|
9460
|
+
join43(home, ".local", "bin", "cursor-agent"),
|
|
6589
9461
|
"/opt/homebrew/bin/cursor-agent"
|
|
6590
9462
|
]) ?? "/usr/local/bin/cursor-agent";
|
|
6591
9463
|
case "hermes":
|
|
6592
9464
|
return firstExistingPath([
|
|
6593
|
-
|
|
9465
|
+
join43(home, ".local", "bin", "hermes"),
|
|
6594
9466
|
"/usr/local/bin/hermes",
|
|
6595
9467
|
"/usr/bin/hermes",
|
|
6596
|
-
|
|
9468
|
+
join43(home, ".npm-global", "bin", "hermes"),
|
|
6597
9469
|
"/opt/homebrew/bin/hermes"
|
|
6598
|
-
]) ??
|
|
9470
|
+
]) ?? join43(home, ".local", "bin", "hermes");
|
|
6599
9471
|
case "pi":
|
|
6600
9472
|
return firstExistingPath([
|
|
6601
|
-
|
|
9473
|
+
join43(home, ".local", "bin", "pi"),
|
|
6602
9474
|
"/usr/local/bin/pi",
|
|
6603
9475
|
"/usr/bin/pi",
|
|
6604
|
-
|
|
9476
|
+
join43(home, ".npm-global", "bin", "pi"),
|
|
6605
9477
|
"/opt/homebrew/bin/pi"
|
|
6606
|
-
]) ??
|
|
9478
|
+
]) ?? join43(home, ".local", "bin", "pi");
|
|
6607
9479
|
}
|
|
6608
9480
|
}
|
|
6609
9481
|
|
|
@@ -6633,28 +9505,28 @@ function extractJsonBlock(s) {
|
|
|
6633
9505
|
}
|
|
6634
9506
|
|
|
6635
9507
|
// dist/src/skillify/local-manifest.js
|
|
6636
|
-
import { existsSync as
|
|
6637
|
-
import { homedir as
|
|
6638
|
-
import { dirname as
|
|
6639
|
-
var LOCAL_MANIFEST_PATH =
|
|
6640
|
-
var LOCAL_MINE_LOCK_PATH =
|
|
9508
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync21, readFileSync as readFileSync30, writeFileSync as writeFileSync23 } from "node:fs";
|
|
9509
|
+
import { homedir as homedir22 } from "node:os";
|
|
9510
|
+
import { dirname as dirname15, join as join44 } from "node:path";
|
|
9511
|
+
var LOCAL_MANIFEST_PATH = join44(homedir22(), ".claude", "hivemind", "local-mined.json");
|
|
9512
|
+
var LOCAL_MINE_LOCK_PATH = join44(homedir22(), ".claude", "hivemind", "local-mined.lock");
|
|
6641
9513
|
function readLocalManifest(path = LOCAL_MANIFEST_PATH) {
|
|
6642
|
-
if (!
|
|
9514
|
+
if (!existsSync34(path))
|
|
6643
9515
|
return null;
|
|
6644
9516
|
try {
|
|
6645
|
-
return JSON.parse(
|
|
9517
|
+
return JSON.parse(readFileSync30(path, "utf-8"));
|
|
6646
9518
|
} catch {
|
|
6647
9519
|
return null;
|
|
6648
9520
|
}
|
|
6649
9521
|
}
|
|
6650
9522
|
function writeLocalManifest(m, path = LOCAL_MANIFEST_PATH) {
|
|
6651
|
-
|
|
6652
|
-
|
|
9523
|
+
mkdirSync21(dirname15(path), { recursive: true });
|
|
9524
|
+
writeFileSync23(path, JSON.stringify(m, null, 2));
|
|
6653
9525
|
}
|
|
6654
9526
|
var LATEST_RUN_WINDOW_MS = 5 * 60 * 1e3;
|
|
6655
9527
|
|
|
6656
9528
|
// dist/src/commands/mine-local.js
|
|
6657
|
-
import { unlinkSync as
|
|
9529
|
+
import { unlinkSync as unlinkSync12 } from "node:fs";
|
|
6658
9530
|
var EPSILON = 0.3;
|
|
6659
9531
|
var DEFAULT_N = 8;
|
|
6660
9532
|
var PAIR_CHAR_CAP = 4e3;
|
|
@@ -6665,9 +9537,9 @@ var IN_FLIGHT_MAX_AGE_MS = 6e4;
|
|
|
6665
9537
|
var GATE_TIMEOUT_MS = 24e4;
|
|
6666
9538
|
var MANIFEST_PATH = LOCAL_MANIFEST_PATH;
|
|
6667
9539
|
function runGateViaStdin(opts) {
|
|
6668
|
-
return new Promise((
|
|
9540
|
+
return new Promise((resolve6) => {
|
|
6669
9541
|
if (opts.agent !== "claude_code") {
|
|
6670
|
-
|
|
9542
|
+
resolve6({
|
|
6671
9543
|
stdout: "",
|
|
6672
9544
|
stderr: "",
|
|
6673
9545
|
errored: true,
|
|
@@ -6675,8 +9547,8 @@ function runGateViaStdin(opts) {
|
|
|
6675
9547
|
});
|
|
6676
9548
|
return;
|
|
6677
9549
|
}
|
|
6678
|
-
if (!
|
|
6679
|
-
|
|
9550
|
+
if (!existsSync35(opts.bin)) {
|
|
9551
|
+
resolve6({
|
|
6680
9552
|
stdout: "",
|
|
6681
9553
|
stderr: "",
|
|
6682
9554
|
errored: true,
|
|
@@ -6692,7 +9564,7 @@ function runGateViaStdin(opts) {
|
|
|
6692
9564
|
"--permission-mode",
|
|
6693
9565
|
"bypassPermissions"
|
|
6694
9566
|
];
|
|
6695
|
-
const child =
|
|
9567
|
+
const child = spawn2(opts.bin, args, {
|
|
6696
9568
|
stdio: ["pipe", "pipe", "pipe"],
|
|
6697
9569
|
env: { ...process.env, HIVEMIND_WIKI_WORKER: "1", HIVEMIND_CAPTURE: "false" }
|
|
6698
9570
|
});
|
|
@@ -6703,7 +9575,7 @@ function runGateViaStdin(opts) {
|
|
|
6703
9575
|
if (settled)
|
|
6704
9576
|
return;
|
|
6705
9577
|
settled = true;
|
|
6706
|
-
|
|
9578
|
+
resolve6(r);
|
|
6707
9579
|
};
|
|
6708
9580
|
const timer = setTimeout(() => {
|
|
6709
9581
|
try {
|
|
@@ -6982,7 +9854,7 @@ async function runMineLocal(args) {
|
|
|
6982
9854
|
return;
|
|
6983
9855
|
lockReleased = true;
|
|
6984
9856
|
try {
|
|
6985
|
-
|
|
9857
|
+
unlinkSync12(LOCAL_MINE_LOCK_PATH);
|
|
6986
9858
|
} catch {
|
|
6987
9859
|
}
|
|
6988
9860
|
};
|
|
@@ -7039,8 +9911,8 @@ async function runMineLocalImpl(args) {
|
|
|
7039
9911
|
console.log(`Dry-run: would invoke ${gateAgent} gate on ${picked.length} session(s) in parallel (concurrency=${GATE_CONCURRENCY}).`);
|
|
7040
9912
|
return;
|
|
7041
9913
|
}
|
|
7042
|
-
const tmpDir =
|
|
7043
|
-
|
|
9914
|
+
const tmpDir = join45(homedir23(), ".claude", "hivemind", `mine-local-${Date.now()}`);
|
|
9915
|
+
mkdirSync22(tmpDir, { recursive: true });
|
|
7044
9916
|
console.log(`Running ${picked.length} gate call(s) in parallel (concurrency=${GATE_CONCURRENCY}, timeout=${GATE_TIMEOUT_MS / 1e3}s each)...`);
|
|
7045
9917
|
const results = await parallelMap(picked, GATE_CONCURRENCY, async (s) => {
|
|
7046
9918
|
const shortId = s.sessionId.slice(0, 8);
|
|
@@ -7051,23 +9923,23 @@ async function runMineLocalImpl(args) {
|
|
|
7051
9923
|
return { session: s, skills: [], reason: "no pairs", error: null };
|
|
7052
9924
|
}
|
|
7053
9925
|
const tail = pairs2.slice(-PER_SESSION_PAIR_CAP);
|
|
7054
|
-
const sessionTmp =
|
|
7055
|
-
|
|
7056
|
-
const verdictPath =
|
|
9926
|
+
const sessionTmp = join45(tmpDir, `s-${shortId}`);
|
|
9927
|
+
mkdirSync22(sessionTmp, { recursive: true });
|
|
9928
|
+
const verdictPath = join45(sessionTmp, "verdict.json");
|
|
7057
9929
|
const prompt = buildSessionPrompt(tail, s, verdictPath);
|
|
7058
|
-
|
|
9930
|
+
writeFileSync24(join45(sessionTmp, "prompt.txt"), prompt);
|
|
7059
9931
|
const gate = await runGateViaStdin({ agent: gateAgent, bin: gateBin, prompt, timeoutMs: GATE_TIMEOUT_MS });
|
|
7060
9932
|
try {
|
|
7061
|
-
|
|
9933
|
+
writeFileSync24(join45(sessionTmp, "gate-stdout.txt"), gate.stdout);
|
|
7062
9934
|
if (gate.stderr)
|
|
7063
|
-
|
|
9935
|
+
writeFileSync24(join45(sessionTmp, "gate-stderr.txt"), gate.stderr);
|
|
7064
9936
|
} catch {
|
|
7065
9937
|
}
|
|
7066
9938
|
if (gate.errored) {
|
|
7067
9939
|
console.log(` [${shortId}] gate failed: ${gate.errorMessage}`);
|
|
7068
9940
|
return { session: s, skills: [], reason: null, error: gate.errorMessage ?? "gate failed" };
|
|
7069
9941
|
}
|
|
7070
|
-
const verdictText =
|
|
9942
|
+
const verdictText = existsSync35(verdictPath) ? readFileSync31(verdictPath, "utf-8") : gate.stdout;
|
|
7071
9943
|
const mv = parseMultiVerdict(verdictText);
|
|
7072
9944
|
if (!mv) {
|
|
7073
9945
|
console.log(` [${shortId}] unparseable verdict (kept at ${sessionTmp})`);
|
|
@@ -7119,8 +9991,8 @@ async function runMineLocalImpl(args) {
|
|
|
7119
9991
|
sourceSessions: [session.sessionId],
|
|
7120
9992
|
agent: gateAgent
|
|
7121
9993
|
});
|
|
7122
|
-
const canonicalDir =
|
|
7123
|
-
const symlinks = fanOutRoots.length > 0 ? fanOutSymlinks(canonicalDir,
|
|
9994
|
+
const canonicalDir = dirname16(result.path);
|
|
9995
|
+
const symlinks = fanOutRoots.length > 0 ? fanOutSymlinks(canonicalDir, basename2(canonicalDir), fanOutRoots) : [];
|
|
7124
9996
|
const symlinkSuffix = symlinks.length > 0 ? `, fan-out \u2192 ${symlinks.length} root(s)` : "";
|
|
7125
9997
|
console.log(` wrote ${skill.name} \u2190 session ${session.sessionId.slice(0, 8)} (${session.agent}${symlinkSuffix})`);
|
|
7126
9998
|
written.push({ skill, session, result, symlinks });
|
|
@@ -7296,11 +10168,11 @@ function showStatus() {
|
|
|
7296
10168
|
console.log(`team: ${cfg.team.length === 0 ? "(empty)" : cfg.team.join(", ")}`);
|
|
7297
10169
|
console.log(`install: ${cfg.install} (${cfg.install === "global" ? "~/.claude/skills/" : "<project>/.claude/skills/"})`);
|
|
7298
10170
|
const dir = stateDir();
|
|
7299
|
-
if (!
|
|
10171
|
+
if (!existsSync36(dir)) {
|
|
7300
10172
|
console.log(`state: (no projects tracked yet)`);
|
|
7301
10173
|
return;
|
|
7302
10174
|
}
|
|
7303
|
-
const files =
|
|
10175
|
+
const files = readdirSync8(dir).filter((f) => f.endsWith(".json") && f !== "config.json" && f !== "pulled.json" && f !== "autopull-last-run.json");
|
|
7304
10176
|
if (files.length === 0) {
|
|
7305
10177
|
console.log(`state: (no projects tracked yet)`);
|
|
7306
10178
|
return;
|
|
@@ -7308,7 +10180,7 @@ function showStatus() {
|
|
|
7308
10180
|
console.log(`state: ${files.length} project(s) tracked`);
|
|
7309
10181
|
for (const f of files) {
|
|
7310
10182
|
try {
|
|
7311
|
-
const s = JSON.parse(
|
|
10183
|
+
const s = JSON.parse(readFileSync32(join46(dir, f), "utf-8"));
|
|
7312
10184
|
const last = typeof s.updatedAt === "number" ? new Date(s.updatedAt).toISOString() : s.lastDate ?? "never";
|
|
7313
10185
|
const skills = Array.isArray(s.skillsGenerated) && s.skillsGenerated.length > 0 ? s.skillsGenerated.join(", ") : "none";
|
|
7314
10186
|
console.log(` - ${s.project} (counter=${s.counter}, last=${last}, skills=${skills})`);
|
|
@@ -7335,7 +10207,7 @@ function setInstall(loc) {
|
|
|
7335
10207
|
}
|
|
7336
10208
|
const cfg = loadScopeConfig();
|
|
7337
10209
|
saveScopeConfig({ ...cfg, install: loc });
|
|
7338
|
-
const path = loc === "global" ?
|
|
10210
|
+
const path = loc === "global" ? join46(homedir24(), ".claude", "skills") : "<cwd>/.claude/skills";
|
|
7339
10211
|
console.log(`Install location set to '${loc}'. New skills will be written to ${path}/<name>/SKILL.md.`);
|
|
7340
10212
|
}
|
|
7341
10213
|
function promoteSkill(name, cwd) {
|
|
@@ -7343,18 +10215,18 @@ function promoteSkill(name, cwd) {
|
|
|
7343
10215
|
console.error("Usage: hivemind skillify promote <skill-name>");
|
|
7344
10216
|
process.exit(1);
|
|
7345
10217
|
}
|
|
7346
|
-
const projectPath =
|
|
7347
|
-
const globalPath =
|
|
7348
|
-
if (!
|
|
10218
|
+
const projectPath = join46(cwd, ".claude", "skills", name);
|
|
10219
|
+
const globalPath = join46(homedir24(), ".claude", "skills", name);
|
|
10220
|
+
if (!existsSync36(join46(projectPath, "SKILL.md"))) {
|
|
7349
10221
|
console.error(`Skill '${name}' not found at ${projectPath}/SKILL.md`);
|
|
7350
10222
|
process.exit(1);
|
|
7351
10223
|
}
|
|
7352
|
-
if (
|
|
10224
|
+
if (existsSync36(join46(globalPath, "SKILL.md"))) {
|
|
7353
10225
|
console.error(`Skill '${name}' already exists at ${globalPath}/SKILL.md \u2014 refusing to overwrite. Remove it first or rename the project skill.`);
|
|
7354
10226
|
process.exit(1);
|
|
7355
10227
|
}
|
|
7356
|
-
|
|
7357
|
-
|
|
10228
|
+
mkdirSync23(dirname17(globalPath), { recursive: true });
|
|
10229
|
+
renameSync12(projectPath, globalPath);
|
|
7358
10230
|
console.log(`Promoted '${name}' from ${projectPath} \u2192 ${globalPath}.`);
|
|
7359
10231
|
}
|
|
7360
10232
|
function teamAdd(name) {
|
|
@@ -7460,7 +10332,7 @@ async function pullSkills(args) {
|
|
|
7460
10332
|
console.error(`pull failed: ${e?.message ?? e}`);
|
|
7461
10333
|
process.exit(1);
|
|
7462
10334
|
}
|
|
7463
|
-
const dest = toRaw === "global" ?
|
|
10335
|
+
const dest = toRaw === "global" ? join46(homedir24(), ".claude", "skills") : `${process.cwd()}/.claude/skills`;
|
|
7464
10336
|
const filterDesc = users.length === 0 ? "all users" : users.join(", ");
|
|
7465
10337
|
console.log(`Destination: ${dest}`);
|
|
7466
10338
|
console.log(`Filter: ${filterDesc}${skillName ? ` \xB7 skill='${skillName}'` : ""}${dryRun ? " \xB7 dry-run" : ""}${force ? " \xB7 force" : ""}`);
|
|
@@ -7510,7 +10382,7 @@ async function unpullSkills(args) {
|
|
|
7510
10382
|
all,
|
|
7511
10383
|
legacyCleanup
|
|
7512
10384
|
});
|
|
7513
|
-
const dest = toRaw === "global" ?
|
|
10385
|
+
const dest = toRaw === "global" ? join46(homedir24(), ".claude", "skills") : `${process.cwd()}/.claude/skills`;
|
|
7514
10386
|
const filterParts = [];
|
|
7515
10387
|
if (users.length > 0)
|
|
7516
10388
|
filterParts.push(`users=${users.join(",")}`);
|
|
@@ -7605,15 +10477,15 @@ if (process.argv[1] && process.argv[1].endsWith("skillify.js")) {
|
|
|
7605
10477
|
}
|
|
7606
10478
|
|
|
7607
10479
|
// dist/src/cli/update.js
|
|
7608
|
-
import { execFileSync as
|
|
7609
|
-
import { closeSync as
|
|
7610
|
-
import { homedir as
|
|
7611
|
-
import { dirname as
|
|
10480
|
+
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
10481
|
+
import { closeSync as closeSync3, existsSync as existsSync37, mkdirSync as mkdirSync24, openSync as openSync3, readFileSync as readFileSync34, realpathSync, unlinkSync as unlinkSync13, writeSync as writeSync2 } from "node:fs";
|
|
10482
|
+
import { homedir as homedir25 } from "node:os";
|
|
10483
|
+
import { dirname as dirname19, join as join48, sep as sep2 } from "node:path";
|
|
7612
10484
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
7613
10485
|
|
|
7614
10486
|
// dist/src/utils/version-check.js
|
|
7615
|
-
import { readFileSync as
|
|
7616
|
-
import { dirname as
|
|
10487
|
+
import { readFileSync as readFileSync33 } from "node:fs";
|
|
10488
|
+
import { dirname as dirname18, join as join47 } from "node:path";
|
|
7617
10489
|
function isNewer(latest, current) {
|
|
7618
10490
|
const parse = (v) => v.split(".").map(Number);
|
|
7619
10491
|
const [la, lb, lc] = parse(latest);
|
|
@@ -7625,7 +10497,7 @@ function isNewer(latest, current) {
|
|
|
7625
10497
|
var NPM_REGISTRY_URL = "https://registry.npmjs.org/@deeplake/hivemind/latest";
|
|
7626
10498
|
var PKG_NAME = "@deeplake/hivemind";
|
|
7627
10499
|
function defaultLockPath() {
|
|
7628
|
-
return
|
|
10500
|
+
return join48(homedir25(), ".deeplake", "hivemind-update.lock");
|
|
7629
10501
|
}
|
|
7630
10502
|
function detectInstallKind(argv1) {
|
|
7631
10503
|
const realArgv1 = (() => {
|
|
@@ -7635,36 +10507,36 @@ function detectInstallKind(argv1) {
|
|
|
7635
10507
|
return argv1 ?? process.argv[1] ?? fileURLToPath2(import.meta.url);
|
|
7636
10508
|
}
|
|
7637
10509
|
})();
|
|
7638
|
-
let dir =
|
|
10510
|
+
let dir = dirname19(realArgv1);
|
|
7639
10511
|
let installDir = null;
|
|
7640
10512
|
for (let i = 0; i < 10; i++) {
|
|
7641
|
-
const pkgPath = `${dir}${
|
|
10513
|
+
const pkgPath = `${dir}${sep2}package.json`;
|
|
7642
10514
|
try {
|
|
7643
|
-
const pkg = JSON.parse(
|
|
10515
|
+
const pkg = JSON.parse(readFileSync34(pkgPath, "utf-8"));
|
|
7644
10516
|
if (pkg.name === PKG_NAME || pkg.name === "hivemind") {
|
|
7645
10517
|
installDir = dir;
|
|
7646
10518
|
break;
|
|
7647
10519
|
}
|
|
7648
10520
|
} catch {
|
|
7649
10521
|
}
|
|
7650
|
-
const parent =
|
|
10522
|
+
const parent = dirname19(dir);
|
|
7651
10523
|
if (parent === dir)
|
|
7652
10524
|
break;
|
|
7653
10525
|
dir = parent;
|
|
7654
10526
|
}
|
|
7655
|
-
installDir ??=
|
|
7656
|
-
if (realArgv1.includes(`${
|
|
10527
|
+
installDir ??= dirname19(realArgv1);
|
|
10528
|
+
if (realArgv1.includes(`${sep2}_npx${sep2}`) || realArgv1.includes(`${sep2}.npx${sep2}`)) {
|
|
7657
10529
|
return { kind: "npx", installDir };
|
|
7658
10530
|
}
|
|
7659
|
-
if (realArgv1.includes(`${
|
|
10531
|
+
if (realArgv1.includes(`${sep2}node_modules${sep2}@deeplake${sep2}hivemind`) || realArgv1.includes(`${sep2}node_modules${sep2}hivemind`)) {
|
|
7660
10532
|
return { kind: "npm-global", installDir };
|
|
7661
10533
|
}
|
|
7662
10534
|
let gitDir = installDir;
|
|
7663
10535
|
for (let i = 0; i < 6; i++) {
|
|
7664
|
-
if (
|
|
10536
|
+
if (existsSync37(`${gitDir}${sep2}.git`)) {
|
|
7665
10537
|
return { kind: "local-dev", installDir };
|
|
7666
10538
|
}
|
|
7667
|
-
const parent =
|
|
10539
|
+
const parent = dirname19(gitDir);
|
|
7668
10540
|
if (parent === gitDir)
|
|
7669
10541
|
break;
|
|
7670
10542
|
gitDir = parent;
|
|
@@ -7683,13 +10555,13 @@ async function getLatestNpmVersion(timeoutMs = 5e3) {
|
|
|
7683
10555
|
}
|
|
7684
10556
|
}
|
|
7685
10557
|
var defaultSpawn = (cmd, args) => {
|
|
7686
|
-
|
|
10558
|
+
execFileSync6(cmd, args, { stdio: "inherit" });
|
|
7687
10559
|
};
|
|
7688
10560
|
function tryAcquireLock(path) {
|
|
7689
|
-
|
|
10561
|
+
mkdirSync24(dirname19(path), { recursive: true, mode: 448 });
|
|
7690
10562
|
const claim = () => {
|
|
7691
|
-
const fd =
|
|
7692
|
-
|
|
10563
|
+
const fd = openSync3(path, "wx", 384);
|
|
10564
|
+
writeSync2(fd, String(process.pid));
|
|
7693
10565
|
return fd;
|
|
7694
10566
|
};
|
|
7695
10567
|
try {
|
|
@@ -7700,7 +10572,7 @@ function tryAcquireLock(path) {
|
|
|
7700
10572
|
}
|
|
7701
10573
|
let holderPid = 0;
|
|
7702
10574
|
try {
|
|
7703
|
-
holderPid = Number(
|
|
10575
|
+
holderPid = Number(readFileSync34(path, "utf-8").trim()) || 0;
|
|
7704
10576
|
} catch {
|
|
7705
10577
|
try {
|
|
7706
10578
|
return claim();
|
|
@@ -7717,7 +10589,7 @@ function tryAcquireLock(path) {
|
|
|
7717
10589
|
}
|
|
7718
10590
|
}
|
|
7719
10591
|
try {
|
|
7720
|
-
|
|
10592
|
+
unlinkSync13(path);
|
|
7721
10593
|
} catch {
|
|
7722
10594
|
}
|
|
7723
10595
|
try {
|
|
@@ -7729,11 +10601,11 @@ function tryAcquireLock(path) {
|
|
|
7729
10601
|
}
|
|
7730
10602
|
function releaseLock(fd, path) {
|
|
7731
10603
|
try {
|
|
7732
|
-
|
|
10604
|
+
closeSync3(fd);
|
|
7733
10605
|
} catch {
|
|
7734
10606
|
}
|
|
7735
10607
|
try {
|
|
7736
|
-
|
|
10608
|
+
unlinkSync13(path);
|
|
7737
10609
|
} catch {
|
|
7738
10610
|
}
|
|
7739
10611
|
}
|
|
@@ -7751,7 +10623,7 @@ async function runUpdate(opts = {}) {
|
|
|
7751
10623
|
}
|
|
7752
10624
|
log(`Update available: ${current} \u2192 ${latest}`);
|
|
7753
10625
|
const detected = opts.installKindOverride ?? detectInstallKind();
|
|
7754
|
-
const
|
|
10626
|
+
const spawn3 = opts.spawn ?? defaultSpawn;
|
|
7755
10627
|
switch (detected.kind) {
|
|
7756
10628
|
case "npm-global": {
|
|
7757
10629
|
if (opts.dryRun) {
|
|
@@ -7766,7 +10638,7 @@ async function runUpdate(opts = {}) {
|
|
|
7766
10638
|
try {
|
|
7767
10639
|
log(`Upgrading via npm\u2026`);
|
|
7768
10640
|
try {
|
|
7769
|
-
|
|
10641
|
+
spawn3("npm", ["install", "-g", `${PKG_NAME}@latest`]);
|
|
7770
10642
|
} catch (e) {
|
|
7771
10643
|
warn(`npm install failed: ${e.message}`);
|
|
7772
10644
|
warn(`Try running it manually: npm install -g ${PKG_NAME}@latest`);
|
|
@@ -7775,7 +10647,7 @@ async function runUpdate(opts = {}) {
|
|
|
7775
10647
|
log(``);
|
|
7776
10648
|
log(`Refreshing agent bundles\u2026`);
|
|
7777
10649
|
try {
|
|
7778
|
-
|
|
10650
|
+
spawn3("hivemind", ["install", "--skip-auth"]);
|
|
7779
10651
|
} catch (e) {
|
|
7780
10652
|
warn(`Agent refresh failed: ${e.message}`);
|
|
7781
10653
|
warn(`Run manually: hivemind install`);
|
|
@@ -7839,7 +10711,7 @@ var AUTH_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
|
7839
10711
|
"autoupdate",
|
|
7840
10712
|
"sessions"
|
|
7841
10713
|
]);
|
|
7842
|
-
var
|
|
10714
|
+
var USAGE3 = `
|
|
7843
10715
|
hivemind \u2014 one brain for every agent on your team
|
|
7844
10716
|
|
|
7845
10717
|
Usage:
|
|
@@ -7869,6 +10741,18 @@ Usage:
|
|
|
7869
10741
|
Check npm for a newer @deeplake/hivemind, upgrade the CLI, and refresh
|
|
7870
10742
|
every detected agent bundle. Single command for all agents.
|
|
7871
10743
|
|
|
10744
|
+
hivemind dashboard [--cwd <path>] [--out <path>] [--no-open]
|
|
10745
|
+
[--serve] [--port <n>]
|
|
10746
|
+
Build a self-contained HTML dashboard for this repo. Combines
|
|
10747
|
+
KPI cards (tokens saved, skills created, memory recalls,
|
|
10748
|
+
sessions) with the codebase-graph visualization. Writes to
|
|
10749
|
+
~/.hivemind/dashboards/<repo-key>/index.html by default.
|
|
10750
|
+
--no-open skips the browser launch (headless / CI scenarios).
|
|
10751
|
+
--serve starts a loopback HTTP server at http://127.0.0.1:<port>
|
|
10752
|
+
(default 8123) so the dashboard is reachable via a URL \u2014 useful
|
|
10753
|
+
over SSH; VS Code / Cursor Remote-SSH auto-forwards the port
|
|
10754
|
+
and opens it in the integrated Simple Browser tab on click.
|
|
10755
|
+
|
|
7872
10756
|
Semantic search (embeddings):
|
|
7873
10757
|
hivemind embeddings install Download @huggingface/transformers
|
|
7874
10758
|
once (~600 MB) into a shared dir,
|
|
@@ -7897,6 +10781,22 @@ Semantic search (embeddings):
|
|
|
7897
10781
|
Add --with-embeddings to "hivemind install" (or "hivemind <agent> install")
|
|
7898
10782
|
to run "embeddings install" automatically after installing the agent(s).
|
|
7899
10783
|
|
|
10784
|
+
Codebase graph (per-repo AST snapshot + cloud sync):
|
|
10785
|
+
hivemind graph build [--cwd <path>] Walk TypeScript sources, extract
|
|
10786
|
+
AST nodes + edges, write a
|
|
10787
|
+
snapshot, and push to cloud.
|
|
10788
|
+
hivemind graph diff <sha1> <sha2> Diff two snapshots by commit.
|
|
10789
|
+
hivemind graph history [-n N] [--json] Show last N build entries.
|
|
10790
|
+
hivemind graph init [--force] Install a managed
|
|
10791
|
+
.git/hooks/post-commit hook
|
|
10792
|
+
that rebuilds on each commit.
|
|
10793
|
+
hivemind graph pull Download the freshest cloud
|
|
10794
|
+
snapshot for HEAD into local.
|
|
10795
|
+
hivemind graph uninstall Remove the managed post-commit
|
|
10796
|
+
hook.
|
|
10797
|
+
Agents query the local snapshot via the Deeplake mount at
|
|
10798
|
+
~/.deeplake/memory/graph/{index.md,find/<pattern>,show/<handle-or-pattern>}.
|
|
10799
|
+
|
|
7900
10800
|
Skill management (mine + share reusable Claude skills across the org):
|
|
7901
10801
|
${renderCliHelpBlock()}
|
|
7902
10802
|
|
|
@@ -8087,7 +10987,7 @@ async function main() {
|
|
|
8087
10987
|
const args = process.argv.slice(2);
|
|
8088
10988
|
const cmd = args[0];
|
|
8089
10989
|
if (!cmd || cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
8090
|
-
log(
|
|
10990
|
+
log(USAGE3);
|
|
8091
10991
|
return;
|
|
8092
10992
|
}
|
|
8093
10993
|
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
@@ -8121,6 +11021,14 @@ async function main() {
|
|
|
8121
11021
|
runSkillifyCommand(args.slice(1));
|
|
8122
11022
|
return;
|
|
8123
11023
|
}
|
|
11024
|
+
if (cmd === "graph") {
|
|
11025
|
+
await runGraphCommand(args.slice(1));
|
|
11026
|
+
return;
|
|
11027
|
+
}
|
|
11028
|
+
if (cmd === "dashboard") {
|
|
11029
|
+
const code = await runDashboardCommand(args.slice(1));
|
|
11030
|
+
process.exit(code);
|
|
11031
|
+
}
|
|
8124
11032
|
if (cmd === "embeddings") {
|
|
8125
11033
|
const sub = args[1];
|
|
8126
11034
|
if (sub === "install") {
|
|
@@ -8168,7 +11076,7 @@ async function main() {
|
|
|
8168
11076
|
return;
|
|
8169
11077
|
}
|
|
8170
11078
|
warn(`Unknown command: ${cmd}`);
|
|
8171
|
-
log(
|
|
11079
|
+
log(USAGE3);
|
|
8172
11080
|
process.exit(1);
|
|
8173
11081
|
}
|
|
8174
11082
|
main().catch((err) => {
|