@deeplake/hivemind 0.7.45 → 0.7.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +64 -0
- package/bundle/cli.js +22702 -7775
- package/codex/bundle/capture.js +228 -0
- package/codex/bundle/commands/auth-login.js +228 -0
- package/codex/bundle/graph-pull-worker.js +1370 -0
- package/codex/bundle/pre-tool-use.js +228 -0
- package/codex/bundle/session-start-setup.js +228 -0
- package/codex/bundle/session-start.js +255 -4
- package/codex/bundle/shell/deeplake-shell.js +1028 -28
- package/codex/bundle/skillify-worker.js +94 -3
- package/codex/bundle/stop.js +282 -50
- package/codex/skills/hivemind-goals/SKILL.md +157 -0
- package/cursor/bundle/capture.js +282 -50
- package/cursor/bundle/commands/auth-login.js +228 -0
- package/cursor/bundle/graph-pull-worker.js +1370 -0
- package/cursor/bundle/pre-tool-use.js +228 -0
- package/cursor/bundle/session-end.js +65 -44
- package/cursor/bundle/session-start.js +662 -6
- package/cursor/bundle/shell/deeplake-shell.js +1028 -28
- package/cursor/bundle/skillify-worker.js +94 -3
- package/hermes/bundle/capture.js +282 -50
- package/hermes/bundle/commands/auth-login.js +228 -0
- package/hermes/bundle/graph-pull-worker.js +1370 -0
- package/hermes/bundle/pre-tool-use.js +228 -0
- package/hermes/bundle/session-end.js +65 -44
- package/hermes/bundle/session-start.js +662 -6
- package/hermes/bundle/shell/deeplake-shell.js +1028 -28
- package/hermes/bundle/skillify-worker.js +94 -3
- package/mcp/bundle/server.js +228 -0
- package/openclaw/dist/chunks/config-FH6JYSJW.js +53 -0
- package/openclaw/dist/index.js +307 -2
- package/openclaw/dist/skillify-worker.js +94 -3
- 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 +4 -1
- package/openclaw/dist/chunks/config-XEK4MJJS.js +0 -36
|
@@ -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
|
@@ -54,13 +54,13 @@ var init_index_marker_store = __esm({
|
|
|
54
54
|
|
|
55
55
|
// dist/src/utils/stdin.js
|
|
56
56
|
function readStdin() {
|
|
57
|
-
return new Promise((
|
|
57
|
+
return new Promise((resolve3, reject) => {
|
|
58
58
|
let data = "";
|
|
59
59
|
process.stdin.setEncoding("utf-8");
|
|
60
60
|
process.stdin.on("data", (chunk) => data += chunk);
|
|
61
61
|
process.stdin.on("end", () => {
|
|
62
62
|
try {
|
|
63
|
-
|
|
63
|
+
resolve3(JSON.parse(data));
|
|
64
64
|
} catch (err) {
|
|
65
65
|
reject(new Error(`Failed to parse hook input: ${err}`));
|
|
66
66
|
}
|
|
@@ -98,6 +98,23 @@ 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",
|
|
117
|
+
codebaseTableName: process.env.HIVEMIND_CODEBASE_TABLE ?? "codebase",
|
|
101
118
|
memoryPath: process.env.HIVEMIND_MEMORY_PATH ?? join(home, ".deeplake", "memory")
|
|
102
119
|
};
|
|
103
120
|
}
|
|
@@ -198,6 +215,65 @@ var SKILLS_COLUMNS = Object.freeze([
|
|
|
198
215
|
{ name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
199
216
|
{ name: "updated_at", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
200
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
|
+
]);
|
|
201
277
|
function validateSchema(label, cols) {
|
|
202
278
|
const seen = /* @__PURE__ */ new Set();
|
|
203
279
|
for (const col of cols) {
|
|
@@ -215,9 +291,38 @@ function validateSchema(label, cols) {
|
|
|
215
291
|
}
|
|
216
292
|
}
|
|
217
293
|
}
|
|
294
|
+
var CODEBASE_COLUMNS = Object.freeze([
|
|
295
|
+
// Identity key (matches the PK below)
|
|
296
|
+
{ name: "org_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
297
|
+
{ name: "workspace_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
298
|
+
{ name: "repo_slug", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
299
|
+
{ name: "user_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
300
|
+
{ name: "worktree_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
301
|
+
{ name: "commit_sha", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
302
|
+
// Observation metadata
|
|
303
|
+
{ name: "parent_sha", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
304
|
+
{ name: "branch", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
305
|
+
{ name: "ts", sql: "TIMESTAMP" },
|
|
306
|
+
{ name: "pushed_by", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
307
|
+
// Snapshot payload
|
|
308
|
+
{ name: "snapshot_sha256", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
309
|
+
{ name: "snapshot_jsonb", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
310
|
+
{ name: "node_count", sql: "BIGINT NOT NULL DEFAULT 0" },
|
|
311
|
+
{ name: "edge_count", sql: "BIGINT NOT NULL DEFAULT 0" },
|
|
312
|
+
// Generator metadata (for drift diagnostics — what hivemind version produced this?)
|
|
313
|
+
{ name: "generator", sql: "TEXT NOT NULL DEFAULT 'hivemind-graph'" },
|
|
314
|
+
{ name: "generator_version", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
315
|
+
{ name: "schema_version", sql: "BIGINT NOT NULL DEFAULT 1" }
|
|
316
|
+
]);
|
|
218
317
|
validateSchema("MEMORY_COLUMNS", MEMORY_COLUMNS);
|
|
219
318
|
validateSchema("SESSIONS_COLUMNS", SESSIONS_COLUMNS);
|
|
220
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);
|
|
325
|
+
validateSchema("CODEBASE_COLUMNS", CODEBASE_COLUMNS);
|
|
221
326
|
function buildCreateTableSql(tableName, cols) {
|
|
222
327
|
const safe = sqlIdent(tableName);
|
|
223
328
|
const colSql = cols.map((c) => `${c.name} ${c.sql}`).join(", ");
|
|
@@ -442,7 +547,7 @@ function getQueryTimeoutMs() {
|
|
|
442
547
|
return Number(process.env.HIVEMIND_QUERY_TIMEOUT_MS ?? 1e4);
|
|
443
548
|
}
|
|
444
549
|
function sleep2(ms) {
|
|
445
|
-
return new Promise((
|
|
550
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
446
551
|
}
|
|
447
552
|
function isTimeoutError(error) {
|
|
448
553
|
const name = error instanceof Error ? error.name.toLowerCase() : "";
|
|
@@ -472,7 +577,7 @@ var Semaphore = class {
|
|
|
472
577
|
this.active++;
|
|
473
578
|
return;
|
|
474
579
|
}
|
|
475
|
-
await new Promise((
|
|
580
|
+
await new Promise((resolve3) => this.waiting.push(resolve3));
|
|
476
581
|
}
|
|
477
582
|
release() {
|
|
478
583
|
this.active--;
|
|
@@ -782,6 +887,24 @@ var DeeplakeApi = class {
|
|
|
782
887
|
* This sidesteps the Deeplake UPDATE-coalescing quirk that bit the wiki
|
|
783
888
|
* worker.
|
|
784
889
|
*/
|
|
890
|
+
/**
|
|
891
|
+
* Create the codebase table. One row per (org, workspace, repo, user,
|
|
892
|
+
* worktree, commit) — see CODEBASE_COLUMNS for the schema. Healing
|
|
893
|
+
* + index follow the same pattern as ensureSessionsTable.
|
|
894
|
+
*/
|
|
895
|
+
async ensureCodebaseTable(name) {
|
|
896
|
+
const safe = sqlIdent(name);
|
|
897
|
+
const tables = await this.listTables();
|
|
898
|
+
if (!tables.includes(safe)) {
|
|
899
|
+
log3(`table "${safe}" not found, creating`);
|
|
900
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, CODEBASE_COLUMNS), safe);
|
|
901
|
+
log3(`table "${safe}" created`);
|
|
902
|
+
if (!tables.includes(safe))
|
|
903
|
+
this._tablesCache = [...tables, safe];
|
|
904
|
+
}
|
|
905
|
+
await this.healSchema(safe, CODEBASE_COLUMNS);
|
|
906
|
+
await this.ensureLookupIndex(safe, "codebase_identity", `("org_id", "workspace_id", "repo_slug", "user_id", "worktree_id", "commit_sha")`);
|
|
907
|
+
}
|
|
785
908
|
async ensureSkillsTable(name) {
|
|
786
909
|
const safe = sqlIdent(name);
|
|
787
910
|
const tables = await this.listTables();
|
|
@@ -795,6 +918,111 @@ var DeeplakeApi = class {
|
|
|
795
918
|
await this.healSchema(safe, SKILLS_COLUMNS);
|
|
796
919
|
await this.ensureLookupIndex(safe, "project_key_name", `("project_key", "name")`);
|
|
797
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
|
+
}
|
|
798
1026
|
};
|
|
799
1027
|
|
|
800
1028
|
// dist/src/utils/session-path.js
|
|
@@ -1057,7 +1285,7 @@ var EmbedClient = class {
|
|
|
1057
1285
|
}
|
|
1058
1286
|
}
|
|
1059
1287
|
connectOnce() {
|
|
1060
|
-
return new Promise((
|
|
1288
|
+
return new Promise((resolve3, reject) => {
|
|
1061
1289
|
const sock = connect(this.socketPath);
|
|
1062
1290
|
const to = setTimeout(() => {
|
|
1063
1291
|
sock.destroy();
|
|
@@ -1065,7 +1293,7 @@ var EmbedClient = class {
|
|
|
1065
1293
|
}, this.timeoutMs);
|
|
1066
1294
|
sock.once("connect", () => {
|
|
1067
1295
|
clearTimeout(to);
|
|
1068
|
-
|
|
1296
|
+
resolve3(sock);
|
|
1069
1297
|
});
|
|
1070
1298
|
sock.once("error", (e) => {
|
|
1071
1299
|
clearTimeout(to);
|
|
@@ -1147,7 +1375,7 @@ var EmbedClient = class {
|
|
|
1147
1375
|
throw new Error("daemon did not become ready within spawnWaitMs");
|
|
1148
1376
|
}
|
|
1149
1377
|
sendAndWait(sock, req) {
|
|
1150
|
-
return new Promise((
|
|
1378
|
+
return new Promise((resolve3, reject) => {
|
|
1151
1379
|
let buf = "";
|
|
1152
1380
|
const to = setTimeout(() => {
|
|
1153
1381
|
sock.destroy();
|
|
@@ -1162,7 +1390,7 @@ var EmbedClient = class {
|
|
|
1162
1390
|
const line = buf.slice(0, nl);
|
|
1163
1391
|
clearTimeout(to);
|
|
1164
1392
|
try {
|
|
1165
|
-
|
|
1393
|
+
resolve3(JSON.parse(line));
|
|
1166
1394
|
} catch (e) {
|
|
1167
1395
|
reject(e);
|
|
1168
1396
|
}
|
|
@@ -1818,9 +2046,54 @@ function spawnSkillifyWorker(opts) {
|
|
|
1818
2046
|
|
|
1819
2047
|
// dist/src/skillify/state.js
|
|
1820
2048
|
import { readFileSync as readFileSync9, writeFileSync as writeFileSync8, writeSync as writeSync3, mkdirSync as mkdirSync10, renameSync as renameSync6, rmdirSync, existsSync as existsSync9, lstatSync as lstatSync2, unlinkSync as unlinkSync5, openSync as openSync4, closeSync as closeSync4 } from "node:fs";
|
|
2049
|
+
import { join as join18 } from "node:path";
|
|
2050
|
+
|
|
2051
|
+
// dist/src/utils/repo-identity.js
|
|
1821
2052
|
import { execSync as execSync2 } from "node:child_process";
|
|
1822
2053
|
import { createHash } from "node:crypto";
|
|
1823
|
-
import {
|
|
2054
|
+
import { basename as basename2, resolve as resolve2 } from "node:path";
|
|
2055
|
+
var DEFAULT_PORTS = {
|
|
2056
|
+
http: "80",
|
|
2057
|
+
https: "443",
|
|
2058
|
+
ssh: "22",
|
|
2059
|
+
git: "9418"
|
|
2060
|
+
};
|
|
2061
|
+
function normalizeGitRemoteUrl(url) {
|
|
2062
|
+
let s = url.trim();
|
|
2063
|
+
const schemeMatch = s.match(/^([a-z][a-z0-9+.-]*):\/\//i);
|
|
2064
|
+
const scheme = schemeMatch ? schemeMatch[1].toLowerCase() : null;
|
|
2065
|
+
if (schemeMatch)
|
|
2066
|
+
s = s.slice(schemeMatch[0].length);
|
|
2067
|
+
if (!scheme) {
|
|
2068
|
+
const scp = s.match(/^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/);
|
|
2069
|
+
if (scp)
|
|
2070
|
+
s = `${scp[1]}/${scp[2]}`;
|
|
2071
|
+
}
|
|
2072
|
+
s = s.replace(/^[^@/]+@/, "");
|
|
2073
|
+
if (scheme && DEFAULT_PORTS[scheme]) {
|
|
2074
|
+
s = s.replace(new RegExp(`^([^/]+):${DEFAULT_PORTS[scheme]}(/|$)`), "$1$2");
|
|
2075
|
+
}
|
|
2076
|
+
s = s.replace(/\.git\/?$/i, "");
|
|
2077
|
+
s = s.replace(/\/+$/, "");
|
|
2078
|
+
return s.toLowerCase();
|
|
2079
|
+
}
|
|
2080
|
+
function deriveProjectKey(cwd) {
|
|
2081
|
+
const absCwd = resolve2(cwd);
|
|
2082
|
+
const project = basename2(absCwd) || "unknown";
|
|
2083
|
+
let signature = null;
|
|
2084
|
+
try {
|
|
2085
|
+
const raw = execSync2("git config --get remote.origin.url", {
|
|
2086
|
+
cwd: absCwd,
|
|
2087
|
+
encoding: "utf-8",
|
|
2088
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2089
|
+
}).trim();
|
|
2090
|
+
signature = raw ? normalizeGitRemoteUrl(raw) : null;
|
|
2091
|
+
} catch {
|
|
2092
|
+
}
|
|
2093
|
+
const input = signature ?? absCwd;
|
|
2094
|
+
const key = createHash("sha1").update(input).digest("hex").slice(0, 16);
|
|
2095
|
+
return { key, project };
|
|
2096
|
+
}
|
|
1824
2097
|
|
|
1825
2098
|
// dist/src/skillify/legacy-migration.js
|
|
1826
2099
|
import { existsSync as existsSync8, renameSync as renameSync5 } from "node:fs";
|
|
@@ -1875,47 +2148,6 @@ function statePath2(projectKey) {
|
|
|
1875
2148
|
function lockPath3(projectKey) {
|
|
1876
2149
|
return join18(getStateDir(), `${projectKey}.lock`);
|
|
1877
2150
|
}
|
|
1878
|
-
var DEFAULT_PORTS = {
|
|
1879
|
-
http: "80",
|
|
1880
|
-
https: "443",
|
|
1881
|
-
ssh: "22",
|
|
1882
|
-
git: "9418"
|
|
1883
|
-
};
|
|
1884
|
-
function normalizeGitRemoteUrl(url) {
|
|
1885
|
-
let s = url.trim();
|
|
1886
|
-
const schemeMatch = s.match(/^([a-z][a-z0-9+.-]*):\/\//i);
|
|
1887
|
-
const scheme = schemeMatch ? schemeMatch[1].toLowerCase() : null;
|
|
1888
|
-
if (schemeMatch)
|
|
1889
|
-
s = s.slice(schemeMatch[0].length);
|
|
1890
|
-
if (!scheme) {
|
|
1891
|
-
const scp = s.match(/^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/);
|
|
1892
|
-
if (scp)
|
|
1893
|
-
s = `${scp[1]}/${scp[2]}`;
|
|
1894
|
-
}
|
|
1895
|
-
s = s.replace(/^[^@/]+@/, "");
|
|
1896
|
-
if (scheme && DEFAULT_PORTS[scheme]) {
|
|
1897
|
-
s = s.replace(new RegExp(`^([^/]+):${DEFAULT_PORTS[scheme]}(/|$)`), "$1$2");
|
|
1898
|
-
}
|
|
1899
|
-
s = s.replace(/\.git\/?$/i, "");
|
|
1900
|
-
s = s.replace(/\/+$/, "");
|
|
1901
|
-
return s.toLowerCase();
|
|
1902
|
-
}
|
|
1903
|
-
function deriveProjectKey(cwd) {
|
|
1904
|
-
const project = basename2(cwd) || "unknown";
|
|
1905
|
-
let signature = null;
|
|
1906
|
-
try {
|
|
1907
|
-
const raw = execSync2("git config --get remote.origin.url", {
|
|
1908
|
-
cwd,
|
|
1909
|
-
encoding: "utf-8",
|
|
1910
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
1911
|
-
}).trim();
|
|
1912
|
-
signature = raw ? normalizeGitRemoteUrl(raw) : null;
|
|
1913
|
-
} catch {
|
|
1914
|
-
}
|
|
1915
|
-
const input = signature ?? cwd;
|
|
1916
|
-
const key = createHash("sha1").update(input).digest("hex").slice(0, 16);
|
|
1917
|
-
return { key, project };
|
|
1918
|
-
}
|
|
1919
2151
|
function readState2(projectKey) {
|
|
1920
2152
|
migrateLegacyStateDir();
|
|
1921
2153
|
const p = statePath2(projectKey);
|