@deeplake/hivemind 0.7.46 → 0.7.48
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/README.md +64 -0
- package/bundle/cli.js +17288 -4219
- package/codex/bundle/capture.js +185 -0
- package/codex/bundle/commands/auth-login.js +185 -0
- package/codex/bundle/graph-pull-worker.js +185 -0
- package/codex/bundle/pre-tool-use.js +185 -0
- package/codex/bundle/session-start-setup.js +185 -0
- package/codex/bundle/session-start.js +185 -0
- package/codex/bundle/shell/deeplake-shell.js +451 -3
- package/codex/bundle/skillify-worker.js +64 -0
- package/codex/bundle/stop.js +185 -0
- package/codex/skills/hivemind-goals/SKILL.md +157 -0
- package/cursor/bundle/capture.js +185 -0
- package/cursor/bundle/commands/auth-login.js +185 -0
- package/cursor/bundle/graph-pull-worker.js +185 -0
- package/cursor/bundle/pre-tool-use.js +185 -0
- package/cursor/bundle/session-end.js +16 -0
- package/cursor/bundle/session-start.js +596 -6
- package/cursor/bundle/shell/deeplake-shell.js +451 -3
- package/cursor/bundle/skillify-worker.js +64 -0
- package/hermes/bundle/capture.js +185 -0
- package/hermes/bundle/commands/auth-login.js +185 -0
- package/hermes/bundle/graph-pull-worker.js +185 -0
- package/hermes/bundle/pre-tool-use.js +185 -0
- package/hermes/bundle/session-end.js +16 -0
- package/hermes/bundle/session-start.js +596 -6
- package/hermes/bundle/shell/deeplake-shell.js +451 -3
- package/hermes/bundle/skillify-worker.js +64 -0
- package/mcp/bundle/server.js +185 -0
- package/openclaw/dist/chunks/{config-O5PDJQ7Y.js → config-FH6JYSJW.js} +16 -0
- package/openclaw/dist/index.js +262 -2
- package/openclaw/dist/skillify-worker.js +64 -0
- package/openclaw/openclaw.plugin.json +4 -2
- package/openclaw/package.json +1 -1
- package/openclaw/skills/hivemind-goals/SKILL.md +30 -0
- package/package.json +2 -1
package/codex/bundle/stop.js
CHANGED
|
@@ -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.
|
package/cursor/bundle/capture.js
CHANGED
|
@@ -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
|