@deeplake/hivemind 0.7.46 → 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 (38) 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 +17230 -4177
  5. package/codex/bundle/capture.js +185 -0
  6. package/codex/bundle/commands/auth-login.js +185 -0
  7. package/codex/bundle/graph-pull-worker.js +185 -0
  8. package/codex/bundle/pre-tool-use.js +185 -0
  9. package/codex/bundle/session-start-setup.js +185 -0
  10. package/codex/bundle/session-start.js +185 -0
  11. package/codex/bundle/shell/deeplake-shell.js +451 -3
  12. package/codex/bundle/skillify-worker.js +64 -0
  13. package/codex/bundle/stop.js +185 -0
  14. package/codex/skills/hivemind-goals/SKILL.md +157 -0
  15. package/cursor/bundle/capture.js +185 -0
  16. package/cursor/bundle/commands/auth-login.js +185 -0
  17. package/cursor/bundle/graph-pull-worker.js +185 -0
  18. package/cursor/bundle/pre-tool-use.js +185 -0
  19. package/cursor/bundle/session-end.js +16 -0
  20. package/cursor/bundle/session-start.js +596 -6
  21. package/cursor/bundle/shell/deeplake-shell.js +451 -3
  22. package/cursor/bundle/skillify-worker.js +64 -0
  23. package/hermes/bundle/capture.js +185 -0
  24. package/hermes/bundle/commands/auth-login.js +185 -0
  25. package/hermes/bundle/graph-pull-worker.js +185 -0
  26. package/hermes/bundle/pre-tool-use.js +185 -0
  27. package/hermes/bundle/session-end.js +16 -0
  28. package/hermes/bundle/session-start.js +596 -6
  29. package/hermes/bundle/shell/deeplake-shell.js +451 -3
  30. package/hermes/bundle/skillify-worker.js +64 -0
  31. package/mcp/bundle/server.js +185 -0
  32. package/openclaw/dist/chunks/{config-O5PDJQ7Y.js → config-FH6JYSJW.js} +16 -0
  33. package/openclaw/dist/index.js +262 -2
  34. package/openclaw/dist/skillify-worker.js +64 -0
  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 +2 -1
@@ -66792,6 +66792,22 @@ function loadConfig() {
66792
66792
  tableName: process.env.HIVEMIND_TABLE ?? "memory",
66793
66793
  sessionsTableName: process.env.HIVEMIND_SESSIONS_TABLE ?? "sessions",
66794
66794
  skillsTableName: process.env.HIVEMIND_SKILLS_TABLE ?? "skills",
66795
+ // Defaults match the table name written into the SQL — keep aligned
66796
+ // with RULES_COLUMNS / TASKS_COLUMNS / TASK_EVENTS_COLUMNS in
66797
+ // deeplake-schema.ts and with the e2e test-org override convention
66798
+ // (memory_test / sessions_test → goals_test, etc.) documented in
66799
+ // CLAUDE.md.
66800
+ rulesTableName: process.env.HIVEMIND_RULES_TABLE ?? "hivemind_rules",
66801
+ tasksTableName: process.env.HIVEMIND_TASKS_TABLE ?? "hivemind_tasks",
66802
+ taskEventsTableName: process.env.HIVEMIND_TASK_EVENTS_TABLE ?? "hivemind_task_events",
66803
+ // Goals + KPIs (refined design — VFS path classifier maps
66804
+ // memory/goal/<user>/<status>/<uuid>.md → hivemind_goals row
66805
+ // memory/kpi/<uuid>/<kpi_id>.md → hivemind_kpis row
66806
+ // See src/shell/deeplake-fs.ts for the translation logic and
66807
+ // GOALS_COLUMNS / KPIS_COLUMNS in deeplake-schema.ts for the
66808
+ // table shape.
66809
+ goalsTableName: process.env.HIVEMIND_GOALS_TABLE ?? "hivemind_goals",
66810
+ kpisTableName: process.env.HIVEMIND_KPIS_TABLE ?? "hivemind_kpis",
66795
66811
  codebaseTableName: process.env.HIVEMIND_CODEBASE_TABLE ?? "codebase",
66796
66812
  memoryPath: process.env.HIVEMIND_MEMORY_PATH ?? join4(home, ".deeplake", "memory")
66797
66813
  };
