@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.
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +64 -0
- package/bundle/cli.js +17230 -4177
- 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/hermes/bundle/capture.js
CHANGED
|
@@ -97,6 +97,22 @@ function loadConfig() {
|
|
|
97
97
|
tableName: process.env.HIVEMIND_TABLE ?? "memory",
|
|
98
98
|
sessionsTableName: process.env.HIVEMIND_SESSIONS_TABLE ?? "sessions",
|
|
99
99
|
skillsTableName: process.env.HIVEMIND_SKILLS_TABLE ?? "skills",
|
|
100
|
+
// Defaults match the table name written into the SQL — keep aligned
|
|
101
|
+
// with RULES_COLUMNS / TASKS_COLUMNS / TASK_EVENTS_COLUMNS in
|
|
102
|
+
// deeplake-schema.ts and with the e2e test-org override convention
|
|
103
|
+
// (memory_test / sessions_test → goals_test, etc.) documented in
|
|
104
|
+
// CLAUDE.md.
|
|
105
|
+
rulesTableName: process.env.HIVEMIND_RULES_TABLE ?? "hivemind_rules",
|
|
106
|
+
tasksTableName: process.env.HIVEMIND_TASKS_TABLE ?? "hivemind_tasks",
|
|
107
|
+
taskEventsTableName: process.env.HIVEMIND_TASK_EVENTS_TABLE ?? "hivemind_task_events",
|
|
108
|
+
// Goals + KPIs (refined design — VFS path classifier maps
|
|
109
|
+
// memory/goal/<user>/<status>/<uuid>.md → hivemind_goals row
|
|
110
|
+
// memory/kpi/<uuid>/<kpi_id>.md → hivemind_kpis row
|
|
111
|
+
// See src/shell/deeplake-fs.ts for the translation logic and
|
|
112
|
+
// GOALS_COLUMNS / KPIS_COLUMNS in deeplake-schema.ts for the
|
|
113
|
+
// table shape.
|
|
114
|
+
goalsTableName: process.env.HIVEMIND_GOALS_TABLE ?? "hivemind_goals",
|
|
115
|
+
kpisTableName: process.env.HIVEMIND_KPIS_TABLE ?? "hivemind_kpis",
|
|
100
116
|
codebaseTableName: process.env.HIVEMIND_CODEBASE_TABLE ?? "codebase",
|
|
101
117
|
memoryPath: process.env.HIVEMIND_MEMORY_PATH ?? join(home, ".deeplake", "memory")
|
|
102
118
|
};
|
|
@@ -198,6 +214,65 @@ var SKILLS_COLUMNS = Object.freeze([
|
|
|
198
214
|
{ name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
199
215
|
{ name: "updated_at", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
200
216
|
]);
|
|
217
|
+
var RULES_COLUMNS = Object.freeze([
|
|
218
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
219
|
+
{ name: "rule_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
220
|
+
{ name: "text", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
221
|
+
{ name: "scope", sql: "TEXT NOT NULL DEFAULT 'team'" },
|
|
222
|
+
{ name: "status", sql: "TEXT NOT NULL DEFAULT 'active'" },
|
|
223
|
+
{ name: "assigned_by", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
224
|
+
{ name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
|
|
225
|
+
{ name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
226
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
|
|
227
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
228
|
+
]);
|
|
229
|
+
var TASKS_COLUMNS = Object.freeze([
|
|
230
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
231
|
+
{ name: "task_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
232
|
+
{ name: "text", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
233
|
+
{ name: "scope", sql: "TEXT NOT NULL DEFAULT 'me'" },
|
|
234
|
+
{ name: "status", sql: "TEXT NOT NULL DEFAULT 'active'" },
|
|
235
|
+
{ name: "assigned_to", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
236
|
+
{ name: "assigned_by", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
237
|
+
{ name: "kpis", sql: "JSONB" },
|
|
238
|
+
{ name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
|
|
239
|
+
{ name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
240
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
|
|
241
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
242
|
+
]);
|
|
243
|
+
var TASK_EVENTS_COLUMNS = Object.freeze([
|
|
244
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
245
|
+
{ name: "task_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
246
|
+
{ name: "task_version", sql: "BIGINT NOT NULL DEFAULT 1" },
|
|
247
|
+
{ name: "kpi_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
248
|
+
{ name: "value", sql: "BIGINT NOT NULL DEFAULT 0" },
|
|
249
|
+
{ name: "note", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
250
|
+
{ name: "source", sql: "TEXT NOT NULL DEFAULT 'user'" },
|
|
251
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
252
|
+
{ name: "ts", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
253
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
254
|
+
]);
|
|
255
|
+
var GOALS_COLUMNS = Object.freeze([
|
|
256
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
257
|
+
{ name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
258
|
+
{ name: "owner", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
259
|
+
{ name: "status", sql: "TEXT NOT NULL DEFAULT 'opened'" },
|
|
260
|
+
{ name: "content", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
261
|
+
{ name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
|
|
262
|
+
{ name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
263
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
|
|
264
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
265
|
+
]);
|
|
266
|
+
var KPIS_COLUMNS = Object.freeze([
|
|
267
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
268
|
+
{ name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
269
|
+
{ name: "kpi_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
270
|
+
{ name: "content", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
271
|
+
{ name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
|
|
272
|
+
{ name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
273
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
|
|
274
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
275
|
+
]);
|
|
201
276
|
function validateSchema(label, cols) {
|
|
202
277
|
const seen = /* @__PURE__ */ new Set();
|
|
203
278
|
for (const col of cols) {
|
|
@@ -241,6 +316,11 @@ var CODEBASE_COLUMNS = Object.freeze([
|
|
|
241
316
|
validateSchema("MEMORY_COLUMNS", MEMORY_COLUMNS);
|
|
242
317
|
validateSchema("SESSIONS_COLUMNS", SESSIONS_COLUMNS);
|
|
243
318
|
validateSchema("SKILLS_COLUMNS", SKILLS_COLUMNS);
|
|
319
|
+
validateSchema("RULES_COLUMNS", RULES_COLUMNS);
|
|
320
|
+
validateSchema("TASKS_COLUMNS", TASKS_COLUMNS);
|
|
321
|
+
validateSchema("TASK_EVENTS_COLUMNS", TASK_EVENTS_COLUMNS);
|
|
322
|
+
validateSchema("GOALS_COLUMNS", GOALS_COLUMNS);
|
|
323
|
+
validateSchema("KPIS_COLUMNS", KPIS_COLUMNS);
|
|
244
324
|
validateSchema("CODEBASE_COLUMNS", CODEBASE_COLUMNS);
|
|
245
325
|
function buildCreateTableSql(tableName, cols) {
|
|
246
326
|
const safe = sqlIdent(tableName);
|
|
@@ -837,6 +917,111 @@ var DeeplakeApi = class {
|
|
|
837
917
|
await this.healSchema(safe, SKILLS_COLUMNS);
|
|
838
918
|
await this.ensureLookupIndex(safe, "project_key_name", `("project_key", "name")`);
|
|
839
919
|
}
|
|
920
|
+
/**
|
|
921
|
+
* Create the rules table.
|
|
922
|
+
*
|
|
923
|
+
* One row per rule version (same write pattern as skills): edits INSERT
|
|
924
|
+
* a fresh row with version+1, reads pick latest per rule_id via
|
|
925
|
+
* `ORDER BY version DESC LIMIT 1`. Sidesteps the Deeplake
|
|
926
|
+
* UPDATE-coalescing quirk by never UPDATEing.
|
|
927
|
+
*/
|
|
928
|
+
async ensureRulesTable(name) {
|
|
929
|
+
const safe = sqlIdent(name);
|
|
930
|
+
const tables = await this.listTables();
|
|
931
|
+
if (!tables.includes(safe)) {
|
|
932
|
+
log3(`table "${safe}" not found, creating`);
|
|
933
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, RULES_COLUMNS), safe);
|
|
934
|
+
log3(`table "${safe}" created`);
|
|
935
|
+
if (!tables.includes(safe))
|
|
936
|
+
this._tablesCache = [...tables, safe];
|
|
937
|
+
}
|
|
938
|
+
await this.healSchema(safe, RULES_COLUMNS);
|
|
939
|
+
await this.ensureLookupIndex(safe, "rule_id_version", `("rule_id", "version")`);
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Create the tasks table.
|
|
943
|
+
*
|
|
944
|
+
* Same write pattern as rules + skills. `kpis` is a nullable JSONB
|
|
945
|
+
* column with the agent's KPI metadata; KPI current values come from
|
|
946
|
+
* `task_events` (SUM(value)), not this snapshot.
|
|
947
|
+
*/
|
|
948
|
+
async ensureTasksTable(name) {
|
|
949
|
+
const safe = sqlIdent(name);
|
|
950
|
+
const tables = await this.listTables();
|
|
951
|
+
if (!tables.includes(safe)) {
|
|
952
|
+
log3(`table "${safe}" not found, creating`);
|
|
953
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, TASKS_COLUMNS), safe);
|
|
954
|
+
log3(`table "${safe}" created`);
|
|
955
|
+
if (!tables.includes(safe))
|
|
956
|
+
this._tablesCache = [...tables, safe];
|
|
957
|
+
}
|
|
958
|
+
await this.healSchema(safe, TASKS_COLUMNS);
|
|
959
|
+
await this.ensureLookupIndex(safe, "task_id_version", `("task_id", "version")`);
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Create the task-events table.
|
|
963
|
+
*
|
|
964
|
+
* Append-only. Every INSERT is a fresh row; never UPDATE. KPI current
|
|
965
|
+
* value is `SUM(value) WHERE task_id=? AND kpi_id=?`. Index on
|
|
966
|
+
* (task_id, kpi_id) is the canonical aggregation key.
|
|
967
|
+
*/
|
|
968
|
+
async ensureTaskEventsTable(name) {
|
|
969
|
+
const safe = sqlIdent(name);
|
|
970
|
+
const tables = await this.listTables();
|
|
971
|
+
if (!tables.includes(safe)) {
|
|
972
|
+
log3(`table "${safe}" not found, creating`);
|
|
973
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, TASK_EVENTS_COLUMNS), safe);
|
|
974
|
+
log3(`table "${safe}" created`);
|
|
975
|
+
if (!tables.includes(safe))
|
|
976
|
+
this._tablesCache = [...tables, safe];
|
|
977
|
+
}
|
|
978
|
+
await this.healSchema(safe, TASK_EVENTS_COLUMNS);
|
|
979
|
+
await this.ensureLookupIndex(safe, "task_id_kpi_id", `("task_id", "kpi_id")`);
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* Create the goals table.
|
|
983
|
+
*
|
|
984
|
+
* Backed by the VFS path convention memory/goal/<owner>/<status>/<goal_id>.md.
|
|
985
|
+
* INSERT-only version-bumped: rm and mv operations translate to fresh
|
|
986
|
+
* v=N+1 rows (status flips for mv → closed; rm is the same soft-close).
|
|
987
|
+
* The (goal_id, version) index lets the VFS dispatch a cheap latest-row
|
|
988
|
+
* read on cat / Read of a single goal.
|
|
989
|
+
*/
|
|
990
|
+
async ensureGoalsTable(name) {
|
|
991
|
+
const safe = sqlIdent(name);
|
|
992
|
+
const tables = await this.listTables();
|
|
993
|
+
if (!tables.includes(safe)) {
|
|
994
|
+
log3(`table "${safe}" not found, creating`);
|
|
995
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, GOALS_COLUMNS), safe);
|
|
996
|
+
log3(`table "${safe}" created`);
|
|
997
|
+
if (!tables.includes(safe))
|
|
998
|
+
this._tablesCache = [...tables, safe];
|
|
999
|
+
}
|
|
1000
|
+
await this.healSchema(safe, GOALS_COLUMNS);
|
|
1001
|
+
await this.ensureLookupIndex(safe, "goal_id_version", `("goal_id", "version")`);
|
|
1002
|
+
await this.ensureLookupIndex(safe, "owner_status", `("owner", "status")`);
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Create the kpis table.
|
|
1006
|
+
*
|
|
1007
|
+
* Backed by memory/kpi/<goal_id>/<kpi_id>.md. KPI rows do NOT carry
|
|
1008
|
+
* owner — ownership derives from the parent goal via logical join on
|
|
1009
|
+
* goal_id. INSERT-only version-bumped. (goal_id, kpi_id) index is the
|
|
1010
|
+
* canonical lookup the VFS uses on Read and Write.
|
|
1011
|
+
*/
|
|
1012
|
+
async ensureKpisTable(name) {
|
|
1013
|
+
const safe = sqlIdent(name);
|
|
1014
|
+
const tables = await this.listTables();
|
|
1015
|
+
if (!tables.includes(safe)) {
|
|
1016
|
+
log3(`table "${safe}" not found, creating`);
|
|
1017
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, KPIS_COLUMNS), safe);
|
|
1018
|
+
log3(`table "${safe}" created`);
|
|
1019
|
+
if (!tables.includes(safe))
|
|
1020
|
+
this._tablesCache = [...tables, safe];
|
|
1021
|
+
}
|
|
1022
|
+
await this.healSchema(safe, KPIS_COLUMNS);
|
|
1023
|
+
await this.ensureLookupIndex(safe, "goal_id_kpi_id", `("goal_id", "kpi_id")`);
|
|
1024
|
+
}
|
|
840
1025
|
};
|
|
841
1026
|
|
|
842
1027
|
// dist/src/utils/session-path.js
|
|
@@ -384,6 +384,22 @@ function loadConfig() {
|
|
|
384
384
|
tableName: process.env.HIVEMIND_TABLE ?? "memory",
|
|
385
385
|
sessionsTableName: process.env.HIVEMIND_SESSIONS_TABLE ?? "sessions",
|
|
386
386
|
skillsTableName: process.env.HIVEMIND_SKILLS_TABLE ?? "skills",
|
|
387
|
+
// Defaults match the table name written into the SQL — keep aligned
|
|
388
|
+
// with RULES_COLUMNS / TASKS_COLUMNS / TASK_EVENTS_COLUMNS in
|
|
389
|
+
// deeplake-schema.ts and with the e2e test-org override convention
|
|
390
|
+
// (memory_test / sessions_test → goals_test, etc.) documented in
|
|
391
|
+
// CLAUDE.md.
|
|
392
|
+
rulesTableName: process.env.HIVEMIND_RULES_TABLE ?? "hivemind_rules",
|
|
393
|
+
tasksTableName: process.env.HIVEMIND_TASKS_TABLE ?? "hivemind_tasks",
|
|
394
|
+
taskEventsTableName: process.env.HIVEMIND_TASK_EVENTS_TABLE ?? "hivemind_task_events",
|
|
395
|
+
// Goals + KPIs (refined design — VFS path classifier maps
|
|
396
|
+
// memory/goal/<user>/<status>/<uuid>.md → hivemind_goals row
|
|
397
|
+
// memory/kpi/<uuid>/<kpi_id>.md → hivemind_kpis row
|
|
398
|
+
// See src/shell/deeplake-fs.ts for the translation logic and
|
|
399
|
+
// GOALS_COLUMNS / KPIS_COLUMNS in deeplake-schema.ts for the
|
|
400
|
+
// table shape.
|
|
401
|
+
goalsTableName: process.env.HIVEMIND_GOALS_TABLE ?? "hivemind_goals",
|
|
402
|
+
kpisTableName: process.env.HIVEMIND_KPIS_TABLE ?? "hivemind_kpis",
|
|
387
403
|
codebaseTableName: process.env.HIVEMIND_CODEBASE_TABLE ?? "codebase",
|
|
388
404
|
memoryPath: process.env.HIVEMIND_MEMORY_PATH ?? join3(home, ".deeplake", "memory")
|
|
389
405
|
};
|
|
@@ -473,6 +489,65 @@ var SKILLS_COLUMNS = Object.freeze([
|
|
|
473
489
|
{ name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
474
490
|
{ name: "updated_at", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
475
491
|
]);
|
|
492
|
+
var RULES_COLUMNS = Object.freeze([
|
|
493
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
494
|
+
{ name: "rule_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
495
|
+
{ name: "text", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
496
|
+
{ name: "scope", sql: "TEXT NOT NULL DEFAULT 'team'" },
|
|
497
|
+
{ name: "status", sql: "TEXT NOT NULL DEFAULT 'active'" },
|
|
498
|
+
{ name: "assigned_by", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
499
|
+
{ name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
|
|
500
|
+
{ name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
501
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
|
|
502
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
503
|
+
]);
|
|
504
|
+
var TASKS_COLUMNS = Object.freeze([
|
|
505
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
506
|
+
{ name: "task_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
507
|
+
{ name: "text", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
508
|
+
{ name: "scope", sql: "TEXT NOT NULL DEFAULT 'me'" },
|
|
509
|
+
{ name: "status", sql: "TEXT NOT NULL DEFAULT 'active'" },
|
|
510
|
+
{ name: "assigned_to", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
511
|
+
{ name: "assigned_by", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
512
|
+
{ name: "kpis", sql: "JSONB" },
|
|
513
|
+
{ name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
|
|
514
|
+
{ name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
515
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
|
|
516
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
517
|
+
]);
|
|
518
|
+
var TASK_EVENTS_COLUMNS = Object.freeze([
|
|
519
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
520
|
+
{ name: "task_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
521
|
+
{ name: "task_version", sql: "BIGINT NOT NULL DEFAULT 1" },
|
|
522
|
+
{ name: "kpi_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
523
|
+
{ name: "value", sql: "BIGINT NOT NULL DEFAULT 0" },
|
|
524
|
+
{ name: "note", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
525
|
+
{ name: "source", sql: "TEXT NOT NULL DEFAULT 'user'" },
|
|
526
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
527
|
+
{ name: "ts", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
528
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
529
|
+
]);
|
|
530
|
+
var GOALS_COLUMNS = Object.freeze([
|
|
531
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
532
|
+
{ name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
533
|
+
{ name: "owner", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
534
|
+
{ name: "status", sql: "TEXT NOT NULL DEFAULT 'opened'" },
|
|
535
|
+
{ name: "content", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
536
|
+
{ name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
|
|
537
|
+
{ name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
538
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
|
|
539
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
540
|
+
]);
|
|
541
|
+
var KPIS_COLUMNS = Object.freeze([
|
|
542
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
543
|
+
{ name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
544
|
+
{ name: "kpi_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
545
|
+
{ name: "content", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
546
|
+
{ name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
|
|
547
|
+
{ name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
548
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
|
|
549
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
550
|
+
]);
|
|
476
551
|
function validateSchema(label, cols) {
|
|
477
552
|
const seen = /* @__PURE__ */ new Set();
|
|
478
553
|
for (const col of cols) {
|
|
@@ -516,6 +591,11 @@ var CODEBASE_COLUMNS = Object.freeze([
|
|
|
516
591
|
validateSchema("MEMORY_COLUMNS", MEMORY_COLUMNS);
|
|
517
592
|
validateSchema("SESSIONS_COLUMNS", SESSIONS_COLUMNS);
|
|
518
593
|
validateSchema("SKILLS_COLUMNS", SKILLS_COLUMNS);
|
|
594
|
+
validateSchema("RULES_COLUMNS", RULES_COLUMNS);
|
|
595
|
+
validateSchema("TASKS_COLUMNS", TASKS_COLUMNS);
|
|
596
|
+
validateSchema("TASK_EVENTS_COLUMNS", TASK_EVENTS_COLUMNS);
|
|
597
|
+
validateSchema("GOALS_COLUMNS", GOALS_COLUMNS);
|
|
598
|
+
validateSchema("KPIS_COLUMNS", KPIS_COLUMNS);
|
|
519
599
|
validateSchema("CODEBASE_COLUMNS", CODEBASE_COLUMNS);
|
|
520
600
|
function buildCreateTableSql(tableName, cols) {
|
|
521
601
|
const safe = sqlIdent(tableName);
|
|
@@ -1094,6 +1174,111 @@ var DeeplakeApi = class {
|
|
|
1094
1174
|
await this.healSchema(safe, SKILLS_COLUMNS);
|
|
1095
1175
|
await this.ensureLookupIndex(safe, "project_key_name", `("project_key", "name")`);
|
|
1096
1176
|
}
|
|
1177
|
+
/**
|
|
1178
|
+
* Create the rules table.
|
|
1179
|
+
*
|
|
1180
|
+
* One row per rule version (same write pattern as skills): edits INSERT
|
|
1181
|
+
* a fresh row with version+1, reads pick latest per rule_id via
|
|
1182
|
+
* `ORDER BY version DESC LIMIT 1`. Sidesteps the Deeplake
|
|
1183
|
+
* UPDATE-coalescing quirk by never UPDATEing.
|
|
1184
|
+
*/
|
|
1185
|
+
async ensureRulesTable(name) {
|
|
1186
|
+
const safe = sqlIdent(name);
|
|
1187
|
+
const tables = await this.listTables();
|
|
1188
|
+
if (!tables.includes(safe)) {
|
|
1189
|
+
log3(`table "${safe}" not found, creating`);
|
|
1190
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, RULES_COLUMNS), safe);
|
|
1191
|
+
log3(`table "${safe}" created`);
|
|
1192
|
+
if (!tables.includes(safe))
|
|
1193
|
+
this._tablesCache = [...tables, safe];
|
|
1194
|
+
}
|
|
1195
|
+
await this.healSchema(safe, RULES_COLUMNS);
|
|
1196
|
+
await this.ensureLookupIndex(safe, "rule_id_version", `("rule_id", "version")`);
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Create the tasks table.
|
|
1200
|
+
*
|
|
1201
|
+
* Same write pattern as rules + skills. `kpis` is a nullable JSONB
|
|
1202
|
+
* column with the agent's KPI metadata; KPI current values come from
|
|
1203
|
+
* `task_events` (SUM(value)), not this snapshot.
|
|
1204
|
+
*/
|
|
1205
|
+
async ensureTasksTable(name) {
|
|
1206
|
+
const safe = sqlIdent(name);
|
|
1207
|
+
const tables = await this.listTables();
|
|
1208
|
+
if (!tables.includes(safe)) {
|
|
1209
|
+
log3(`table "${safe}" not found, creating`);
|
|
1210
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, TASKS_COLUMNS), safe);
|
|
1211
|
+
log3(`table "${safe}" created`);
|
|
1212
|
+
if (!tables.includes(safe))
|
|
1213
|
+
this._tablesCache = [...tables, safe];
|
|
1214
|
+
}
|
|
1215
|
+
await this.healSchema(safe, TASKS_COLUMNS);
|
|
1216
|
+
await this.ensureLookupIndex(safe, "task_id_version", `("task_id", "version")`);
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Create the task-events table.
|
|
1220
|
+
*
|
|
1221
|
+
* Append-only. Every INSERT is a fresh row; never UPDATE. KPI current
|
|
1222
|
+
* value is `SUM(value) WHERE task_id=? AND kpi_id=?`. Index on
|
|
1223
|
+
* (task_id, kpi_id) is the canonical aggregation key.
|
|
1224
|
+
*/
|
|
1225
|
+
async ensureTaskEventsTable(name) {
|
|
1226
|
+
const safe = sqlIdent(name);
|
|
1227
|
+
const tables = await this.listTables();
|
|
1228
|
+
if (!tables.includes(safe)) {
|
|
1229
|
+
log3(`table "${safe}" not found, creating`);
|
|
1230
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, TASK_EVENTS_COLUMNS), safe);
|
|
1231
|
+
log3(`table "${safe}" created`);
|
|
1232
|
+
if (!tables.includes(safe))
|
|
1233
|
+
this._tablesCache = [...tables, safe];
|
|
1234
|
+
}
|
|
1235
|
+
await this.healSchema(safe, TASK_EVENTS_COLUMNS);
|
|
1236
|
+
await this.ensureLookupIndex(safe, "task_id_kpi_id", `("task_id", "kpi_id")`);
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Create the goals table.
|
|
1240
|
+
*
|
|
1241
|
+
* Backed by the VFS path convention memory/goal/<owner>/<status>/<goal_id>.md.
|
|
1242
|
+
* INSERT-only version-bumped: rm and mv operations translate to fresh
|
|
1243
|
+
* v=N+1 rows (status flips for mv → closed; rm is the same soft-close).
|
|
1244
|
+
* The (goal_id, version) index lets the VFS dispatch a cheap latest-row
|
|
1245
|
+
* read on cat / Read of a single goal.
|
|
1246
|
+
*/
|
|
1247
|
+
async ensureGoalsTable(name) {
|
|
1248
|
+
const safe = sqlIdent(name);
|
|
1249
|
+
const tables = await this.listTables();
|
|
1250
|
+
if (!tables.includes(safe)) {
|
|
1251
|
+
log3(`table "${safe}" not found, creating`);
|
|
1252
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, GOALS_COLUMNS), safe);
|
|
1253
|
+
log3(`table "${safe}" created`);
|
|
1254
|
+
if (!tables.includes(safe))
|
|
1255
|
+
this._tablesCache = [...tables, safe];
|
|
1256
|
+
}
|
|
1257
|
+
await this.healSchema(safe, GOALS_COLUMNS);
|
|
1258
|
+
await this.ensureLookupIndex(safe, "goal_id_version", `("goal_id", "version")`);
|
|
1259
|
+
await this.ensureLookupIndex(safe, "owner_status", `("owner", "status")`);
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Create the kpis table.
|
|
1263
|
+
*
|
|
1264
|
+
* Backed by memory/kpi/<goal_id>/<kpi_id>.md. KPI rows do NOT carry
|
|
1265
|
+
* owner — ownership derives from the parent goal via logical join on
|
|
1266
|
+
* goal_id. INSERT-only version-bumped. (goal_id, kpi_id) index is the
|
|
1267
|
+
* canonical lookup the VFS uses on Read and Write.
|
|
1268
|
+
*/
|
|
1269
|
+
async ensureKpisTable(name) {
|
|
1270
|
+
const safe = sqlIdent(name);
|
|
1271
|
+
const tables = await this.listTables();
|
|
1272
|
+
if (!tables.includes(safe)) {
|
|
1273
|
+
log3(`table "${safe}" not found, creating`);
|
|
1274
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, KPIS_COLUMNS), safe);
|
|
1275
|
+
log3(`table "${safe}" created`);
|
|
1276
|
+
if (!tables.includes(safe))
|
|
1277
|
+
this._tablesCache = [...tables, safe];
|
|
1278
|
+
}
|
|
1279
|
+
await this.healSchema(safe, KPIS_COLUMNS);
|
|
1280
|
+
await this.ensureLookupIndex(safe, "goal_id_kpi_id", `("goal_id", "kpi_id")`);
|
|
1281
|
+
}
|
|
1097
1282
|
};
|
|
1098
1283
|
|
|
1099
1284
|
// dist/src/cli/util.js
|
|
@@ -91,6 +91,22 @@ function loadConfig() {
|
|
|
91
91
|
tableName: process.env.HIVEMIND_TABLE ?? "memory",
|
|
92
92
|
sessionsTableName: process.env.HIVEMIND_SESSIONS_TABLE ?? "sessions",
|
|
93
93
|
skillsTableName: process.env.HIVEMIND_SKILLS_TABLE ?? "skills",
|
|
94
|
+
// Defaults match the table name written into the SQL — keep aligned
|
|
95
|
+
// with RULES_COLUMNS / TASKS_COLUMNS / TASK_EVENTS_COLUMNS in
|
|
96
|
+
// deeplake-schema.ts and with the e2e test-org override convention
|
|
97
|
+
// (memory_test / sessions_test → goals_test, etc.) documented in
|
|
98
|
+
// CLAUDE.md.
|
|
99
|
+
rulesTableName: process.env.HIVEMIND_RULES_TABLE ?? "hivemind_rules",
|
|
100
|
+
tasksTableName: process.env.HIVEMIND_TASKS_TABLE ?? "hivemind_tasks",
|
|
101
|
+
taskEventsTableName: process.env.HIVEMIND_TASK_EVENTS_TABLE ?? "hivemind_task_events",
|
|
102
|
+
// Goals + KPIs (refined design — VFS path classifier maps
|
|
103
|
+
// memory/goal/<user>/<status>/<uuid>.md → hivemind_goals row
|
|
104
|
+
// memory/kpi/<uuid>/<kpi_id>.md → hivemind_kpis row
|
|
105
|
+
// See src/shell/deeplake-fs.ts for the translation logic and
|
|
106
|
+
// GOALS_COLUMNS / KPIS_COLUMNS in deeplake-schema.ts for the
|
|
107
|
+
// table shape.
|
|
108
|
+
goalsTableName: process.env.HIVEMIND_GOALS_TABLE ?? "hivemind_goals",
|
|
109
|
+
kpisTableName: process.env.HIVEMIND_KPIS_TABLE ?? "hivemind_kpis",
|
|
94
110
|
codebaseTableName: process.env.HIVEMIND_CODEBASE_TABLE ?? "codebase",
|
|
95
111
|
memoryPath: process.env.HIVEMIND_MEMORY_PATH ?? join(home, ".deeplake", "memory")
|
|
96
112
|
};
|
|
@@ -189,6 +205,65 @@ var SKILLS_COLUMNS = Object.freeze([
|
|
|
189
205
|
{ name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
190
206
|
{ name: "updated_at", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
191
207
|
]);
|
|
208
|
+
var RULES_COLUMNS = Object.freeze([
|
|
209
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
210
|
+
{ name: "rule_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
211
|
+
{ name: "text", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
212
|
+
{ name: "scope", sql: "TEXT NOT NULL DEFAULT 'team'" },
|
|
213
|
+
{ name: "status", sql: "TEXT NOT NULL DEFAULT 'active'" },
|
|
214
|
+
{ name: "assigned_by", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
215
|
+
{ name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
|
|
216
|
+
{ name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
217
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
|
|
218
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
219
|
+
]);
|
|
220
|
+
var TASKS_COLUMNS = Object.freeze([
|
|
221
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
222
|
+
{ name: "task_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
223
|
+
{ name: "text", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
224
|
+
{ name: "scope", sql: "TEXT NOT NULL DEFAULT 'me'" },
|
|
225
|
+
{ name: "status", sql: "TEXT NOT NULL DEFAULT 'active'" },
|
|
226
|
+
{ name: "assigned_to", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
227
|
+
{ name: "assigned_by", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
228
|
+
{ name: "kpis", sql: "JSONB" },
|
|
229
|
+
{ name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
|
|
230
|
+
{ name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
231
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
|
|
232
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
233
|
+
]);
|
|
234
|
+
var TASK_EVENTS_COLUMNS = Object.freeze([
|
|
235
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
236
|
+
{ name: "task_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
237
|
+
{ name: "task_version", sql: "BIGINT NOT NULL DEFAULT 1" },
|
|
238
|
+
{ name: "kpi_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
239
|
+
{ name: "value", sql: "BIGINT NOT NULL DEFAULT 0" },
|
|
240
|
+
{ name: "note", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
241
|
+
{ name: "source", sql: "TEXT NOT NULL DEFAULT 'user'" },
|
|
242
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
243
|
+
{ name: "ts", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
244
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
245
|
+
]);
|
|
246
|
+
var GOALS_COLUMNS = Object.freeze([
|
|
247
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
248
|
+
{ name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
249
|
+
{ name: "owner", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
250
|
+
{ name: "status", sql: "TEXT NOT NULL DEFAULT 'opened'" },
|
|
251
|
+
{ name: "content", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
252
|
+
{ name: "version", sql: "BIGINT NOT NULL DEFAULT 1" },
|
|
253
|
+
{ name: "created_at", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
254
|
+
{ name: "agent", sql: "TEXT NOT NULL DEFAULT 'manual'" },
|
|
255
|
+
{ name: "plugin_version", sql: "TEXT NOT NULL DEFAULT ''" }
|
|
256
|
+
]);
|
|
257
|
+
var KPIS_COLUMNS = Object.freeze([
|
|
258
|
+
{ name: "id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
259
|
+
{ name: "goal_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
260
|
+
{ name: "kpi_id", sql: "TEXT NOT NULL DEFAULT ''" },
|
|
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
|
+
]);
|
|
192
267
|
function validateSchema(label, cols) {
|
|
193
268
|
const seen = /* @__PURE__ */ new Set();
|
|
194
269
|
for (const col of cols) {
|
|
@@ -232,6 +307,11 @@ var CODEBASE_COLUMNS = Object.freeze([
|
|
|
232
307
|
validateSchema("MEMORY_COLUMNS", MEMORY_COLUMNS);
|
|
233
308
|
validateSchema("SESSIONS_COLUMNS", SESSIONS_COLUMNS);
|
|
234
309
|
validateSchema("SKILLS_COLUMNS", SKILLS_COLUMNS);
|
|
310
|
+
validateSchema("RULES_COLUMNS", RULES_COLUMNS);
|
|
311
|
+
validateSchema("TASKS_COLUMNS", TASKS_COLUMNS);
|
|
312
|
+
validateSchema("TASK_EVENTS_COLUMNS", TASK_EVENTS_COLUMNS);
|
|
313
|
+
validateSchema("GOALS_COLUMNS", GOALS_COLUMNS);
|
|
314
|
+
validateSchema("KPIS_COLUMNS", KPIS_COLUMNS);
|
|
235
315
|
validateSchema("CODEBASE_COLUMNS", CODEBASE_COLUMNS);
|
|
236
316
|
function buildCreateTableSql(tableName, cols) {
|
|
237
317
|
const safe = sqlIdent(tableName);
|
|
@@ -828,6 +908,111 @@ var DeeplakeApi = class {
|
|
|
828
908
|
await this.healSchema(safe, SKILLS_COLUMNS);
|
|
829
909
|
await this.ensureLookupIndex(safe, "project_key_name", `("project_key", "name")`);
|
|
830
910
|
}
|
|
911
|
+
/**
|
|
912
|
+
* Create the rules table.
|
|
913
|
+
*
|
|
914
|
+
* One row per rule version (same write pattern as skills): edits INSERT
|
|
915
|
+
* a fresh row with version+1, reads pick latest per rule_id via
|
|
916
|
+
* `ORDER BY version DESC LIMIT 1`. Sidesteps the Deeplake
|
|
917
|
+
* UPDATE-coalescing quirk by never UPDATEing.
|
|
918
|
+
*/
|
|
919
|
+
async ensureRulesTable(name) {
|
|
920
|
+
const safe = sqlIdent(name);
|
|
921
|
+
const tables = await this.listTables();
|
|
922
|
+
if (!tables.includes(safe)) {
|
|
923
|
+
log3(`table "${safe}" not found, creating`);
|
|
924
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, RULES_COLUMNS), safe);
|
|
925
|
+
log3(`table "${safe}" created`);
|
|
926
|
+
if (!tables.includes(safe))
|
|
927
|
+
this._tablesCache = [...tables, safe];
|
|
928
|
+
}
|
|
929
|
+
await this.healSchema(safe, RULES_COLUMNS);
|
|
930
|
+
await this.ensureLookupIndex(safe, "rule_id_version", `("rule_id", "version")`);
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Create the tasks table.
|
|
934
|
+
*
|
|
935
|
+
* Same write pattern as rules + skills. `kpis` is a nullable JSONB
|
|
936
|
+
* column with the agent's KPI metadata; KPI current values come from
|
|
937
|
+
* `task_events` (SUM(value)), not this snapshot.
|
|
938
|
+
*/
|
|
939
|
+
async ensureTasksTable(name) {
|
|
940
|
+
const safe = sqlIdent(name);
|
|
941
|
+
const tables = await this.listTables();
|
|
942
|
+
if (!tables.includes(safe)) {
|
|
943
|
+
log3(`table "${safe}" not found, creating`);
|
|
944
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, TASKS_COLUMNS), safe);
|
|
945
|
+
log3(`table "${safe}" created`);
|
|
946
|
+
if (!tables.includes(safe))
|
|
947
|
+
this._tablesCache = [...tables, safe];
|
|
948
|
+
}
|
|
949
|
+
await this.healSchema(safe, TASKS_COLUMNS);
|
|
950
|
+
await this.ensureLookupIndex(safe, "task_id_version", `("task_id", "version")`);
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Create the task-events table.
|
|
954
|
+
*
|
|
955
|
+
* Append-only. Every INSERT is a fresh row; never UPDATE. KPI current
|
|
956
|
+
* value is `SUM(value) WHERE task_id=? AND kpi_id=?`. Index on
|
|
957
|
+
* (task_id, kpi_id) is the canonical aggregation key.
|
|
958
|
+
*/
|
|
959
|
+
async ensureTaskEventsTable(name) {
|
|
960
|
+
const safe = sqlIdent(name);
|
|
961
|
+
const tables = await this.listTables();
|
|
962
|
+
if (!tables.includes(safe)) {
|
|
963
|
+
log3(`table "${safe}" not found, creating`);
|
|
964
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, TASK_EVENTS_COLUMNS), safe);
|
|
965
|
+
log3(`table "${safe}" created`);
|
|
966
|
+
if (!tables.includes(safe))
|
|
967
|
+
this._tablesCache = [...tables, safe];
|
|
968
|
+
}
|
|
969
|
+
await this.healSchema(safe, TASK_EVENTS_COLUMNS);
|
|
970
|
+
await this.ensureLookupIndex(safe, "task_id_kpi_id", `("task_id", "kpi_id")`);
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Create the goals table.
|
|
974
|
+
*
|
|
975
|
+
* Backed by the VFS path convention memory/goal/<owner>/<status>/<goal_id>.md.
|
|
976
|
+
* INSERT-only version-bumped: rm and mv operations translate to fresh
|
|
977
|
+
* v=N+1 rows (status flips for mv → closed; rm is the same soft-close).
|
|
978
|
+
* The (goal_id, version) index lets the VFS dispatch a cheap latest-row
|
|
979
|
+
* read on cat / Read of a single goal.
|
|
980
|
+
*/
|
|
981
|
+
async ensureGoalsTable(name) {
|
|
982
|
+
const safe = sqlIdent(name);
|
|
983
|
+
const tables = await this.listTables();
|
|
984
|
+
if (!tables.includes(safe)) {
|
|
985
|
+
log3(`table "${safe}" not found, creating`);
|
|
986
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, GOALS_COLUMNS), safe);
|
|
987
|
+
log3(`table "${safe}" created`);
|
|
988
|
+
if (!tables.includes(safe))
|
|
989
|
+
this._tablesCache = [...tables, safe];
|
|
990
|
+
}
|
|
991
|
+
await this.healSchema(safe, GOALS_COLUMNS);
|
|
992
|
+
await this.ensureLookupIndex(safe, "goal_id_version", `("goal_id", "version")`);
|
|
993
|
+
await this.ensureLookupIndex(safe, "owner_status", `("owner", "status")`);
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Create the kpis table.
|
|
997
|
+
*
|
|
998
|
+
* Backed by memory/kpi/<goal_id>/<kpi_id>.md. KPI rows do NOT carry
|
|
999
|
+
* owner — ownership derives from the parent goal via logical join on
|
|
1000
|
+
* goal_id. INSERT-only version-bumped. (goal_id, kpi_id) index is the
|
|
1001
|
+
* canonical lookup the VFS uses on Read and Write.
|
|
1002
|
+
*/
|
|
1003
|
+
async ensureKpisTable(name) {
|
|
1004
|
+
const safe = sqlIdent(name);
|
|
1005
|
+
const tables = await this.listTables();
|
|
1006
|
+
if (!tables.includes(safe)) {
|
|
1007
|
+
log3(`table "${safe}" not found, creating`);
|
|
1008
|
+
await this.createTableWithRetry(buildCreateTableSql(safe, KPIS_COLUMNS), safe);
|
|
1009
|
+
log3(`table "${safe}" created`);
|
|
1010
|
+
if (!tables.includes(safe))
|
|
1011
|
+
this._tablesCache = [...tables, safe];
|
|
1012
|
+
}
|
|
1013
|
+
await this.healSchema(safe, KPIS_COLUMNS);
|
|
1014
|
+
await this.ensureLookupIndex(safe, "goal_id_kpi_id", `("goal_id", "kpi_id")`);
|
|
1015
|
+
}
|
|
831
1016
|
};
|
|
832
1017
|
|
|
833
1018
|
// dist/src/utils/repo-identity.js
|