@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
@@ -103,6 +103,22 @@ function loadConfig() {
103
103
  tableName: process.env.HIVEMIND_TABLE ?? "memory",
104
104
  sessionsTableName: process.env.HIVEMIND_SESSIONS_TABLE ?? "sessions",
105
105
  skillsTableName: process.env.HIVEMIND_SKILLS_TABLE ?? "skills",
106
+ // Defaults match the table name written into the SQL — keep aligned
107
+ // with RULES_COLUMNS / TASKS_COLUMNS / TASK_EVENTS_COLUMNS in
108
+ // deeplake-schema.ts and with the e2e test-org override convention
109
+ // (memory_test / sessions_test → goals_test, etc.) documented in
110
+ // CLAUDE.md.
111
+ rulesTableName: process.env.HIVEMIND_RULES_TABLE ?? "hivemind_rules",
112
+ tasksTableName: process.env.HIVEMIND_TASKS_TABLE ?? "hivemind_tasks",
113
+ taskEventsTableName: process.env.HIVEMIND_TASK_EVENTS_TABLE ?? "hivemind_task_events",
114
+ // Goals + KPIs (refined design — VFS path classifier maps
115
+ // memory/goal/<user>/<status>/<uuid>.md → hivemind_goals row
116
+ // memory/kpi/<uuid>/<kpi_id>.md → hivemind_kpis row
117
+ // See src/shell/deeplake-fs.ts for the translation logic and
118
+ // GOALS_COLUMNS / KPIS_COLUMNS in deeplake-schema.ts for the
119
+ // table shape.
120
+ goalsTableName: process.env.HIVEMIND_GOALS_TABLE ?? "hivemind_goals",
121
+ kpisTableName: process.env.HIVEMIND_KPIS_TABLE ?? "hivemind_kpis",
106
122
  codebaseTableName: process.env.HIVEMIND_CODEBASE_TABLE ?? "codebase",
107
123
  memoryPath: process.env.HIVEMIND_MEMORY_PATH ?? join(home, ".deeplake", "memory")
108
124
  };