@@ -66893,6 +66909,65 @@ var SKILLS_COLUMNS = Object.freeze([
66893
66909
  { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
66894
66910
  { name: "updated_at", sql: "TEXT NOT NULL DEFAULT ''" }
66895
66911
  ]);
66912
+ var RULES_COLUMNS = Object.freeze([
66913
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
66914
+ { name: "rule_id", sql: "TEXT NOT NULL DEFAULT ''" },
66915
+ { name: "text", sql: "TEXT NOT NULL DEFAULT ''" },
66916
+ { name: "scope", sql: "TEXT NOT NULL DEFAULT 'team'" },
66917
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'active'" },
66918
+ { name: "assigned_by", sql: "TEXT NOT NULL DEFAULT ''" },
66919
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
66920
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
66921
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
66922
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
66923
+ ]);
66924
+ var TASKS_COLUMNS = Object.freeze([
66925
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
66926
+ { name: "task_id", sql: "TEXT NOT NULL DEFAULT ''" },
66927
+ { name: "text", sql: "TEXT NOT NULL DEFAULT ''" },
66928
+ { name: "scope", sql: "TEXT NOT NULL DEFAULT 'me'" },
66929
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'active'" },
66930
+ { name: "assigned_to", sql: "TEXT NOT NULL DEFAULT ''" },
66931
+ { name: "assigned_by", sql: "TEXT NOT NULL DEFAULT ''" },
66932
+ { name: "kpis", sql: "JSONB" },
66933
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
66934
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
66935
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
66936
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
66937
+ ]);
66938
+ var TASK_EVENTS_COLUMNS = Object.freeze([
66939
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
66940
+ { name: "task_id", sql: "TEXT NOT NULL DEFAULT ''" },
66941
+ { name: "task_version", sql: "BIGINT NOT NULL DEFAULT 1" },
66942
+ { name: "kpi_id", sql: "TEXT NOT NULL DEFAULT ''" },
66943
+ { name: "value", sql: "BIGINT NOT NULL DEFAULT 0" },
66944
+ { name: "note", sql: "TEXT NOT NULL DEFAULT ''" },
66945
+ { name: "source", sql: "TEXT NOT NULL DEFAULT 'user'" },
66946
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT ''" },
66947
+ { name: "ts", sql: "TEXT NOT NULL DEFAULT ''" },
66948
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
66949
+ ]);
66950
+ var GOALS_COLUMNS = Object.freeze([
66951
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
66952
+ { name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
66953
+ { name: "owner", sql: "TEXT NOT NULL DEFAULT ''" },
66954
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'opened'" },
66955
+ { name: "content", sql: "TEXT NOT NULL DEFAULT ''" },
66956
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
66957
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
66958
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
66959
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
66960
+ ]);
66961
+ var KPIS_COLUMNS = Object.freeze([
66962
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
66963
+ { name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
66964
+ { name: "kpi_id", sql: "TEXT NOT NULL DEFAULT ''" },
66965
+ { name: "content", sql: "TEXT NOT NULL DEFAULT ''" },
66966
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
66967
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
66968
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
66969
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
66970
+ ]);
66896
66971
  function validateSchema(label, cols) {
66897
66972
  const seen = /* @__PURE__ */ new Set();
66898
66973
  for (const col of cols) {
@@ -66936,6 +67011,11 @@ var CODEBASE_COLUMNS = Object.freeze([
66936
67011
  validateSchema("MEMORY_COLUMNS", MEMORY_COLUMNS);
66937
67012
  validateSchema("SESSIONS_COLUMNS", SESSIONS_COLUMNS);
66938
67013
  validateSchema("SKILLS_COLUMNS", SKILLS_COLUMNS);
67014
+ validateSchema("RULES_COLUMNS", RULES_COLUMNS);
67015
+ validateSchema("TASKS_COLUMNS", TASKS_COLUMNS);
67016
+ validateSchema("TASK_EVENTS_COLUMNS", TASK_EVENTS_COLUMNS);
67017
+ validateSchema("GOALS_COLUMNS", GOALS_COLUMNS);
67018
+ validateSchema("KPIS_COLUMNS", KPIS_COLUMNS);
66939
67019
  validateSchema("CODEBASE_COLUMNS", CODEBASE_COLUMNS);
66940
67020
  function buildCreateTableSql(tableName, cols) {
66941
67021
  const safe = sqlIdent(tableName);
@@ -67532,6 +67612,111 @@ var DeeplakeApi = class {
67532
67612
  await this.healSchema(safe, SKILLS_COLUMNS);
67533
67613
  await this.ensureLookupIndex(safe, "project_key_name", `("project_key", "name")`);
67534
67614
  }
67615
+ /**
67616
+ * Create the rules table.
67617
+ *
67618
+ * One row per rule version (same write pattern as skills): edits INSERT
67619
+ * a fresh row with version+1, reads pick latest per rule_id via
67620
+ * `ORDER BY version DESC LIMIT 1`. Sidesteps the Deeplake
67621
+ * UPDATE-coalescing quirk by never UPDATEing.
67622
+ */
67623
+ async ensureRulesTable(name) {
67624
+ const safe = sqlIdent(name);
67625
+ const tables = await this.listTables();
67626
+ if (!tables.includes(safe)) {
67627
+ log3(`table "${safe}" not found, creating`);
67628
+ await this.createTableWithRetry(buildCreateTableSql(safe, RULES_COLUMNS), safe);
67629
+ log3(`table "${safe}" created`);
67630
+ if (!tables.includes(safe))
67631
+ this._tablesCache = [...tables, safe];
67632
+ }
67633
+ await this.healSchema(safe, RULES_COLUMNS);
67634
+ await this.ensureLookupIndex(safe, "rule_id_version", `("rule_id", "version")`);
67635
+ }
67636
+ /**
67637
+ * Create the tasks table.
67638
+ *
67639
+ * Same write pattern as rules + skills. `kpis` is a nullable JSONB
67640
+ * column with the agent's KPI metadata; KPI current values come from
67641
+ * `task_events` (SUM(value)), not this snapshot.
67642
+ */
67643
+ async ensureTasksTable(name) {
67644
+ const safe = sqlIdent(name);
67645
+ const tables = await this.listTables();
67646
+ if (!tables.includes(safe)) {
67647
+ log3(`table "${safe}" not found, creating`);
67648
+ await this.createTableWithRetry(buildCreateTableSql(safe, TASKS_COLUMNS), safe);
67649
+ log3(`table "${safe}" created`);
67650
+ if (!tables.includes(safe))
67651
+ this._tablesCache = [...tables, safe];
67652
+ }
67653
+ await this.healSchema(safe, TASKS_COLUMNS);
67654
+ await this.ensureLookupIndex(safe, "task_id_version", `("task_id", "version")`);
67655
+ }
67656
+ /**
67657
+ * Create the task-events table.
67658
+ *
67659
+ * Append-only. Every INSERT is a fresh row; never UPDATE. KPI current
67660
+ * value is `SUM(value) WHERE task_id=? AND kpi_id=?`. Index on
67661
+ * (task_id, kpi_id) is the canonical aggregation key.
67662
+ */
67663
+ async ensureTaskEventsTable(name) {
67664
+ const safe = sqlIdent(name);
67665
+ const tables = await this.listTables();
67666
+ if (!tables.includes(safe)) {
67667
+ log3(`table "${safe}" not found, creating`);
67668
+ await this.createTableWithRetry(buildCreateTableSql(safe, TASK_EVENTS_COLUMNS), safe);
67669
+ log3(`table "${safe}" created`);
67670
+ if (!tables.includes(safe))
67671
+ this._tablesCache = [...tables, safe];
67672
+ }
67673
+ await this.healSchema(safe, TASK_EVENTS_COLUMNS);
67674
+ await this.ensureLookupIndex(safe, "task_id_kpi_id", `("task_id", "kpi_id")`);
67675
+ }
67676
+ /**
67677
+ * Create the goals table.
67678
+ *
67679
+ * Backed by the VFS path convention memory/goal/<owner>/<status>/<goal_id>.md.
67680
+ * INSERT-only version-bumped: rm and mv operations translate to fresh
67681
+ * v=N+1 rows (status flips for mv → closed; rm is the same soft-close).
67682
+ * The (goal_id, version) index lets the VFS dispatch a cheap latest-row
67683
+ * read on cat / Read of a single goal.
67684
+ */
67685
+ async ensureGoalsTable(name) {
67686
+ const safe = sqlIdent(name);
67687
+ const tables = await this.listTables();
67688
+ if (!tables.includes(safe)) {
67689
+ log3(`table "${safe}" not found, creating`);
67690
+ await this.createTableWithRetry(buildCreateTableSql(safe, GOALS_COLUMNS), safe);
67691
+ log3(`table "${safe}" created`);
67692
+ if (!tables.includes(safe))
67693
+ this._tablesCache = [...tables, safe];
67694
+ }
67695
+ await this.healSchema(safe, GOALS_COLUMNS);
67696
+ await this.ensureLookupIndex(safe, "goal_id_version", `("goal_id", "version")`);
67697
+ await this.ensureLookupIndex(safe, "owner_status", `("owner", "status")`);
67698
+ }
67699
+ /**
67700
+ * Create the kpis table.
67701
+ *
67702
+ * Backed by memory/kpi/<goal_id>/<kpi_id>.md. KPI rows do NOT carry
67703
+ * owner — ownership derives from the parent goal via logical join on
67704
+ * goal_id. INSERT-only version-bumped. (goal_id, kpi_id) index is the
67705
+ * canonical lookup the VFS uses on Read and Write.
67706
+ */
67707
+ async ensureKpisTable(name) {
67708
+ const safe = sqlIdent(name);
67709
+ const tables = await this.listTables();
67710
+ if (!tables.includes(safe)) {
67711
+ log3(`table "${safe}" not found, creating`);
67712
+ await this.createTableWithRetry(buildCreateTableSql(safe, KPIS_COLUMNS), safe);
67713
+ log3(`table "${safe}" created`);
67714
+ if (!tables.includes(safe))
67715
+ this._tablesCache = [...tables, safe];
67716
+ }
67717
+ await this.healSchema(safe, KPIS_COLUMNS);
67718
+ await this.ensureLookupIndex(safe, "goal_id_kpi_id", `("goal_id", "kpi_id")`);
67719
+ }
67535
67720
  };
67536
67721
 
67537
67722
  // dist/src/shell/deeplake-fs.js
@@ -68544,6 +68729,82 @@ function buildVirtualIndexContent(summaryRows, sessionRows = [], opts = {}) {
68544
68729
  return lines.join("\n");
68545
68730
  }
68546
68731
 
68732
+ // dist/src/shell/goal-paths.js
68733
+ var VALID_STATUS = /* @__PURE__ */ new Set(["opened", "in_progress", "closed"]);
68734
+ function segmentsUnderMemory(p22) {
68735
+ let s10 = p22.replace(/\/+$/, "");
68736
+ const memIdx = s10.lastIndexOf("/memory/");
68737
+ if (memIdx >= 0) {
68738
+ s10 = s10.slice(memIdx + "/memory/".length);
68739
+ } else {
68740
+ s10 = s10.replace(/^\/+/, "");
68741
+ if (s10 === "memory")
68742
+ return null;
68743
+ if (s10.startsWith("memory/"))
68744
+ s10 = s10.slice("memory/".length);
68745
+ }
68746
+ if (s10.length === 0)
68747
+ return null;
68748
+ return s10.split("/");
68749
+ }
68750
+ function classifyPath(p22) {
68751
+ const segs = segmentsUnderMemory(p22);
68752
+ if (!segs)
68753
+ return "memory";
68754
+ if (segs[0] === "goal") {
68755
+ if (segs.length === 4 && segs[3].endsWith(".md") && VALID_STATUS.has(segs[2])) {
68756
+ return "goal";
68757
+ }
68758
+ return "memory";
68759
+ }
68760
+ if (segs[0] === "kpi") {
68761
+ if (segs.length === 3 && segs[2].endsWith(".md")) {
68762
+ return "kpi";
68763
+ }
68764
+ return "memory";
68765
+ }
68766
+ return "memory";
68767
+ }
68768
+ function decomposeGoalPath(p22) {
68769
+ const segs = segmentsUnderMemory(p22);
68770
+ if (!segs || segs.length !== 4 || segs[0] !== "goal") {
68771
+ throw new Error(`Not a goal path: ${p22}`);
68772
+ }
68773
+ const status = segs[2];
68774
+ if (!VALID_STATUS.has(status)) {
68775
+ throw new Error(`Invalid goal status in path: ${p22} (got '${status}')`);
68776
+ }
68777
+ const filename = segs[3];
68778
+ if (!filename.endsWith(".md")) {
68779
+ throw new Error(`Goal path must end with .md: ${p22}`);
68780
+ }
68781
+ return {
68782
+ owner: segs[1],
68783
+ status,
68784
+ goal_id: filename.slice(0, -".md".length)
68785
+ };
68786
+ }
68787
+ function decomposeKpiPath(p22) {
68788
+ const segs = segmentsUnderMemory(p22);
68789
+ if (!segs || segs.length !== 3 || segs[0] !== "kpi") {
68790
+ throw new Error(`Not a kpi path: ${p22}`);
68791
+ }
68792
+ const filename = segs[2];
68793
+ if (!filename.endsWith(".md")) {
68794
+ throw new Error(`KPI path must end with .md: ${p22}`);
68795
+ }
68796
+ return {
68797
+ goal_id: segs[1],
68798
+ kpi_id: filename.slice(0, -".md".length)
68799
+ };
68800
+ }
68801
+ function composeGoalPath(parts) {
68802
+ return `/goal/${parts.owner}/${parts.status}/${parts.goal_id}.md`;
68803
+ }
68804
+ function composeKpiPath(parts) {
68805
+ return `/kpi/${parts.goal_id}/${parts.kpi_id}.md`;
68806
+ }
68807
+
68547
68808
  // dist/src/graph/vfs-handler.js
68548
68809
  import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync9, renameSync as renameSync5, writeFileSync as writeFileSync7 } from "node:fs";
68549
68810
  import { createHash as createHash3 } from "node:crypto";
@@ -69061,6 +69322,12 @@ var DeeplakeFs = class _DeeplakeFs {
69061
69322
  // Paths that live in the sessions table (multi-row, read by concatenation)
69062
69323
  sessionPaths = /* @__PURE__ */ new Set();
69063
69324
  sessionsTable = null;
69325
+ // Path-routed structured tables. When non-null, the VFS classifies
69326
+ // each path (see ./goal-paths.ts) and dispatches reads/writes to
69327
+ // the right table instead of the generic memory table. Null means
69328
+ // the goal/kpi routing is disabled (test or legacy configurations).
69329
+ goalsTable = null;
69330
+ kpisTable = null;
69064
69331
  // Embedding client lazily created on first flush. Lives as long as the process.
69065
69332
  embedClient = null;
69066
69333
  constructor(client, table, mountPoint) {
@@ -69071,10 +69338,24 @@ var DeeplakeFs = class _DeeplakeFs {
69071
69338
  if (mountPoint !== "/")
69072
69339
  this.dirs.set("/", /* @__PURE__ */ new Set([mountPoint.slice(1)]));
69073
69340
  }
69074
- static async create(client, table, mount = "/memory", sessionsTable) {
69341
+ static async create(client, table, mount = "/memory", sessionsTable, extra) {
69075
69342
  const fs3 = new _DeeplakeFs(client, table, mount);
69076
69343
  fs3.sessionsTable = sessionsTable ?? null;
69344
+ fs3.goalsTable = extra?.goalsTable ?? null;
69345
+ fs3.kpisTable = extra?.kpisTable ?? null;
69077
69346
  await client.ensureTable();
69347
+ if (fs3.goalsTable) {
69348
+ try {
69349
+ await client.ensureGoalsTable(fs3.goalsTable);
69350
+ } catch {
69351
+ }
69352
+ }
69353
+ if (fs3.kpisTable) {
69354
+ try {
69355
+ await client.ensureKpisTable(fs3.kpisTable);
69356
+ } catch {
69357
+ }
69358
+ }
69078
69359
  let sessionSyncOk = true;
69079
69360
  const memoryBootstrap = (async () => {
69080
69361
  const sql = `SELECT path, size_bytes, mime_type FROM "${table}" ORDER BY path`;
@@ -69120,7 +69401,61 @@ var DeeplakeFs = class _DeeplakeFs {
69120
69401
  } catch {
69121
69402
  }
69122
69403
  })() : Promise.resolve();
69123
- await Promise.all([memoryBootstrap, sessionsBootstrap]);
69404
+ const goalsBootstrap = fs3.goalsTable ? (async () => {
69405
+ try {
69406
+ const goalRows = await client.query(
69407
+ // One row per goal_id (UPDATE-or-INSERT model). Synthesize
69408
+ // the canonical VFS path from owner / status / goal_id.
69409
+ `SELECT goal_id, owner, status, content, created_at FROM "${fs3.goalsTable}" ORDER BY created_at DESC`
69410
+ );
69411
+ for (const row of goalRows) {
69412
+ const owner = String(row["owner"] ?? "");
69413
+ const status = String(row["status"] ?? "");
69414
+ const goal_id = String(row["goal_id"] ?? "");
69415
+ if (!owner || !status || !goal_id)
69416
+ continue;
69417
+ if (status !== "opened" && status !== "in_progress" && status !== "closed")
69418
+ continue;
69419
+ const p22 = composeGoalPath({ owner, status, goal_id });
69420
+ const content = String(row["content"] ?? "");
69421
+ fs3.files.set(p22, Buffer.from(content, "utf-8"));
69422
+ fs3.meta.set(p22, {
69423
+ size: Buffer.byteLength(content, "utf-8"),
69424
+ mime: "text/markdown",
69425
+ mtime: /* @__PURE__ */ new Date()
69426
+ });
69427
+ fs3.addToTree(p22);
69428
+ fs3.flushed.add(p22);
69429
+ }
69430
+ } catch {
69431
+ }
69432
+ })() : Promise.resolve();
69433
+ const kpisBootstrap = fs3.kpisTable ? (async () => {
69434
+ try {
69435
+ const kpiRows = await client.query(
69436
+ // One row per (goal_id, kpi_id) (UPDATE-or-INSERT model).
69437
+ `SELECT goal_id, kpi_id, content, created_at FROM "${fs3.kpisTable}" ORDER BY created_at DESC`
69438
+ );
69439
+ for (const row of kpiRows) {
69440
+ const goal_id = String(row["goal_id"] ?? "");
69441
+ const kpi_id = String(row["kpi_id"] ?? "");
69442
+ if (!goal_id || !kpi_id)
69443
+ continue;
69444
+ const p22 = composeKpiPath({ goal_id, kpi_id });
69445
+ const content = String(row["content"] ?? "");
69446
+ fs3.files.set(p22, Buffer.from(content, "utf-8"));
69447
+ fs3.meta.set(p22, {
69448
+ size: Buffer.byteLength(content, "utf-8"),
69449
+ mime: "text/markdown",
69450
+ mtime: /* @__PURE__ */ new Date()
69451
+ });
69452
+ fs3.addToTree(p22);
69453
+ fs3.flushed.add(p22);
69454
+ }
69455
+ } catch {
69456
+ }
69457
+ })() : Promise.resolve();
69458
+ await Promise.all([memoryBootstrap, sessionsBootstrap, goalsBootstrap, kpisBootstrap]);
69124
69459
  return fs3;
69125
69460
  }
69126
69461
  // ── tree management ───────────────────────────────────────────────────────
@@ -69189,6 +69524,15 @@ var DeeplakeFs = class _DeeplakeFs {
69189
69524
  return Promise.all(rows.map((r10) => this.embedClient.embed(r10.contentText, "document")));
69190
69525
  }
69191
69526
  async upsertRow(r10, embedding) {
69527
+ const kind = classifyPath(r10.path);
69528
+ if (kind === "goal" && this.goalsTable) {
69529
+ await this.upsertGoalRow(r10);
69530
+ return;
69531
+ }
69532
+ if (kind === "kpi" && this.kpisTable) {
69533
+ await this.upsertKpiRow(r10);
69534
+ return;
69535
+ }
69192
69536
  const text = sqlStr(r10.contentText);
69193
69537
  const p22 = sqlStr(r10.path);
69194
69538
  const fname = sqlStr(r10.filename);
@@ -69212,6 +69556,57 @@ var DeeplakeFs = class _DeeplakeFs {
69212
69556
  this.flushed.add(r10.path);
69213
69557
  }
69214
69558
  }
69559
+ /**
69560
+ * UPDATE-or-INSERT for a goal row, keyed by goal_id. One row per
69561
+ * goal forever — status changes, owner reassignments, and text
69562
+ * edits all mutate the same row in place. The version column
69563
+ * stays at 1 (vestigial in the schema; kept so the column is
69564
+ * already there if we ever bring back the audit-trail pattern).
69565
+ *
69566
+ * Trade-off versus the prior INSERT-only-version-bump design:
69567
+ * - Pros: 1 row per goal makes the Deeplake table view obvious,
69568
+ * no row proliferation, simpler bootstrap queries.
69569
+ * - Cons: no audit trail; vulnerable to Deeplake's
69570
+ * UPDATE-coalescing quirk if two writes hit the same row
69571
+ * within microseconds. For the v1 single-user / small-team
69572
+ * workflow the user explicitly chose this trade-off.
69573
+ */
69574
+ async upsertGoalRow(r10) {
69575
+ if (!this.goalsTable)
69576
+ throw new Error("goalsTable not configured");
69577
+ const parts = decomposeGoalPath(r10.path);
69578
+ const safe = this.goalsTable;
69579
+ const ts3 = r10.lastUpdateDate ?? r10.creationDate ?? (/* @__PURE__ */ new Date()).toISOString();
69580
+ const existing = await this.client.query(`SELECT id FROM "${safe}" WHERE goal_id = '${sqlStr(parts.goal_id)}' LIMIT 1`);
69581
+ if (existing.length > 0) {
69582
+ await this.client.query(`UPDATE "${safe}" SET owner = '${sqlStr(parts.owner)}', status = '${sqlStr(parts.status)}', content = E'${sqlStr(r10.contentText)}', created_at = '${sqlStr(ts3)}' WHERE goal_id = '${sqlStr(parts.goal_id)}'`);
69583
+ } else {
69584
+ const id = randomUUID2();
69585
+ await this.client.query(`INSERT INTO "${safe}" (id, goal_id, owner, status, content, version, created_at, agent, plugin_version) VALUES ('${id}', '${sqlStr(parts.goal_id)}', '${sqlStr(parts.owner)}', '${sqlStr(parts.status)}', E'${sqlStr(r10.contentText)}', 1, '${sqlStr(ts3)}', 'manual', '')`);
69586
+ }
69587
+ this.flushed.add(r10.path);
69588
+ }
69589
+ /**
69590
+ * UPDATE-or-INSERT for a KPI row, keyed by (goal_id, kpi_id).
69591
+ * Same trade-off as upsertGoalRow — one row per KPI forever,
69592
+ * no version proliferation. Progress bumps (Edit on the `current:`
69593
+ * line) and any other content edits mutate the same row in place.
69594
+ */
69595
+ async upsertKpiRow(r10) {
69596
+ if (!this.kpisTable)
69597
+ throw new Error("kpisTable not configured");
69598
+ const parts = decomposeKpiPath(r10.path);
69599
+ const safe = this.kpisTable;
69600
+ const ts3 = r10.lastUpdateDate ?? r10.creationDate ?? (/* @__PURE__ */ new Date()).toISOString();
69601
+ const existing = await this.client.query(`SELECT id FROM "${safe}" WHERE goal_id = '${sqlStr(parts.goal_id)}' AND kpi_id = '${sqlStr(parts.kpi_id)}' LIMIT 1`);
69602
+ if (existing.length > 0) {
69603
+ await this.client.query(`UPDATE "${safe}" SET content = E'${sqlStr(r10.contentText)}', created_at = '${sqlStr(ts3)}' WHERE goal_id = '${sqlStr(parts.goal_id)}' AND kpi_id = '${sqlStr(parts.kpi_id)}'`);
69604
+ } else {
69605
+ const id = randomUUID2();
69606
+ await this.client.query(`INSERT INTO "${safe}" (id, goal_id, kpi_id, content, version, created_at, agent, plugin_version) VALUES ('${id}', '${sqlStr(parts.goal_id)}', '${sqlStr(parts.kpi_id)}', E'${sqlStr(r10.contentText)}', 1, '${sqlStr(ts3)}', 'manual', '')`);
69607
+ }
69608
+ this.flushed.add(r10.path);
69609
+ }
69215
69610
  // ── Virtual index.md generation ────────────────────────────────────────────
69216
69611
  async generateVirtualIndex() {
69217
69612
  const fetchLimit = INDEX_LIMIT_PER_SECTION + 1;
@@ -69583,6 +69978,31 @@ var DeeplakeFs = class _DeeplakeFs {
69583
69978
  return;
69584
69979
  throw fsErr("ENOENT", "no such file or directory", p22);
69585
69980
  }
69981
+ if (this.goalsTable && classifyPath(p22) === "goal") {
69982
+ const parts = decomposeGoalPath(p22);
69983
+ if (parts.status === "closed") {
69984
+ this.removeFromTree(p22);
69985
+ return;
69986
+ }
69987
+ const closedPath = composeGoalPath({ ...parts, status: "closed" });
69988
+ const contentBuf = this.files.get(p22);
69989
+ const content = contentBuf instanceof Buffer ? contentBuf.toString("utf-8") : "";
69990
+ await this.upsertGoalRow({
69991
+ path: closedPath,
69992
+ filename: basename5(closedPath),
69993
+ contentText: content,
69994
+ mimeType: "text/markdown",
69995
+ sizeBytes: Buffer.byteLength(content, "utf-8"),
69996
+ creationDate: void 0,
69997
+ lastUpdateDate: (/* @__PURE__ */ new Date()).toISOString()
69998
+ });
69999
+ this.files.set(closedPath, contentBuf ?? null);
70000
+ this.meta.set(closedPath, this.meta.get(p22) ?? { size: 0, mime: "text/markdown", mtime: /* @__PURE__ */ new Date() });
70001
+ this.addToTree(closedPath);
70002
+ this.flushed.add(closedPath);
70003
+ this.removeFromTree(p22);
70004
+ return;
70005
+ }
69586
70006
  if (this.dirs.has(p22)) {
69587
70007
  const children = this.dirs.get(p22) ?? /* @__PURE__ */ new Set();
69588
70008
  if (children.size > 0 && !opts?.recursive)
@@ -69633,6 +70053,32 @@ var DeeplakeFs = class _DeeplakeFs {
69633
70053
  throw fsErr("EPERM", "session files are read-only", s10);
69634
70054
  if (this.sessionPaths.has(d15))
69635
70055
  throw fsErr("EPERM", "session files are read-only", d15);
70056
+ if (this.goalsTable && classifyPath(s10) === "goal" && classifyPath(d15) === "goal") {
70057
+ const from = decomposeGoalPath(s10);
70058
+ const to3 = decomposeGoalPath(d15);
70059
+ if (from.goal_id !== to3.goal_id || from.owner !== to3.owner) {
70060
+ throw fsErr("EPERM", "cannot rename goal_id or owner via mv (only status)", d15);
70061
+ }
70062
+ if (!this.files.has(s10))
70063
+ throw fsErr("ENOENT", "no such file or directory", s10);
70064
+ const contentBuf = this.files.get(s10);
70065
+ const content = contentBuf instanceof Buffer ? contentBuf.toString("utf-8") : "";
70066
+ await this.upsertGoalRow({
70067
+ path: d15,
70068
+ filename: basename5(d15),
70069
+ contentText: content,
70070
+ mimeType: "text/markdown",
70071
+ sizeBytes: Buffer.byteLength(content, "utf-8"),
70072
+ creationDate: void 0,
70073
+ lastUpdateDate: (/* @__PURE__ */ new Date()).toISOString()
70074
+ });
70075
+ this.files.set(d15, contentBuf ?? null);
70076
+ this.meta.set(d15, this.meta.get(s10) ?? { size: 0, mime: "text/markdown", mtime: /* @__PURE__ */ new Date() });
70077
+ this.addToTree(d15);
70078
+ this.flushed.add(d15);
70079
+ this.removeFromTree(s10);
70080
+ return;
70081
+ }
69636
70082
  await this.cp(src, dest, { recursive: true });
69637
70083
  await this.rm(src, { recursive: true, force: true });
69638
70084
  }
@@ -70787,13 +71233,15 @@ async function main() {
70787
71233
  }
70788
71234
  const table = process.env["HIVEMIND_TABLE"] ?? "memory";
70789
71235
  const sessionsTable = process.env["HIVEMIND_SESSIONS_TABLE"] ?? "sessions";
71236
+ const goalsTable = process.env["HIVEMIND_GOALS_TABLE"] ?? config.goalsTableName;
71237
+ const kpisTable = process.env["HIVEMIND_KPIS_TABLE"] ?? config.kpisTableName;
70790
71238
  const mount = process.env["HIVEMIND_MOUNT"] ?? "/";
70791
71239
  const client = new DeeplakeApi(config.token, config.apiUrl, config.orgId, config.workspaceId, table);
70792
71240
  if (!isOneShot) {
70793
71241
  process.stderr.write(`Connecting to deeplake://${config.workspaceId}/${table} ...
70794
71242
  `);
70795
71243
  }
70796
- const fs3 = await DeeplakeFs.create(client, table, mount, sessionsTable);
71244
+ const fs3 = await DeeplakeFs.create(client, table, mount, sessionsTable, { goalsTable, kpisTable });
70797
71245
  if (!isOneShot) {
70798
71246
  const fileCount = fs3.getAllPaths().filter((p22) => !!p22).length;
70799
71247
  process.stderr.write(`Ready. ${fileCount} files loaded.
@@ -378,6 +378,65 @@ var SKILLS_COLUMNS = Object.freeze([
378
378
  { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
379
379
  { name: "updated_at", sql: "TEXT NOT NULL DEFAULT ''" }
380
380
  ]);
381
+ var RULES_COLUMNS = Object.freeze([
382
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
383
+ { name: "rule_id", sql: "TEXT NOT NULL DEFAULT ''" },
384
+ { name: "text", sql: "TEXT NOT NULL DEFAULT ''" },
385
+ { name: "scope", sql: "TEXT NOT NULL DEFAULT 'team'" },
386
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'active'" },
387
+ { name: "assigned_by", sql: "TEXT NOT NULL DEFAULT ''" },
388
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
389
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
390
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
391
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
392
+ ]);
393
+ var TASKS_COLUMNS = Object.freeze([
394
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
395
+ { name: "task_id", sql: "TEXT NOT NULL DEFAULT ''" },
396
+ { name: "text", sql: "TEXT NOT NULL DEFAULT ''" },
397
+ { name: "scope", sql: "TEXT NOT NULL DEFAULT 'me'" },
398
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'active'" },
399
+ { name: "assigned_to", sql: "TEXT NOT NULL DEFAULT ''" },
400
+ { name: "assigned_by", sql: "TEXT NOT NULL DEFAULT ''" },
401
+ { name: "kpis", sql: "JSONB" },
402
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
403
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
404
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
405
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
406
+ ]);
407
+ var TASK_EVENTS_COLUMNS = Object.freeze([
408
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
409
+ { name: "task_id", sql: "TEXT NOT NULL DEFAULT ''" },
410
+ { name: "task_version", sql: "BIGINT NOT NULL DEFAULT 1" },
411
+ { name: "kpi_id", sql: "TEXT NOT NULL DEFAULT ''" },
412
+ { name: "value", sql: "BIGINT NOT NULL DEFAULT 0" },
413
+ { name: "note", sql: "TEXT NOT NULL DEFAULT ''" },
414
+ { name: "source", sql: "TEXT NOT NULL DEFAULT 'user'" },
415
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT ''" },
416
+ { name: "ts", sql: "TEXT NOT NULL DEFAULT ''" },
417
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
418
+ ]);
419
+ var GOALS_COLUMNS = Object.freeze([
420
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
421
+ { name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
422
+ { name: "owner", sql: "TEXT NOT NULL DEFAULT ''" },
423
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'opened'" },
424
+ { name: "content", sql: "TEXT NOT NULL DEFAULT ''" },
425
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
426
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
427
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
428
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
429
+ ]);
430
+ var KPIS_COLUMNS = Object.freeze([
431
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
432
+ { name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
433
+ { name: "kpi_id", sql: "TEXT NOT NULL DEFAULT ''" },
434
+ { name: "content", sql: "TEXT NOT NULL DEFAULT ''" },
435
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
436
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
437
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
438
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
439
+ ]);
381
440
  function validateSchema(label, cols) {
382
441
  const seen = /* @__PURE__ */ new Set();
383
442
  for (const col of cols) {
@@ -421,6 +480,11 @@ var CODEBASE_COLUMNS = Object.freeze([
421
480
  validateSchema("MEMORY_COLUMNS", MEMORY_COLUMNS);
422
481
  validateSchema("SESSIONS_COLUMNS", SESSIONS_COLUMNS);
423
482
  validateSchema("SKILLS_COLUMNS", SKILLS_COLUMNS);
483
+ validateSchema("RULES_COLUMNS", RULES_COLUMNS);
484
+ validateSchema("TASKS_COLUMNS", TASKS_COLUMNS);
485
+ validateSchema("TASK_EVENTS_COLUMNS", TASK_EVENTS_COLUMNS);
486
+ validateSchema("GOALS_COLUMNS", GOALS_COLUMNS);
487
+ validateSchema("KPIS_COLUMNS", KPIS_COLUMNS);
424
488
  validateSchema("CODEBASE_COLUMNS", CODEBASE_COLUMNS);
425
489
  function buildCreateTableSql(tableName, cols) {
426
490
  const safe = sqlIdent(tableName);