@deeplake/hivemind 0.7.45 → 0.7.47

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.
Files changed (39) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +64 -0
  4. package/bundle/cli.js +22702 -7775
  5. package/codex/bundle/capture.js +228 -0
  6. package/codex/bundle/commands/auth-login.js +228 -0
  7. package/codex/bundle/graph-pull-worker.js +1370 -0
  8. package/codex/bundle/pre-tool-use.js +228 -0
  9. package/codex/bundle/session-start-setup.js +228 -0
  10. package/codex/bundle/session-start.js +255 -4
  11. package/codex/bundle/shell/deeplake-shell.js +1028 -28
  12. package/codex/bundle/skillify-worker.js +94 -3
  13. package/codex/bundle/stop.js +282 -50
  14. package/codex/skills/hivemind-goals/SKILL.md +157 -0
  15. package/cursor/bundle/capture.js +282 -50
  16. package/cursor/bundle/commands/auth-login.js +228 -0
  17. package/cursor/bundle/graph-pull-worker.js +1370 -0
  18. package/cursor/bundle/pre-tool-use.js +228 -0
  19. package/cursor/bundle/session-end.js +65 -44
  20. package/cursor/bundle/session-start.js +662 -6
  21. package/cursor/bundle/shell/deeplake-shell.js +1028 -28
  22. package/cursor/bundle/skillify-worker.js +94 -3
  23. package/hermes/bundle/capture.js +282 -50
  24. package/hermes/bundle/commands/auth-login.js +228 -0
  25. package/hermes/bundle/graph-pull-worker.js +1370 -0
  26. package/hermes/bundle/pre-tool-use.js +228 -0
  27. package/hermes/bundle/session-end.js +65 -44
  28. package/hermes/bundle/session-start.js +662 -6
  29. package/hermes/bundle/shell/deeplake-shell.js +1028 -28
  30. package/hermes/bundle/skillify-worker.js +94 -3
  31. package/mcp/bundle/server.js +228 -0
  32. package/openclaw/dist/chunks/config-FH6JYSJW.js +53 -0
  33. package/openclaw/dist/index.js +307 -2
  34. package/openclaw/dist/skillify-worker.js +94 -3
  35. package/openclaw/openclaw.plugin.json +4 -2
  36. package/openclaw/package.json +1 -1
  37. package/openclaw/skills/hivemind-goals/SKILL.md +30 -0
  38. package/package.json +4 -1
  39. package/openclaw/dist/chunks/config-XEK4MJJS.js +0 -36
@@ -97,6 +97,23 @@ function loadConfig() {
97
97
  tableName: process.env.HIVEMIND_TABLE ?? "memory",
98
98
  sessionsTableName: process.env.HIVEMIND_SESSIONS_TABLE ?? "sessions",
99
99
  skillsTableName: process.env.HIVEMIND_SKILLS_TABLE ?? "skills",
100
+ // Defaults match the table name written into the SQL — keep aligned
101
+ // with RULES_COLUMNS / TASKS_COLUMNS / TASK_EVENTS_COLUMNS in
102
+ // deeplake-schema.ts and with the e2e test-org override convention
103
+ // (memory_test / sessions_test → goals_test, etc.) documented in
104
+ // CLAUDE.md.
105
+ rulesTableName: process.env.HIVEMIND_RULES_TABLE ?? "hivemind_rules",
106
+ tasksTableName: process.env.HIVEMIND_TASKS_TABLE ?? "hivemind_tasks",
107
+ taskEventsTableName: process.env.HIVEMIND_TASK_EVENTS_TABLE ?? "hivemind_task_events",
108
+ // Goals + KPIs (refined design — VFS path classifier maps
109
+ // memory/goal/<user>/<status>/<uuid>.md → hivemind_goals row
110
+ // memory/kpi/<uuid>/<kpi_id>.md → hivemind_kpis row
111
+ // See src/shell/deeplake-fs.ts for the translation logic and
112
+ // GOALS_COLUMNS / KPIS_COLUMNS in deeplake-schema.ts for the
113
+ // table shape.
114
+ goalsTableName: process.env.HIVEMIND_GOALS_TABLE ?? "hivemind_goals",
115
+ kpisTableName: process.env.HIVEMIND_KPIS_TABLE ?? "hivemind_kpis",
116
+ codebaseTableName: process.env.HIVEMIND_CODEBASE_TABLE ?? "codebase",
100
117
  memoryPath: process.env.HIVEMIND_MEMORY_PATH ?? join(home, ".deeplake", "memory")
101
118
  };