@@ -204,6 +220,65 @@ var SKILLS_COLUMNS = Object.freeze([
204
220
  { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
205
221
  { name: "updated_at", sql: "TEXT NOT NULL DEFAULT ''" }
206
222
  ]);
223
+ var RULES_COLUMNS = Object.freeze([
224
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
225
+ { name: "rule_id", sql: "TEXT NOT NULL DEFAULT ''" },
226
+ { name: "text", sql: "TEXT NOT NULL DEFAULT ''" },
227
+ { name: "scope", sql: "TEXT NOT NULL DEFAULT 'team'" },
228
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'active'" },
229
+ { name: "assigned_by", sql: "TEXT NOT NULL DEFAULT ''" },
230
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
231
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
232
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
233
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
234
+ ]);
235
+ var TASKS_COLUMNS = Object.freeze([
236
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
237
+ { name: "task_id", sql: "TEXT NOT NULL DEFAULT ''" },
238
+ { name: "text", sql: "TEXT NOT NULL DEFAULT ''" },
239
+ { name: "scope", sql: "TEXT NOT NULL DEFAULT 'me'" },
240
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'active'" },
241
+ { name: "assigned_to", sql: "TEXT NOT NULL DEFAULT ''" },
242
+ { name: "assigned_by", sql: "TEXT NOT NULL DEFAULT ''" },
243
+ { name: "kpis", sql: "JSONB" },
244
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
245
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
246
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
247
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
248
+ ]);
249
+ var TASK_EVENTS_COLUMNS = Object.freeze([
250
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
251
+ { name: "task_id", sql: "TEXT NOT NULL DEFAULT ''" },
252
+ { name: "task_version", sql: "BIGINT NOT NULL DEFAULT 1" },
253
+ { name: "kpi_id", sql: "TEXT NOT NULL DEFAULT ''" },
254
+ { name: "value", sql: "BIGINT NOT NULL DEFAULT 0" },
255
+ { name: "note", sql: "TEXT NOT NULL DEFAULT ''" },
256
+ { name: "source", sql: "TEXT NOT NULL DEFAULT 'user'" },
257
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT ''" },
258
+ { name: "ts", sql: "TEXT NOT NULL DEFAULT ''" },
259
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
260
+ ]);
261
+ var GOALS_COLUMNS = Object.freeze([
262
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
263
+ { name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
264
+ { name: "owner", sql: "TEXT NOT NULL DEFAULT ''" },
265
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'opened'" },
266
+ { name: "content", sql: "TEXT NOT NULL DEFAULT ''" },
267
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
268
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
269
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
270
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
271
+ ]);
272
+ var KPIS_COLUMNS = Object.freeze([
273
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
274
+ { name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
275
+ { name: "kpi_id", sql: "TEXT NOT NULL DEFAULT ''" },
276
+ { name: "content", sql: "TEXT NOT NULL DEFAULT ''" },
277
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
278
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
279
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
280
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
281
+ ]);
207
282
  function validateSchema(label, cols) {
208
283
  const seen = /* @__PURE__ */ new Set();
209
284
  for (const col of cols) {
@@ -247,6 +322,11 @@ var CODEBASE_COLUMNS = Object.freeze([
247
322
  validateSchema("MEMORY_COLUMNS", MEMORY_COLUMNS);
248
323
  validateSchema("SESSIONS_COLUMNS", SESSIONS_COLUMNS);
249
324
  validateSchema("SKILLS_COLUMNS", SKILLS_COLUMNS);
325
+ validateSchema("RULES_COLUMNS", RULES_COLUMNS);
326
+ validateSchema("TASKS_COLUMNS", TASKS_COLUMNS);
327
+ validateSchema("TASK_EVENTS_COLUMNS", TASK_EVENTS_COLUMNS);
328
+ validateSchema("GOALS_COLUMNS", GOALS_COLUMNS);
329
+ validateSchema("KPIS_COLUMNS", KPIS_COLUMNS);
250
330
  validateSchema("CODEBASE_COLUMNS", CODEBASE_COLUMNS);
251
331
  function buildCreateTableSql(tableName, cols) {
252
332
  const safe = sqlIdent(tableName);
@@ -843,6 +923,111 @@ var DeeplakeApi = class {
843
923
  await this.healSchema(safe, SKILLS_COLUMNS);
844
924
  await this.ensureLookupIndex(safe, "project_key_name", `("project_key", "name")`);
845
925
  }
926
+ /**
927
+ * Create the rules table.
928
+ *
929
+ * One row per rule version (same write pattern as skills): edits INSERT
930
+ * a fresh row with version+1, reads pick latest per rule_id via
931
+ * `ORDER BY version DESC LIMIT 1`. Sidesteps the Deeplake
932
+ * UPDATE-coalescing quirk by never UPDATEing.
933
+ */
934
+ async ensureRulesTable(name) {
935
+ const safe = sqlIdent(name);
936
+ const tables = await this.listTables();
937
+ if (!tables.includes(safe)) {
938
+ log3(`table "${safe}" not found, creating`);
939
+ await this.createTableWithRetry(buildCreateTableSql(safe, RULES_COLUMNS), safe);
940
+ log3(`table "${safe}" created`);
941
+ if (!tables.includes(safe))
942
+ this._tablesCache = [...tables, safe];
943
+ }
944
+ await this.healSchema(safe, RULES_COLUMNS);
945
+ await this.ensureLookupIndex(safe, "rule_id_version", `("rule_id", "version")`);
946
+ }
947
+ /**
948
+ * Create the tasks table.
949
+ *
950
+ * Same write pattern as rules + skills. `kpis` is a nullable JSONB
951
+ * column with the agent's KPI metadata; KPI current values come from
952
+ * `task_events` (SUM(value)), not this snapshot.
953
+ */
954
+ async ensureTasksTable(name) {
955
+ const safe = sqlIdent(name);
956
+ const tables = await this.listTables();
957
+ if (!tables.includes(safe)) {
958
+ log3(`table "${safe}" not found, creating`);
959
+ await this.createTableWithRetry(buildCreateTableSql(safe, TASKS_COLUMNS), safe);
960
+ log3(`table "${safe}" created`);
961
+ if (!tables.includes(safe))
962
+ this._tablesCache = [...tables, safe];
963
+ }
964
+ await this.healSchema(safe, TASKS_COLUMNS);
965
+ await this.ensureLookupIndex(safe, "task_id_version", `("task_id", "version")`);
966
+ }
967
+ /**
968
+ * Create the task-events table.
969
+ *
970
+ * Append-only. Every INSERT is a fresh row; never UPDATE. KPI current
971
+ * value is `SUM(value) WHERE task_id=? AND kpi_id=?`. Index on
972
+ * (task_id, kpi_id) is the canonical aggregation key.
973
+ */
974
+ async ensureTaskEventsTable(name) {
975
+ const safe = sqlIdent(name);
976
+ const tables = await this.listTables();
977
+ if (!tables.includes(safe)) {
978
+ log3(`table "${safe}" not found, creating`);
979
+ await this.createTableWithRetry(buildCreateTableSql(safe, TASK_EVENTS_COLUMNS), safe);
980
+ log3(`table "${safe}" created`);
981
+ if (!tables.includes(safe))
982
+ this._tablesCache = [...tables, safe];
983
+ }
984
+ await this.healSchema(safe, TASK_EVENTS_COLUMNS);
985
+ await this.ensureLookupIndex(safe, "task_id_kpi_id", `("task_id", "kpi_id")`);
986
+ }
987
+ /**
988
+ * Create the goals table.
989
+ *
990
+ * Backed by the VFS path convention memory/goal/<owner>/<status>/<goal_id>.md.
991
+ * INSERT-only version-bumped: rm and mv operations translate to fresh
992
+ * v=N+1 rows (status flips for mv → closed; rm is the same soft-close).
993
+ * The (goal_id, version) index lets the VFS dispatch a cheap latest-row
994
+ * read on cat / Read of a single goal.
995
+ */
996
+ async ensureGoalsTable(name) {
997
+ const safe = sqlIdent(name);
998
+ const tables = await this.listTables();
999
+ if (!tables.includes(safe)) {
1000
+ log3(`table "${safe}" not found, creating`);
1001
+ await this.createTableWithRetry(buildCreateTableSql(safe, GOALS_COLUMNS), safe);
1002
+ log3(`table "${safe}" created`);
1003
+ if (!tables.includes(safe))
1004
+ this._tablesCache = [...tables, safe];
1005
+ }
1006
+ await this.healSchema(safe, GOALS_COLUMNS);
1007
+ await this.ensureLookupIndex(safe, "goal_id_version", `("goal_id", "version")`);
1008
+ await this.ensureLookupIndex(safe, "owner_status", `("owner", "status")`);
1009
+ }
1010
+ /**
1011
+ * Create the kpis table.
1012
+ *
1013
+ * Backed by memory/kpi/<goal_id>/<kpi_id>.md. KPI rows do NOT carry
1014
+ * owner — ownership derives from the parent goal via logical join on
1015
+ * goal_id. INSERT-only version-bumped. (goal_id, kpi_id) index is the
1016
+ * canonical lookup the VFS uses on Read and Write.
1017
+ */
1018
+ async ensureKpisTable(name) {
1019
+ const safe = sqlIdent(name);
1020
+ const tables = await this.listTables();
1021
+ if (!tables.includes(safe)) {
1022
+ log3(`table "${safe}" not found, creating`);
1023
+ await this.createTableWithRetry(buildCreateTableSql(safe, KPIS_COLUMNS), safe);
1024
+ log3(`table "${safe}" created`);
1025
+ if (!tables.includes(safe))
1026
+ this._tablesCache = [...tables, safe];
1027
+ }
1028
+ await this.healSchema(safe, KPIS_COLUMNS);
1029
+ await this.ensureLookupIndex(safe, "goal_id_kpi_id", `("goal_id", "kpi_id")`);
1030
+ }
846
1031
  };
847
1032
 
848
1033
  // dist/src/hooks/codex/spawn-wiki-worker.js
@@ -0,0 +1,157 @@
1
+ ---
2
+ name: hivemind-goals
3
+ description: Create, track and update team goals + KPIs via the Deeplake virtual filesystem at memory/goal/ and memory/kpi/. Use whenever the user mentions a goal, objective, KPI, target, milestone, or asks to track progress on something measurable.
4
+ allowed-tools: Bash
5
+ ---
6
+
7
+ # Hivemind Goals
8
+
9
+ Track goals and KPIs as Markdown files inside the Deeplake virtual filesystem. Each file is one row in a dedicated team-shared table — the path encodes the structural metadata, the file body holds the human-readable description.
10
+
11
+ ## When to use this skill
12
+
13
+ Activate when the user expresses any of:
14
+ - "I want to track X / aim for X / track my progress on Y"
15
+ - "add a goal", "add a KPI", "what are my goals?"
16
+ - "mark this as done", "close that goal"
17
+ - "shipping X by Friday", "5 PRs this week", any measurable target
18
+
19
+ For "list my goals" → run `ls ~/.deeplake/memory/goal/<userName>/opened/` and `ls ~/.deeplake/memory/goal/<userName>/in_progress/`. If empty, ask the user if they want to create one.
20
+
21
+ ## Path conventions (LEARN THESE)
22
+
23
+ ```
24
+ ~/.deeplake/memory/goal/<owner>/<status>/<goal_id>.md
25
+ ~/.deeplake/memory/kpi/<goal_id>/<kpi_id>.md
26
+ ```
27
+
28
+ - `<owner>` — user identifier (use the userName from `hivemind whoami` or the credentials)
29
+ - `<status>` — one of `opened`, `in_progress`, `closed`
30
+ - `<goal_id>` — UUIDv4 you generate at create time
31
+ - `<kpi_id>` — short slug like `k-prs` or `k-demos`
32
+
33
+ **Path encoding is the source of truth.** The owner, status, goal_id, and kpi_id come from the path — NOT from the file body. Do NOT write owner/status/goal_id/kpi_id inside the file content.
34
+
35
+ ## File body format
36
+
37
+ Goal file body — plain markdown, free form:
38
+ ```
39
+ ship the goals-graph feature
40
+
41
+ Notes: focus on KPI tracking via VFS, no separate CLI.
42
+ Due: 2026-05-30.
43
+ ```
44
+
45
+ KPI file body — markdown with a few mandatory key:value lines so the commit-driven auto-progress worker can parse and bump:
46
+ ```
47
+ PRs merged
48
+
49
+ - target: 5
50
+ - current: 2
51
+ - unit: count
52
+ ```
53
+
54
+ The `target:`, `current:`, `unit:` lines must stay on a single line each. The first line is the human-readable name. Anything else is free notes.
55
+
56
+ ## Operations
57
+
58
+ ### 1. Create a new goal
59
+
60
+ When the user expresses a new goal:
61
+
62
+ 1. Get the current owner with `hivemind whoami` (use the userName, e.g. `emanuele.fenocchi`).
63
+ 2. Generate a UUIDv4: `uuidgen` or `node -e 'console.log(crypto.randomUUID())'`.
64
+ 3. Write the goal file at `~/.deeplake/memory/goal/<owner>/opened/<uuid>.md` with the goal description as body.
65
+ 4. Respond to the user that the goal is created.
66
+
67
+ **Do NOT auto-generate KPIs.** A goal is created with zero KPI files by default. Generate KPIs ONLY when the user explicitly asks you to ("aggiungi KPI per …", "add metrics for this goal", "track these metrics: …"). When the user asks, write each KPI as a separate file at `~/.deeplake/memory/kpi/<goal_id>/<kpi-slug>.md` with the body format documented above.
68
+
69
+ ### 2. List goals
70
+
71
+ ```bash
72
+ ls ~/.deeplake/memory/goal/<owner>/opened/
73
+ ls ~/.deeplake/memory/goal/<owner>/in_progress/
74
+ ```
75
+
76
+ Then `cat` each `<uuid>.md` to read the body. Optionally `ls ~/.deeplake/memory/kpi/<uuid>/` and `cat` each KPI to surface progress.
77
+
78
+ ### 3. Edit a goal description
79
+
80
+ ```bash
81
+ # Use Read + Edit (or Write) on the existing file. The VFS handles
82
+ # version-bumping — every write produces a fresh row in the
83
+ # hivemind_goals table.
84
+ ```
85
+
86
+ ### 4. Move a goal to in_progress
87
+
88
+ ```bash
89
+ mv ~/.deeplake/memory/goal/<owner>/opened/<uuid>.md ~/.deeplake/memory/goal/<owner>/in_progress/<uuid>.md
90
+ ```
91
+
92
+ `mv` between status folders is an atomic version-bump. The file body carries over unchanged.
93
+
94
+ ### 5. Close a goal
95
+
96
+ Two equivalent ways:
97
+
98
+ ```bash
99
+ # Explicit mv to closed (recommended — clearest intent)
100
+ mv ~/.deeplake/memory/goal/<owner>/in_progress/<uuid>.md ~/.deeplake/memory/goal/<owner>/closed/<uuid>.md
101
+
102
+ # Or: rm (the VFS interprets rm on a goal path as a soft-close)
103
+ rm ~/.deeplake/memory/goal/<owner>/opened/<uuid>.md
104
+ ```
105
+
106
+ **Important:** `rm` does NOT actually delete the goal. It is a soft-close — the VFS writes a new version with status=closed. The goal remains in the team-shared table for audit. There is no hard-delete in v1.
107
+
108
+ ### 6. Add a KPI manually
109
+
110
+ ```bash
111
+ Write the file at ~/.deeplake/memory/kpi/<uuid>/<kpi-slug>.md with:
112
+ <KPI name>
113
+
114
+ - target: <N>
115
+ - current: 0
116
+ - unit: <unit>
117
+ ```
118
+
119
+ ### 7. Record progress on a KPI
120
+
121
+ Read the KPI file, increment the `current:` line, write it back:
122
+
123
+ ```
124
+ <KPI name>
125
+
126
+ - target: 5
127
+ - current: 3 ← incremented from 2
128
+ - unit: count
129
+ ```
130
+
131
+ Use the Edit tool for the most surgical change (just the line with `current:`).
132
+
133
+ ### 8. Reassign a goal (transfer ownership)
134
+
135
+ ```bash
136
+ mv ~/.deeplake/memory/goal/<old-owner>/<status>/<uuid>.md ~/.deeplake/memory/goal/<new-owner>/<status>/<uuid>.md
137
+ ```
138
+
139
+ Goal ownership lives in the path. KPI files do NOT have an owner segment — they are linked to the goal by `<uuid>`, so they need no change when a goal is reassigned.
140
+
141
+ ## Constraints — DO NOT do these
142
+
143
+ - Do NOT put `owner`, `status`, `goal_id`, or `kpi_id` inside the file body. The path is the source of truth — duplicating in the body causes drift.
144
+ - Do NOT use status values other than `opened`, `in_progress`, `closed`.
145
+ - Do NOT rename the goal_id (the UUID in the filename) via `mv`. The VFS rejects goal_id renames.
146
+ - Do NOT block on the KPI generator subprocess — always spawn it detached (`nohup … &`).
147
+ - Do NOT use the old `hivemind tasks` CLI — that belonged to an earlier design and is being removed.
148
+
149
+ ## Auto-progress from `git commit`
150
+
151
+ A PostToolUse hook listens for `git commit`. When it fires, it spawns the agent's native LLM in the background with the commit diff + the list of the current user's open goals. The LLM reads each goal + its KPIs, judges whether the commit advanced any KPI, and edits the relevant KPI file to bump `current:`. This is fire-and-forget; the user does not block on it.
152
+
153
+ To disable globally: `HIVEMIND_AUTO_KPI_FROM_COMMITS=false`.
154
+
155
+ ## Team visibility
156
+
157
+ Every write goes to a team-shared table on Deeplake (`hivemind_goals` or `hivemind_kpis`). Other team members see your goals in their SessionStart context and via direct `ls` / `cat` on the same paths in their own VFS. No explicit sharing step needed.
@@ -98,6 +98,22 @@ function loadConfig() {
98
98
  tableName: process.env.HIVEMIND_TABLE ?? "memory",
99
99
  sessionsTableName: process.env.HIVEMIND_SESSIONS_TABLE ?? "sessions",
100
100
  skillsTableName: process.env.HIVEMIND_SKILLS_TABLE ?? "skills",
101
+ // Defaults match the table name written into the SQL — keep aligned
102
+ // with RULES_COLUMNS / TASKS_COLUMNS / TASK_EVENTS_COLUMNS in
103
+ // deeplake-schema.ts and with the e2e test-org override convention
104
+ // (memory_test / sessions_test → goals_test, etc.) documented in
105
+ // CLAUDE.md.
106
+ rulesTableName: process.env.HIVEMIND_RULES_TABLE ?? "hivemind_rules",
107
+ tasksTableName: process.env.HIVEMIND_TASKS_TABLE ?? "hivemind_tasks",
108
+ taskEventsTableName: process.env.HIVEMIND_TASK_EVENTS_TABLE ?? "hivemind_task_events",
109
+ // Goals + KPIs (refined design — VFS path classifier maps
110
+ // memory/goal/<user>/<status>/<uuid>.md → hivemind_goals row
111
+ // memory/kpi/<uuid>/<kpi_id>.md → hivemind_kpis row
112
+ // See src/shell/deeplake-fs.ts for the translation logic and
113
+ // GOALS_COLUMNS / KPIS_COLUMNS in deeplake-schema.ts for the
114
+ // table shape.
115
+ goalsTableName: process.env.HIVEMIND_GOALS_TABLE ?? "hivemind_goals",
116
+ kpisTableName: process.env.HIVEMIND_KPIS_TABLE ?? "hivemind_kpis",
101
117
  codebaseTableName: process.env.HIVEMIND_CODEBASE_TABLE ?? "codebase",
102
118
  memoryPath: process.env.HIVEMIND_MEMORY_PATH ?? join(home, ".deeplake", "memory")
103
119
  };
@@ -199,6 +215,65 @@ var SKILLS_COLUMNS = Object.freeze([
199
215
  { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
200
216
  { name: "updated_at", sql: "TEXT NOT NULL DEFAULT ''" }
201
217
  ]);
218
+ var RULES_COLUMNS = Object.freeze([
219
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
220
+ { name: "rule_id", sql: "TEXT NOT NULL DEFAULT ''" },
221
+ { name: "text", sql: "TEXT NOT NULL DEFAULT ''" },
222
+ { name: "scope", sql: "TEXT NOT NULL DEFAULT 'team'" },
223
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'active'" },
224
+ { name: "assigned_by", sql: "TEXT NOT NULL DEFAULT ''" },
225
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
226
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
227
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
228
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
229
+ ]);
230
+ var TASKS_COLUMNS = Object.freeze([
231
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
232
+ { name: "task_id", sql: "TEXT NOT NULL DEFAULT ''" },
233
+ { name: "text", sql: "TEXT NOT NULL DEFAULT ''" },
234
+ { name: "scope", sql: "TEXT NOT NULL DEFAULT 'me'" },
235
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'active'" },
236
+ { name: "assigned_to", sql: "TEXT NOT NULL DEFAULT ''" },
237
+ { name: "assigned_by", sql: "TEXT NOT NULL DEFAULT ''" },
238
+ { name: "kpis", sql: "JSONB" },
239
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
240
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
241
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
242
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
243
+ ]);
244
+ var TASK_EVENTS_COLUMNS = Object.freeze([
245
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
246
+ { name: "task_id", sql: "TEXT NOT NULL DEFAULT ''" },
247
+ { name: "task_version", sql: "BIGINT NOT NULL DEFAULT 1" },
248
+ { name: "kpi_id", sql: "TEXT NOT NULL DEFAULT ''" },
249
+ { name: "value", sql: "BIGINT NOT NULL DEFAULT 0" },
250
+ { name: "note", sql: "TEXT NOT NULL DEFAULT ''" },
251
+ { name: "source", sql: "TEXT NOT NULL DEFAULT 'user'" },
252
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT ''" },
253
+ { name: "ts", sql: "TEXT NOT NULL DEFAULT ''" },
254
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
255
+ ]);
256
+ var GOALS_COLUMNS = Object.freeze([
257
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
258
+ { name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
259
+ { name: "owner", sql: "TEXT NOT NULL DEFAULT ''" },
260
+ { name: "status", sql: "TEXT NOT NULL DEFAULT 'opened'" },
261
+ { name: "content", sql: "TEXT NOT NULL DEFAULT ''" },
262
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
263
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
264
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
265
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
266
+ ]);
267
+ var KPIS_COLUMNS = Object.freeze([
268
+ { name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
269
+ { name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
270
+ { name: "kpi_id", sql: "TEXT NOT NULL DEFAULT ''" },
271
+ { name: "content", sql: "TEXT NOT NULL DEFAULT ''" },
272
+ { name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
273
+ { name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
274
+ { name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
275
+ { name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
276
+ ]);
202
277
  function validateSchema(label, cols) {
203
278
  const seen = /* @__PURE__ */ new Set();
204
279
  for (const col of cols) {
@@ -242,6 +317,11 @@ var CODEBASE_COLUMNS = Object.freeze([
242
317
  validateSchema("MEMORY_COLUMNS", MEMORY_COLUMNS);
243
318
  validateSchema("SESSIONS_COLUMNS", SESSIONS_COLUMNS);
244
319
  validateSchema("SKILLS_COLUMNS", SKILLS_COLUMNS);
320
+ validateSchema("RULES_COLUMNS", RULES_COLUMNS);
321
+ validateSchema("TASKS_COLUMNS", TASKS_COLUMNS);
322
+ validateSchema("TASK_EVENTS_COLUMNS", TASK_EVENTS_COLUMNS);
323
+ validateSchema("GOALS_COLUMNS", GOALS_COLUMNS);
324
+ validateSchema("KPIS_COLUMNS", KPIS_COLUMNS);
245
325
  validateSchema("CODEBASE_COLUMNS", CODEBASE_COLUMNS);
246
326
  function buildCreateTableSql(tableName, cols) {
247
327
  const safe = sqlIdent(tableName);
@@ -838,6 +918,111 @@ var DeeplakeApi = class {
838
918
  await this.healSchema(safe, SKILLS_COLUMNS);
839
919
  await this.ensureLookupIndex(safe, "project_key_name", `("project_key", "name")`);
840
920
  }
921
+ /**
922
+ * Create the rules table.
923
+ *
924
+ * One row per rule version (same write pattern as skills): edits INSERT
925
+ * a fresh row with version+1, reads pick latest per rule_id via
926
+ * `ORDER BY version DESC LIMIT 1`. Sidesteps the Deeplake
927
+ * UPDATE-coalescing quirk by never UPDATEing.
928
+ */
929
+ async ensureRulesTable(name) {
930
+ const safe = sqlIdent(name);
931
+ const tables = await this.listTables();
932
+ if (!tables.includes(safe)) {
933
+ log3(`table "${safe}" not found, creating`);
934
+ await this.createTableWithRetry(buildCreateTableSql(safe, RULES_COLUMNS), safe);
935
+ log3(`table "${safe}" created`);
936
+ if (!tables.includes(safe))
937
+ this._tablesCache = [...tables, safe];
938
+ }
939
+ await this.healSchema(safe, RULES_COLUMNS);
940
+ await this.ensureLookupIndex(safe, "rule_id_version", `("rule_id", "version")`);
941
+ }
942
+ /**
943
+ * Create the tasks table.
944
+ *
945
+ * Same write pattern as rules + skills. `kpis` is a nullable JSONB
946
+ * column with the agent's KPI metadata; KPI current values come from
947
+ * `task_events` (SUM(value)), not this snapshot.
948
+ */
949
+ async ensureTasksTable(name) {
950
+ const safe = sqlIdent(name);
951
+ const tables = await this.listTables();
952
+ if (!tables.includes(safe)) {
953
+ log3(`table "${safe}" not found, creating`);
954
+ await this.createTableWithRetry(buildCreateTableSql(safe, TASKS_COLUMNS), safe);
955
+ log3(`table "${safe}" created`);
956
+ if (!tables.includes(safe))
957
+ this._tablesCache = [...tables, safe];
958
+ }
959
+ await this.healSchema(safe, TASKS_COLUMNS);
960
+ await this.ensureLookupIndex(safe, "task_id_version", `("task_id", "version")`);
961
+ }
962
+ /**
963
+ * Create the task-events table.
964
+ *
965
+ * Append-only. Every INSERT is a fresh row; never UPDATE. KPI current
966
+ * value is `SUM(value) WHERE task_id=? AND kpi_id=?`. Index on
967
+ * (task_id, kpi_id) is the canonical aggregation key.
968
+ */
969
+ async ensureTaskEventsTable(name) {
970
+ const safe = sqlIdent(name);
971
+ const tables = await this.listTables();
972
+ if (!tables.includes(safe)) {
973
+ log3(`table "${safe}" not found, creating`);
974
+ await this.createTableWithRetry(buildCreateTableSql(safe, TASK_EVENTS_COLUMNS), safe);
975
+ log3(`table "${safe}" created`);
976
+ if (!tables.includes(safe))
977
+ this._tablesCache = [...tables, safe];
978
+ }
979
+ await this.healSchema(safe, TASK_EVENTS_COLUMNS);
980
+ await this.ensureLookupIndex(safe, "task_id_kpi_id", `("task_id", "kpi_id")`);
981
+ }
982
+ /**
983
+ * Create the goals table.
984
+ *
985
+ * Backed by the VFS path convention memory/goal/<owner>/<status>/<goal_id>.md.
986
+ * INSERT-only version-bumped: rm and mv operations translate to fresh
987
+ * v=N+1 rows (status flips for mv → closed; rm is the same soft-close).
988
+ * The (goal_id, version) index lets the VFS dispatch a cheap latest-row
989
+ * read on cat / Read of a single goal.
990
+ */
991
+ async ensureGoalsTable(name) {
992
+ const safe = sqlIdent(name);
993
+ const tables = await this.listTables();
994
+ if (!tables.includes(safe)) {
995
+ log3(`table "${safe}" not found, creating`);
996
+ await this.createTableWithRetry(buildCreateTableSql(safe, GOALS_COLUMNS), safe);
997
+ log3(`table "${safe}" created`);
998
+ if (!tables.includes(safe))
999
+ this._tablesCache = [...tables, safe];
1000
+ }
1001
+ await this.healSchema(safe, GOALS_COLUMNS);
1002
+ await this.ensureLookupIndex(safe, "goal_id_version", `("goal_id", "version")`);
1003
+ await this.ensureLookupIndex(safe, "owner_status", `("owner", "status")`);
1004
+ }
1005
+ /**
1006
+ * Create the kpis table.
1007
+ *
1008
+ * Backed by memory/kpi/<goal_id>/<kpi_id>.md. KPI rows do NOT carry
1009
+ * owner — ownership derives from the parent goal via logical join on
1010
+ * goal_id. INSERT-only version-bumped. (goal_id, kpi_id) index is the
1011
+ * canonical lookup the VFS uses on Read and Write.
1012
+ */
1013
+ async ensureKpisTable(name) {
1014
+ const safe = sqlIdent(name);
1015
+ const tables = await this.listTables();
1016
+ if (!tables.includes(safe)) {
1017
+ log3(`table "${safe}" not found, creating`);
1018
+ await this.createTableWithRetry(buildCreateTableSql(safe, KPIS_COLUMNS), safe);
1019
+ log3(`table "${safe}" created`);
1020
+ if (!tables.includes(safe))
1021
+ this._tablesCache = [...tables, safe];
1022
+ }
1023
+ await this.healSchema(safe, KPIS_COLUMNS);
1024
+ await this.ensureLookupIndex(safe, "goal_id_kpi_id", `("goal_id", "kpi_id")`);
1025
+ }
841
1026
  };
842
1027
 
843
1028
  // dist/src/utils/session-path.js