102
119
  }
@@ -197,6 +214,65 @@ var SKILLS_COLUMNS = Object.freeze([
197
214
  { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
198
215
  { name: "updated_at", sql: "TEXT NOT NULL DEFAULT ''" }
199
216
  ]);
217
+ var RULES_COLUMNS = Object.freeze([
218
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
219
+ { name: "rule_id", sql: "TEXT NOT NULL DEFAULT ''" },
220
+ { name: "text", sql: "TEXT NOT NULL DEFAULT ''" },
221
+ { name: "scope", sql: "TEXT NOT NULL DEFAULT 'team'" },
222
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'active'" },
223
+ { name: "assigned_by", sql: "TEXT NOT NULL DEFAULT ''" },
224
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
225
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
226
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
227
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
228
+ ]);
229
+ var TASKS_COLUMNS = Object.freeze([
230
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
231
+ { name: "task_id", sql: "TEXT NOT NULL DEFAULT ''" },
232
+ { name: "text", sql: "TEXT NOT NULL DEFAULT ''" },
233
+ { name: "scope", sql: "TEXT NOT NULL DEFAULT 'me'" },
234
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'active'" },
235
+ { name: "assigned_to", sql: "TEXT NOT NULL DEFAULT ''" },
236
+ { name: "assigned_by", sql: "TEXT NOT NULL DEFAULT ''" },
237
+ { name: "kpis", sql: "JSONB" },
238
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
239
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
240
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
241
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
242
+ ]);
243
+ var TASK_EVENTS_COLUMNS = Object.freeze([
244
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
245
+ { name: "task_id", sql: "TEXT NOT NULL DEFAULT ''" },
246
+ { name: "task_version", sql: "BIGINT NOT NULL DEFAULT 1" },
247
+ { name: "kpi_id", sql: "TEXT NOT NULL DEFAULT ''" },
248
+ { name: "value", sql: "BIGINT NOT NULL DEFAULT 0" },
249
+ { name: "note", sql: "TEXT NOT NULL DEFAULT ''" },
250
+ { name: "source", sql: "TEXT NOT NULL DEFAULT 'user'" },
251
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT ''" },
252
+ { name: "ts", sql: "TEXT NOT NULL DEFAULT ''" },
253
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
254
+ ]);
255
+ var GOALS_COLUMNS = Object.freeze([
256
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
257
+ { name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
258
+ { name: "owner", sql: "TEXT NOT NULL DEFAULT ''" },
259
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'opened'" },
260
+ { name: "content", sql: "TEXT NOT NULL DEFAULT ''" },
261
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
262
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
263
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
264
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
265
+ ]);
266
+ var KPIS_COLUMNS = Object.freeze([
267
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
268
+ { name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
269
+ { name: "kpi_id", sql: "TEXT NOT NULL DEFAULT ''" },
270
+ { name: "content", sql: "TEXT NOT NULL DEFAULT ''" },
271
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
272
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
273
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
274
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
275
+ ]);
200
276
  function validateSchema(label, cols) {
201
277
  const seen = /* @__PURE__ */ new Set();
202
278
  for (const col of cols) {
@@ -214,9 +290,38 @@ function validateSchema(label, cols) {
214
290
  }
215
291
  }
216
292
  }
293
+ var CODEBASE_COLUMNS = Object.freeze([
294
+ // Identity key (matches the PK below)
295
+ { name: "org_id", sql: "TEXT NOT NULL DEFAULT ''" },
296
+ { name: "workspace_id", sql: "TEXT NOT NULL DEFAULT ''" },
297
+ { name: "repo_slug", sql: "TEXT NOT NULL DEFAULT ''" },
298
+ { name: "user_id", sql: "TEXT NOT NULL DEFAULT ''" },
299
+ { name: "worktree_id", sql: "TEXT NOT NULL DEFAULT ''" },
300
+ { name: "commit_sha", sql: "TEXT NOT NULL DEFAULT ''" },
301
+ // Observation metadata
302
+ { name: "parent_sha", sql: "TEXT NOT NULL DEFAULT ''" },
303
+ { name: "branch", sql: "TEXT NOT NULL DEFAULT ''" },
304
+ { name: "ts", sql: "TIMESTAMP" },
305
+ { name: "pushed_by", sql: "TEXT NOT NULL DEFAULT ''" },
306
+ // Snapshot payload
307
+ { name: "snapshot_sha256", sql: "TEXT NOT NULL DEFAULT ''" },
308
+ { name: "snapshot_jsonb", sql: "TEXT NOT NULL DEFAULT ''" },
309
+ { name: "node_count", sql: "BIGINT NOT NULL DEFAULT 0" },
310
+ { name: "edge_count", sql: "BIGINT NOT NULL DEFAULT 0" },
311
+ // Generator metadata (for drift diagnostics — what hivemind version produced this?)
312
+ { name: "generator", sql: "TEXT NOT NULL DEFAULT 'hivemind-graph'" },
313
+ { name: "generator_version", sql: "TEXT NOT NULL DEFAULT ''" },
314
+ { name: "schema_version", sql: "BIGINT NOT NULL DEFAULT 1" }
315
+ ]);
217
316
  validateSchema("MEMORY_COLUMNS", MEMORY_COLUMNS);
218
317
  validateSchema("SESSIONS_COLUMNS", SESSIONS_COLUMNS);
219
318
  validateSchema("SKILLS_COLUMNS", SKILLS_COLUMNS);
319
+ validateSchema("RULES_COLUMNS", RULES_COLUMNS);
320
+ validateSchema("TASKS_COLUMNS", TASKS_COLUMNS);
321
+ validateSchema("TASK_EVENTS_COLUMNS", TASK_EVENTS_COLUMNS);
322
+ validateSchema("GOALS_COLUMNS", GOALS_COLUMNS);
323
+ validateSchema("KPIS_COLUMNS", KPIS_COLUMNS);
324
+ validateSchema("CODEBASE_COLUMNS", CODEBASE_COLUMNS);
220
325
  function buildCreateTableSql(tableName, cols) {
221
326
  const safe = sqlIdent(tableName);
222
327
  const colSql = cols.map((c) => `${c.name} ${c.sql}`).join(", ");
@@ -781,6 +886,24 @@ var DeeplakeApi = class {
781
886
  * This sidesteps the Deeplake UPDATE-coalescing quirk that bit the wiki
782
887
  * worker.
783
888
  */
889
+ /**
890
+ * Create the codebase table. One row per (org, workspace, repo, user,
891
+ * worktree, commit) — see CODEBASE_COLUMNS for the schema. Healing
892
+ * + index follow the same pattern as ensureSessionsTable.
893
+ */
894
+ async ensureCodebaseTable(name) {
895
+ const safe = sqlIdent(name);
896
+ const tables = await this.listTables();
897
+ if (!tables.includes(safe)) {
898
+ log3(`table "${safe}" not found, creating`);
899
+ await this.createTableWithRetry(buildCreateTableSql(safe, CODEBASE_COLUMNS), safe);
900
+ log3(`table "${safe}" created`);
901
+ if (!tables.includes(safe))
902
+ this._tablesCache = [...tables, safe];
903
+ }
904
+ await this.healSchema(safe, CODEBASE_COLUMNS);
905
+ await this.ensureLookupIndex(safe, "codebase_identity", `("org_id", "workspace_id", "repo_slug", "user_id", "worktree_id", "commit_sha")`);
906
+ }
784
907
  async ensureSkillsTable(name) {
785
908
  const safe = sqlIdent(name);
786
909
  const tables = await this.listTables();
@@ -794,6 +917,111 @@ var DeeplakeApi = class {
794
917
  await this.healSchema(safe, SKILLS_COLUMNS);
795
918
  await this.ensureLookupIndex(safe, "project_key_name", `("project_key", "name")`);
796
919
  }
920
+ /**
921
+ * Create the rules table.
922
+ *
923
+ * One row per rule version (same write pattern as skills): edits INSERT
924
+ * a fresh row with version+1, reads pick latest per rule_id via
925
+ * `ORDER BY version DESC LIMIT 1`. Sidesteps the Deeplake
926
+ * UPDATE-coalescing quirk by never UPDATEing.
927
+ */
928
+ async ensureRulesTable(name) {
929
+ const safe = sqlIdent(name);
930
+ const tables = await this.listTables();
931
+ if (!tables.includes(safe)) {
932
+ log3(`table "${safe}" not found, creating`);
933
+ await this.createTableWithRetry(buildCreateTableSql(safe, RULES_COLUMNS), safe);
934
+ log3(`table "${safe}" created`);
935
+ if (!tables.includes(safe))
936
+ this._tablesCache = [...tables, safe];
937
+ }
938
+ await this.healSchema(safe, RULES_COLUMNS);
939
+ await this.ensureLookupIndex(safe, "rule_id_version", `("rule_id", "version")`);
940
+ }
941
+ /**
942
+ * Create the tasks table.
943
+ *
944
+ * Same write pattern as rules + skills. `kpis` is a nullable JSONB
945
+ * column with the agent's KPI metadata; KPI current values come from
946
+ * `task_events` (SUM(value)), not this snapshot.
947
+ */
948
+ async ensureTasksTable(name) {
949
+ const safe = sqlIdent(name);
950
+ const tables = await this.listTables();
951
+ if (!tables.includes(safe)) {
952
+ log3(`table "${safe}" not found, creating`);
953
+ await this.createTableWithRetry(buildCreateTableSql(safe, TASKS_COLUMNS), safe);
954
+ log3(`table "${safe}" created`);
955
+ if (!tables.includes(safe))
956
+ this._tablesCache = [...tables, safe];
957
+ }
958
+ await this.healSchema(safe, TASKS_COLUMNS);
959
+ await this.ensureLookupIndex(safe, "task_id_version", `("task_id", "version")`);
960
+ }
961
+ /**
962
+ * Create the task-events table.
963
+ *
964
+ * Append-only. Every INSERT is a fresh row; never UPDATE. KPI current
965
+ * value is `SUM(value) WHERE task_id=? AND kpi_id=?`. Index on
966
+ * (task_id, kpi_id) is the canonical aggregation key.
967
+ */
968
+ async ensureTaskEventsTable(name) {
969
+ const safe = sqlIdent(name);
970
+ const tables = await this.listTables();
971
+ if (!tables.includes(safe)) {
972
+ log3(`table "${safe}" not found, creating`);
973
+ await this.createTableWithRetry(buildCreateTableSql(safe, TASK_EVENTS_COLUMNS), safe);
974
+ log3(`table "${safe}" created`);
975
+ if (!tables.includes(safe))
976
+ this._tablesCache = [...tables, safe];
977
+ }
978
+ await this.healSchema(safe, TASK_EVENTS_COLUMNS);
979
+ await this.ensureLookupIndex(safe, "task_id_kpi_id", `("task_id", "kpi_id")`);
980
+ }
981
+ /**
982
+ * Create the goals table.
983
+ *
984
+ * Backed by the VFS path convention memory/goal/<owner>/<status>/<goal_id>.md.
985
+ * INSERT-only version-bumped: rm and mv operations translate to fresh
986
+ * v=N+1 rows (status flips for mv → closed; rm is the same soft-close).
987
+ * The (goal_id, version) index lets the VFS dispatch a cheap latest-row
988
+ * read on cat / Read of a single goal.
989
+ */
990
+ async ensureGoalsTable(name) {
991
+ const safe = sqlIdent(name);
992
+ const tables = await this.listTables();
993
+ if (!tables.includes(safe)) {
994
+ log3(`table "${safe}" not found, creating`);
995
+ await this.createTableWithRetry(buildCreateTableSql(safe, GOALS_COLUMNS), safe);
996
+ log3(`table "${safe}" created`);
997
+ if (!tables.includes(safe))
998
+ this._tablesCache = [...tables, safe];
999
+ }
1000
+ await this.healSchema(safe, GOALS_COLUMNS);
1001
+ await this.ensureLookupIndex(safe, "goal_id_version", `("goal_id", "version")`);
1002
+ await this.ensureLookupIndex(safe, "owner_status", `("owner", "status")`);
1003
+ }
1004
+ /**
1005
+ * Create the kpis table.
1006
+ *
1007
+ * Backed by memory/kpi/<goal_id>/<kpi_id>.md. KPI rows do NOT carry
1008
+ * owner — ownership derives from the parent goal via logical join on
1009
+ * goal_id. INSERT-only version-bumped. (goal_id, kpi_id) index is the
1010
+ * canonical lookup the VFS uses on Read and Write.
1011
+ */
1012
+ async ensureKpisTable(name) {
1013
+ const safe = sqlIdent(name);
1014
+ const tables = await this.listTables();
1015
+ if (!tables.includes(safe)) {
1016
+ log3(`table "${safe}" not found, creating`);
1017
+ await this.createTableWithRetry(buildCreateTableSql(safe, KPIS_COLUMNS), safe);
1018
+ log3(`table "${safe}" created`);
1019
+ if (!tables.includes(safe))
1020
+ this._tablesCache = [...tables, safe];
1021
+ }
1022
+ await this.healSchema(safe, KPIS_COLUMNS);
1023
+ await this.ensureLookupIndex(safe, "goal_id_kpi_id", `("goal_id", "kpi_id")`);
1024
+ }
797
1025
  };
798
1026
 
799
1027
  // dist/src/shell/grep-core.js
@@ -2,13 +2,13 @@
2
2
 
3
3
  // dist/src/utils/stdin.js
4
4
  function readStdin() {
5
- return new Promise((resolve, reject) => {
5
+ return new Promise((resolve2, reject) => {
6
6
  let data = "";
7
7
  process.stdin.setEncoding("utf-8");
8
8
  process.stdin.on("data", (chunk) => data += chunk);
9
9
  process.stdin.on("end", () => {
10
10
  try {
11
- resolve(JSON.parse(data));
11
+ resolve2(JSON.parse(data));
12
12
  } catch (err) {
13
13
  reject(new Error(`Failed to parse hook input: ${err}`));
14
14
  }
@@ -64,6 +64,23 @@ function loadConfig() {
64
64
  tableName: process.env.HIVEMIND_TABLE ?? "memory",
65
65
  sessionsTableName: process.env.HIVEMIND_SESSIONS_TABLE ?? "sessions",
66
66
  skillsTableName: process.env.HIVEMIND_SKILLS_TABLE ?? "skills",
67
+ // Defaults match the table name written into the SQL — keep aligned
68
+ // with RULES_COLUMNS / TASKS_COLUMNS / TASK_EVENTS_COLUMNS in
69
+ // deeplake-schema.ts and with the e2e test-org override convention
70
+ // (memory_test / sessions_test → goals_test, etc.) documented in
71
+ // CLAUDE.md.
72
+ rulesTableName: process.env.HIVEMIND_RULES_TABLE ?? "hivemind_rules",
73
+ tasksTableName: process.env.HIVEMIND_TASKS_TABLE ?? "hivemind_tasks",
74
+ taskEventsTableName: process.env.HIVEMIND_TASK_EVENTS_TABLE ?? "hivemind_task_events",
75
+ // Goals + KPIs (refined design — VFS path classifier maps
76
+ // memory/goal/<user>/<status>/<uuid>.md → hivemind_goals row
77
+ // memory/kpi/<uuid>/<kpi_id>.md → hivemind_kpis row
78
+ // See src/shell/deeplake-fs.ts for the translation logic and
79
+ // GOALS_COLUMNS / KPIS_COLUMNS in deeplake-schema.ts for the
80
+ // table shape.
81
+ goalsTableName: process.env.HIVEMIND_GOALS_TABLE ?? "hivemind_goals",
82
+ kpisTableName: process.env.HIVEMIND_KPIS_TABLE ?? "hivemind_kpis",
83
+ codebaseTableName: process.env.HIVEMIND_CODEBASE_TABLE ?? "codebase",
67
84
  memoryPath: process.env.HIVEMIND_MEMORY_PATH ?? join2(home, ".deeplake", "memory")
68
85
  };
69
86
  }
@@ -407,9 +424,54 @@ function spawnSkillifyWorker(opts) {
407
424
 
408
425
  // dist/src/skillify/state.js
409
426
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, writeSync as writeSync2, mkdirSync as mkdirSync5, renameSync as renameSync3, rmdirSync, existsSync as existsSync5, lstatSync, unlinkSync as unlinkSync2, openSync as openSync2, closeSync as closeSync2 } from "node:fs";
427
+ import { join as join11 } from "node:path";
428
+
429
+ // dist/src/utils/repo-identity.js
410
430
  import { execSync as execSync2 } from "node:child_process";
411
431
  import { createHash } from "node:crypto";
412
- import { join as join11, basename } from "node:path";
432
+ import { basename, resolve } from "node:path";
433
+ var DEFAULT_PORTS = {
434
+ http: "80",
435
+ https: "443",
436
+ ssh: "22",
437
+ git: "9418"
438
+ };
439
+ function normalizeGitRemoteUrl(url) {
440
+ let s = url.trim();
441
+ const schemeMatch = s.match(/^([a-z][a-z0-9+.-]*):\/\//i);
442
+ const scheme = schemeMatch ? schemeMatch[1].toLowerCase() : null;
443
+ if (schemeMatch)
444
+ s = s.slice(schemeMatch[0].length);
445
+ if (!scheme) {
446
+ const scp = s.match(/^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/);
447
+ if (scp)
448
+ s = `${scp[1]}/${scp[2]}`;
449
+ }
450
+ s = s.replace(/^[^@/]+@/, "");
451
+ if (scheme && DEFAULT_PORTS[scheme]) {
452
+ s = s.replace(new RegExp(`^([^/]+):${DEFAULT_PORTS[scheme]}(/|$)`), "$1$2");
453
+ }
454
+ s = s.replace(/\.git\/?$/i, "");
455
+ s = s.replace(/\/+$/, "");
456
+ return s.toLowerCase();
457
+ }
458
+ function deriveProjectKey(cwd) {
459
+ const absCwd = resolve(cwd);
460
+ const project = basename(absCwd) || "unknown";
461
+ let signature = null;
462
+ try {
463
+ const raw = execSync2("git config --get remote.origin.url", {
464
+ cwd: absCwd,
465
+ encoding: "utf-8",
466
+ stdio: ["ignore", "pipe", "ignore"]
467
+ }).trim();
468
+ signature = raw ? normalizeGitRemoteUrl(raw) : null;
469
+ } catch {
470
+ }
471
+ const input = signature ?? absCwd;
472
+ const key = createHash("sha1").update(input).digest("hex").slice(0, 16);
473
+ return { key, project };
474
+ }
413
475
 
414
476
  // dist/src/skillify/legacy-migration.js
415
477
  import { existsSync as existsSync4, renameSync as renameSync2 } from "node:fs";
@@ -464,47 +526,6 @@ function statePath(projectKey) {
464
526
  function lockPath2(projectKey) {
465
527
  return join11(getStateDir(), `${projectKey}.lock`);
466
528
  }
467
- var DEFAULT_PORTS = {
468
- http: "80",
469
- https: "443",
470
- ssh: "22",
471
- git: "9418"
472
- };
473
- function normalizeGitRemoteUrl(url) {
474
- let s = url.trim();
475
- const schemeMatch = s.match(/^([a-z][a-z0-9+.-]*):\/\//i);
476
- const scheme = schemeMatch ? schemeMatch[1].toLowerCase() : null;
477
- if (schemeMatch)
478
- s = s.slice(schemeMatch[0].length);
479
- if (!scheme) {
480
- const scp = s.match(/^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/);
481
- if (scp)
482
- s = `${scp[1]}/${scp[2]}`;
483
- }
484
- s = s.replace(/^[^@/]+@/, "");
485
- if (scheme && DEFAULT_PORTS[scheme]) {
486
- s = s.replace(new RegExp(`^([^/]+):${DEFAULT_PORTS[scheme]}(/|$)`), "$1$2");
487
- }
488
- s = s.replace(/\.git\/?$/i, "");
489
- s = s.replace(/\/+$/, "");
490
- return s.toLowerCase();
491
- }
492
- function deriveProjectKey(cwd) {
493
- const project = basename(cwd) || "unknown";
494
- let signature = null;
495
- try {
496
- const raw = execSync2("git config --get remote.origin.url", {
497
- cwd,
498
- encoding: "utf-8",
499
- stdio: ["ignore", "pipe", "ignore"]
500
- }).trim();
501
- signature = raw ? normalizeGitRemoteUrl(raw) : null;
502
- } catch {
503
- }
504
- const input = signature ?? cwd;
505
- const key = createHash("sha1").update(input).digest("hex").slice(0, 16);
506
- return { key, project };
507
- }
508
529
  function readState(projectKey) {
509
530
  migrateLegacyStateDir();
510
531
  const p = statePath(projectKey);