open-think 0.2.3 → 0.2.5
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.
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node --no-warnings=ExperimentalWarning
|
|
2
|
+
|
|
3
|
+
// src/lib/paths.ts
|
|
4
|
+
import path from "path";
|
|
5
|
+
import fs from "fs";
|
|
6
|
+
function getHome() {
|
|
7
|
+
const home = process.env.HOME;
|
|
8
|
+
if (!home) {
|
|
9
|
+
throw new Error("HOME environment variable is not set");
|
|
10
|
+
}
|
|
11
|
+
return home;
|
|
12
|
+
}
|
|
13
|
+
function sanitizeName(name) {
|
|
14
|
+
if (!name || /[\/\\\.]{2}/.test(name) || /[^a-zA-Z0-9_-]/.test(name)) {
|
|
15
|
+
throw new Error(`Invalid cortex name: "${name}". Use only alphanumeric characters, hyphens, and underscores.`);
|
|
16
|
+
}
|
|
17
|
+
return name;
|
|
18
|
+
}
|
|
19
|
+
function getThinkHome() {
|
|
20
|
+
const thinkHome = process.env.THINK_HOME;
|
|
21
|
+
if (thinkHome === void 0 || thinkHome === "") return null;
|
|
22
|
+
return thinkHome;
|
|
23
|
+
}
|
|
24
|
+
function getThinkDir() {
|
|
25
|
+
return getThinkHome() ?? path.join(getHome(), ".think");
|
|
26
|
+
}
|
|
27
|
+
function getThinkConfigDir() {
|
|
28
|
+
const thinkHome = getThinkHome();
|
|
29
|
+
if (thinkHome) return path.join(thinkHome, "config");
|
|
30
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(getHome(), ".config");
|
|
31
|
+
return path.join(xdgConfig, "think");
|
|
32
|
+
}
|
|
33
|
+
function getThinkDataDir() {
|
|
34
|
+
const thinkHome = getThinkHome();
|
|
35
|
+
if (thinkHome) return path.join(thinkHome, "data");
|
|
36
|
+
const xdgData = process.env.XDG_DATA_HOME || path.join(getHome(), ".local", "share");
|
|
37
|
+
return path.join(xdgData, "think");
|
|
38
|
+
}
|
|
39
|
+
function getEngramsDir() {
|
|
40
|
+
return path.join(getThinkDir(), "engrams");
|
|
41
|
+
}
|
|
42
|
+
function getEngramDbPath(cortexName) {
|
|
43
|
+
return path.join(getEngramsDir(), `${sanitizeName(cortexName)}.db`);
|
|
44
|
+
}
|
|
45
|
+
function getRepoPath() {
|
|
46
|
+
return path.join(getThinkDir(), "repo");
|
|
47
|
+
}
|
|
48
|
+
function getLongtermDir() {
|
|
49
|
+
return path.join(getThinkDir(), "longterm");
|
|
50
|
+
}
|
|
51
|
+
function getLongtermPath(cortexName) {
|
|
52
|
+
return path.join(getLongtermDir(), `${sanitizeName(cortexName)}.md`);
|
|
53
|
+
}
|
|
54
|
+
function getCuratorMdPath() {
|
|
55
|
+
return path.join(getThinkDir(), "curator.md");
|
|
56
|
+
}
|
|
57
|
+
function ensureThinkDirs() {
|
|
58
|
+
fs.mkdirSync(getEngramsDir(), { recursive: true });
|
|
59
|
+
fs.mkdirSync(getLongtermDir(), { recursive: true });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export {
|
|
63
|
+
getThinkConfigDir,
|
|
64
|
+
getThinkDataDir,
|
|
65
|
+
getEngramsDir,
|
|
66
|
+
getEngramDbPath,
|
|
67
|
+
getRepoPath,
|
|
68
|
+
getLongtermPath,
|
|
69
|
+
getCuratorMdPath,
|
|
70
|
+
ensureThinkDirs
|
|
71
|
+
};
|
|
@@ -1,88 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node --no-warnings=ExperimentalWarning
|
|
2
|
+
import {
|
|
3
|
+
getRepoPath,
|
|
4
|
+
getThinkConfigDir
|
|
5
|
+
} from "./chunk-DCTG6IK4.js";
|
|
2
6
|
|
|
3
7
|
// src/lib/git.ts
|
|
4
8
|
import { execFileSync } from "child_process";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
9
|
+
import fs2 from "fs";
|
|
10
|
+
import path2 from "path";
|
|
7
11
|
|
|
8
|
-
// src/lib/
|
|
12
|
+
// src/lib/config.ts
|
|
9
13
|
import path from "path";
|
|
10
14
|
import fs from "fs";
|
|
11
|
-
function getHome() {
|
|
12
|
-
const home = process.env.HOME;
|
|
13
|
-
if (!home) {
|
|
14
|
-
throw new Error("HOME environment variable is not set");
|
|
15
|
-
}
|
|
16
|
-
return home;
|
|
17
|
-
}
|
|
18
|
-
function sanitizeName(name) {
|
|
19
|
-
if (!name || /[\/\\\.]{2}/.test(name) || /[^a-zA-Z0-9_-]/.test(name)) {
|
|
20
|
-
throw new Error(`Invalid cortex name: "${name}". Use only alphanumeric characters, hyphens, and underscores.`);
|
|
21
|
-
}
|
|
22
|
-
return name;
|
|
23
|
-
}
|
|
24
|
-
function getThinkHome() {
|
|
25
|
-
const thinkHome = process.env.THINK_HOME;
|
|
26
|
-
if (thinkHome === void 0 || thinkHome === "") return null;
|
|
27
|
-
return thinkHome;
|
|
28
|
-
}
|
|
29
|
-
function getThinkDir() {
|
|
30
|
-
return getThinkHome() ?? path.join(getHome(), ".think");
|
|
31
|
-
}
|
|
32
|
-
function getThinkConfigDir() {
|
|
33
|
-
const thinkHome = getThinkHome();
|
|
34
|
-
if (thinkHome) return path.join(thinkHome, "config");
|
|
35
|
-
const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(getHome(), ".config");
|
|
36
|
-
return path.join(xdgConfig, "think");
|
|
37
|
-
}
|
|
38
|
-
function getThinkDataDir() {
|
|
39
|
-
const thinkHome = getThinkHome();
|
|
40
|
-
if (thinkHome) return path.join(thinkHome, "data");
|
|
41
|
-
const xdgData = process.env.XDG_DATA_HOME || path.join(getHome(), ".local", "share");
|
|
42
|
-
return path.join(xdgData, "think");
|
|
43
|
-
}
|
|
44
|
-
function getEngramsDir() {
|
|
45
|
-
return path.join(getThinkDir(), "engrams");
|
|
46
|
-
}
|
|
47
|
-
function getEngramDbPath(cortexName) {
|
|
48
|
-
return path.join(getEngramsDir(), `${sanitizeName(cortexName)}.db`);
|
|
49
|
-
}
|
|
50
|
-
function getRepoPath() {
|
|
51
|
-
return path.join(getThinkDir(), "repo");
|
|
52
|
-
}
|
|
53
|
-
function getLongtermDir() {
|
|
54
|
-
return path.join(getThinkDir(), "longterm");
|
|
55
|
-
}
|
|
56
|
-
function getLongtermPath(cortexName) {
|
|
57
|
-
return path.join(getLongtermDir(), `${sanitizeName(cortexName)}.md`);
|
|
58
|
-
}
|
|
59
|
-
function getCuratorMdPath() {
|
|
60
|
-
return path.join(getThinkDir(), "curator.md");
|
|
61
|
-
}
|
|
62
|
-
function ensureThinkDirs() {
|
|
63
|
-
fs.mkdirSync(getEngramsDir(), { recursive: true });
|
|
64
|
-
fs.mkdirSync(getLongtermDir(), { recursive: true });
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// src/lib/config.ts
|
|
68
|
-
import path2 from "path";
|
|
69
|
-
import fs2 from "fs";
|
|
70
15
|
import { v4 as uuidv4 } from "uuid";
|
|
71
16
|
function getConfigDir() {
|
|
72
17
|
return getThinkConfigDir();
|
|
73
18
|
}
|
|
74
19
|
function configPath() {
|
|
75
|
-
return
|
|
20
|
+
return path.join(getConfigDir(), "config.json");
|
|
76
21
|
}
|
|
77
22
|
function saveConfig(config) {
|
|
78
23
|
const dir = getConfigDir();
|
|
79
|
-
|
|
80
|
-
|
|
24
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
25
|
+
fs.writeFileSync(configPath(), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
81
26
|
}
|
|
82
27
|
function getConfig() {
|
|
83
28
|
const fp = configPath();
|
|
84
|
-
if (
|
|
85
|
-
const raw =
|
|
29
|
+
if (fs.existsSync(fp)) {
|
|
30
|
+
const raw = fs.readFileSync(fp, "utf-8");
|
|
86
31
|
return JSON.parse(raw);
|
|
87
32
|
}
|
|
88
33
|
const config = {
|
|
@@ -108,14 +53,14 @@ function ensureRepoCloned() {
|
|
|
108
53
|
throw new Error("No cortex repo configured. Run: think cortex setup");
|
|
109
54
|
}
|
|
110
55
|
const repoPath = getRepoPath();
|
|
111
|
-
if (
|
|
56
|
+
if (fs2.existsSync(path2.join(repoPath, ".git"))) {
|
|
112
57
|
const remote = runGit(["remote", "get-url", "origin"], repoPath);
|
|
113
58
|
if (remote !== config.cortex.repo) {
|
|
114
59
|
throw new Error(`Repo at ${repoPath} points to ${remote}, expected ${config.cortex.repo}`);
|
|
115
60
|
}
|
|
116
61
|
return;
|
|
117
62
|
}
|
|
118
|
-
|
|
63
|
+
fs2.mkdirSync(repoPath, { recursive: true });
|
|
119
64
|
execFileSync("git", ["clone", "--no-checkout", config.cortex.repo, repoPath], {
|
|
120
65
|
encoding: "utf-8",
|
|
121
66
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -136,7 +81,7 @@ function createOrphanBranch(branchName) {
|
|
|
136
81
|
} catch {
|
|
137
82
|
}
|
|
138
83
|
const repoPath = getRepoPath();
|
|
139
|
-
|
|
84
|
+
fs2.writeFileSync(path2.join(repoPath, "000001.jsonl"), "", "utf-8");
|
|
140
85
|
runGit(["add", "000001.jsonl"]);
|
|
141
86
|
runGit(["commit", "-m", `init: create cortex ${branchName}`]);
|
|
142
87
|
runGit(["push", "--set-upstream", "origin", branchName]);
|
|
@@ -153,7 +98,7 @@ function readFileFromBranch(branchName, filePath) {
|
|
|
153
98
|
}
|
|
154
99
|
function appendAndCommit(branchName, newLines, commitMessage, maxRetries = 3, targetFile = "memories.jsonl") {
|
|
155
100
|
const repoPath = getRepoPath();
|
|
156
|
-
const filePath =
|
|
101
|
+
const filePath = path2.join(repoPath, targetFile);
|
|
157
102
|
try {
|
|
158
103
|
runGit(["switch", branchName]);
|
|
159
104
|
} catch {
|
|
@@ -172,7 +117,7 @@ function appendAndCommit(branchName, newLines, commitMessage, maxRetries = 3, ta
|
|
|
172
117
|
}
|
|
173
118
|
}
|
|
174
119
|
const content = newLines.join("\n") + "\n";
|
|
175
|
-
|
|
120
|
+
fs2.appendFileSync(filePath, content, "utf-8");
|
|
176
121
|
runGit(["add", targetFile]);
|
|
177
122
|
runGit(["commit", "-m", commitMessage]);
|
|
178
123
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
@@ -222,11 +167,11 @@ function migrateToBuckets(branchName) {
|
|
|
222
167
|
runGit(["pull", "--rebase", "origin", branchName]);
|
|
223
168
|
} catch {
|
|
224
169
|
}
|
|
225
|
-
const legacyPath =
|
|
226
|
-
const bucketPath =
|
|
227
|
-
if (
|
|
170
|
+
const legacyPath = path2.join(repoPath, "memories.jsonl");
|
|
171
|
+
const bucketPath = path2.join(repoPath, "000001.jsonl");
|
|
172
|
+
if (fs2.existsSync(legacyPath) && !fs2.existsSync(bucketPath)) {
|
|
228
173
|
const preMigrationRef = runGit(["rev-parse", "HEAD"]);
|
|
229
|
-
|
|
174
|
+
fs2.renameSync(legacyPath, bucketPath);
|
|
230
175
|
runGit(["add", "-A"]);
|
|
231
176
|
runGit(["commit", "-m", "migrate: memories.jsonl -> 000001.jsonl"]);
|
|
232
177
|
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
@@ -248,12 +193,6 @@ function migrateToBuckets(branchName) {
|
|
|
248
193
|
}
|
|
249
194
|
|
|
250
195
|
export {
|
|
251
|
-
getThinkDataDir,
|
|
252
|
-
getEngramsDir,
|
|
253
|
-
getEngramDbPath,
|
|
254
|
-
getLongtermPath,
|
|
255
|
-
getCuratorMdPath,
|
|
256
|
-
ensureThinkDirs,
|
|
257
196
|
getConfigDir,
|
|
258
197
|
saveConfig,
|
|
259
198
|
getConfig,
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/usr/bin/env node --no-warnings=ExperimentalWarning
|
|
2
|
+
import {
|
|
3
|
+
ensureThinkDirs,
|
|
4
|
+
getEngramDbPath
|
|
5
|
+
} from "./chunk-DCTG6IK4.js";
|
|
6
|
+
|
|
7
|
+
// src/db/memory-queries.ts
|
|
8
|
+
import { v7 as uuidv7 } from "uuid";
|
|
9
|
+
|
|
10
|
+
// src/db/engrams.ts
|
|
11
|
+
import { DatabaseSync } from "node:sqlite";
|
|
12
|
+
|
|
13
|
+
// src/db/migrate.ts
|
|
14
|
+
function runMigrations(db, migrations2) {
|
|
15
|
+
db.exec(`
|
|
16
|
+
CREATE TABLE IF NOT EXISTS _migrations (
|
|
17
|
+
version INTEGER PRIMARY KEY NOT NULL,
|
|
18
|
+
applied_at TEXT NOT NULL
|
|
19
|
+
) STRICT;
|
|
20
|
+
`);
|
|
21
|
+
const currentVersion = db.prepare(
|
|
22
|
+
"SELECT COALESCE(MAX(version), 0) as version FROM _migrations"
|
|
23
|
+
).get();
|
|
24
|
+
const pending = migrations2.filter((m) => m.version > currentVersion.version).sort((a, b) => a.version - b.version);
|
|
25
|
+
for (const migration of pending) {
|
|
26
|
+
db.exec("BEGIN");
|
|
27
|
+
try {
|
|
28
|
+
migration.up(db);
|
|
29
|
+
db.prepare("INSERT INTO _migrations (version, applied_at) VALUES (?, ?)").run(
|
|
30
|
+
migration.version,
|
|
31
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
32
|
+
);
|
|
33
|
+
db.exec("COMMIT");
|
|
34
|
+
} catch (err) {
|
|
35
|
+
db.exec("ROLLBACK");
|
|
36
|
+
throw new Error(`Migration v${migration.version} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/db/engrams.ts
|
|
42
|
+
var dbs = /* @__PURE__ */ new Map();
|
|
43
|
+
var migrations = [
|
|
44
|
+
{
|
|
45
|
+
version: 1,
|
|
46
|
+
up: (db) => {
|
|
47
|
+
db.exec(`
|
|
48
|
+
CREATE TABLE IF NOT EXISTS engrams (
|
|
49
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
50
|
+
content TEXT NOT NULL,
|
|
51
|
+
created_at TEXT NOT NULL,
|
|
52
|
+
expires_at TEXT NOT NULL,
|
|
53
|
+
evaluated_at TEXT,
|
|
54
|
+
promoted INTEGER,
|
|
55
|
+
deleted_at TEXT
|
|
56
|
+
) STRICT;
|
|
57
|
+
`);
|
|
58
|
+
db.exec(`
|
|
59
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS engrams_fts
|
|
60
|
+
USING fts5(content, content='engrams', content_rowid='rowid');
|
|
61
|
+
`);
|
|
62
|
+
db.exec(`
|
|
63
|
+
CREATE TRIGGER IF NOT EXISTS engrams_ai AFTER INSERT ON engrams BEGIN
|
|
64
|
+
INSERT INTO engrams_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
65
|
+
END;
|
|
66
|
+
`);
|
|
67
|
+
db.exec(`
|
|
68
|
+
CREATE TRIGGER IF NOT EXISTS engrams_ad AFTER DELETE ON engrams BEGIN
|
|
69
|
+
INSERT INTO engrams_fts(engrams_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
|
|
70
|
+
END;
|
|
71
|
+
`);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
version: 2,
|
|
76
|
+
up: (db) => {
|
|
77
|
+
db.exec(`
|
|
78
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
79
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
80
|
+
ts TEXT NOT NULL,
|
|
81
|
+
author TEXT NOT NULL,
|
|
82
|
+
content TEXT NOT NULL,
|
|
83
|
+
source_ids TEXT NOT NULL DEFAULT '[]',
|
|
84
|
+
created_at TEXT NOT NULL,
|
|
85
|
+
deleted_at TEXT,
|
|
86
|
+
sync_version INTEGER NOT NULL DEFAULT 0
|
|
87
|
+
) STRICT;
|
|
88
|
+
`);
|
|
89
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memories_ts ON memories(ts);");
|
|
90
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memories_sync_version ON memories(sync_version);");
|
|
91
|
+
db.exec(`
|
|
92
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts
|
|
93
|
+
USING fts5(content, content='memories', content_rowid='rowid');
|
|
94
|
+
`);
|
|
95
|
+
db.exec(`
|
|
96
|
+
CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
|
|
97
|
+
INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
98
|
+
END;
|
|
99
|
+
`);
|
|
100
|
+
db.exec(`
|
|
101
|
+
CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
|
|
102
|
+
INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
|
|
103
|
+
END;
|
|
104
|
+
`);
|
|
105
|
+
db.exec(`
|
|
106
|
+
CREATE TABLE IF NOT EXISTS longterm_summary (
|
|
107
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
108
|
+
content TEXT NOT NULL,
|
|
109
|
+
updated_at TEXT NOT NULL,
|
|
110
|
+
sync_version INTEGER NOT NULL DEFAULT 0
|
|
111
|
+
) STRICT;
|
|
112
|
+
`);
|
|
113
|
+
db.exec(`
|
|
114
|
+
CREATE TABLE IF NOT EXISTS sync_cursors (
|
|
115
|
+
backend TEXT NOT NULL,
|
|
116
|
+
direction TEXT NOT NULL,
|
|
117
|
+
cursor_value TEXT NOT NULL,
|
|
118
|
+
updated_at TEXT NOT NULL,
|
|
119
|
+
PRIMARY KEY (backend, direction)
|
|
120
|
+
) STRICT;
|
|
121
|
+
`);
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
version: 3,
|
|
126
|
+
up: (db) => {
|
|
127
|
+
db.exec("ALTER TABLE engrams ADD COLUMN episode_key TEXT;");
|
|
128
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_engrams_episode_key ON engrams(episode_key);");
|
|
129
|
+
db.exec("ALTER TABLE memories ADD COLUMN episode_key TEXT;");
|
|
130
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_memories_episode_key ON memories(episode_key);");
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
version: 4,
|
|
135
|
+
up: (db) => {
|
|
136
|
+
db.exec("ALTER TABLE engrams ADD COLUMN context TEXT;");
|
|
137
|
+
db.exec("ALTER TABLE engrams ADD COLUMN decisions TEXT;");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
];
|
|
141
|
+
function getCortexDb(cortexName) {
|
|
142
|
+
const cached = dbs.get(cortexName);
|
|
143
|
+
if (cached) return cached;
|
|
144
|
+
ensureThinkDirs();
|
|
145
|
+
const dbPath = getEngramDbPath(cortexName);
|
|
146
|
+
const db = new DatabaseSync(dbPath);
|
|
147
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
148
|
+
db.exec("PRAGMA synchronous = NORMAL");
|
|
149
|
+
runMigrations(db, migrations);
|
|
150
|
+
dbs.set(cortexName, db);
|
|
151
|
+
return db;
|
|
152
|
+
}
|
|
153
|
+
function closeCortexDb(cortexName) {
|
|
154
|
+
const db = dbs.get(cortexName);
|
|
155
|
+
if (db) {
|
|
156
|
+
db.close();
|
|
157
|
+
dbs.delete(cortexName);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// src/db/memory-queries.ts
|
|
162
|
+
function insertMemory(cortexName, params) {
|
|
163
|
+
const db = getCortexDb(cortexName);
|
|
164
|
+
const id = params.id ?? uuidv7();
|
|
165
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
166
|
+
const sourceIds = JSON.stringify(params.source_ids ?? []);
|
|
167
|
+
const episodeKey = params.episode_key ?? null;
|
|
168
|
+
db.prepare(
|
|
169
|
+
`INSERT INTO memories (id, ts, author, content, source_ids, created_at, deleted_at, sync_version, episode_key)
|
|
170
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, (SELECT COALESCE(MAX(sync_version), 0) + 1 FROM memories), ?)`
|
|
171
|
+
).run(id, params.ts, params.author, params.content, sourceIds, now, params.deleted_at ?? null, episodeKey);
|
|
172
|
+
const row = db.prepare("SELECT * FROM memories WHERE id = ?").get(id);
|
|
173
|
+
return row;
|
|
174
|
+
}
|
|
175
|
+
function insertMemoryIfNotExists(cortexName, params) {
|
|
176
|
+
const db = getCortexDb(cortexName);
|
|
177
|
+
const existing = db.prepare("SELECT id FROM memories WHERE id = ?").get(params.id);
|
|
178
|
+
if (existing) return false;
|
|
179
|
+
insertMemory(cortexName, params);
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
function getMemories(cortexName, params = {}) {
|
|
183
|
+
const db = getCortexDb(cortexName);
|
|
184
|
+
const conditions = ["deleted_at IS NULL"];
|
|
185
|
+
const values = [];
|
|
186
|
+
if (params.since) {
|
|
187
|
+
conditions.push("ts >= ?");
|
|
188
|
+
values.push(params.since);
|
|
189
|
+
}
|
|
190
|
+
if (params.until) {
|
|
191
|
+
conditions.push("ts <= ?");
|
|
192
|
+
values.push(params.until);
|
|
193
|
+
}
|
|
194
|
+
const where = `WHERE ${conditions.join(" AND ")}`;
|
|
195
|
+
if (params.limit) {
|
|
196
|
+
values.push(params.limit);
|
|
197
|
+
return db.prepare(
|
|
198
|
+
`SELECT * FROM memories ${where} ORDER BY ts ASC LIMIT ?`
|
|
199
|
+
).all(...values);
|
|
200
|
+
}
|
|
201
|
+
return db.prepare(
|
|
202
|
+
`SELECT * FROM memories ${where} ORDER BY ts ASC`
|
|
203
|
+
).all(...values);
|
|
204
|
+
}
|
|
205
|
+
function getMemoriesBySyncVersion(cortexName, sinceVersion) {
|
|
206
|
+
const db = getCortexDb(cortexName);
|
|
207
|
+
return db.prepare(
|
|
208
|
+
"SELECT * FROM memories WHERE sync_version > ? ORDER BY sync_version ASC"
|
|
209
|
+
).all(sinceVersion);
|
|
210
|
+
}
|
|
211
|
+
function searchMemories(cortexName, query, limit = 20) {
|
|
212
|
+
const db = getCortexDb(cortexName);
|
|
213
|
+
try {
|
|
214
|
+
return db.prepare(
|
|
215
|
+
`SELECT m.* FROM memories m JOIN memories_fts f ON m.rowid = f.rowid
|
|
216
|
+
WHERE memories_fts MATCH ? AND m.deleted_at IS NULL
|
|
217
|
+
ORDER BY rank LIMIT ?`
|
|
218
|
+
).all(query, limit);
|
|
219
|
+
} catch {
|
|
220
|
+
const pattern = `%${query.replace(/%/g, "\\%").replace(/_/g, "\\_")}%`;
|
|
221
|
+
return db.prepare(
|
|
222
|
+
`SELECT * FROM memories WHERE content LIKE ? ESCAPE '\\' AND deleted_at IS NULL ORDER BY ts DESC LIMIT ?`
|
|
223
|
+
).all(pattern, limit);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
function tombstoneMemory(cortexName, id) {
|
|
227
|
+
const db = getCortexDb(cortexName);
|
|
228
|
+
db.prepare(
|
|
229
|
+
`UPDATE memories SET deleted_at = ?, sync_version = (SELECT COALESCE(MAX(sync_version), 0) + 1 FROM memories)
|
|
230
|
+
WHERE id = ? AND deleted_at IS NULL`
|
|
231
|
+
).run((/* @__PURE__ */ new Date()).toISOString(), id);
|
|
232
|
+
}
|
|
233
|
+
function getLongtermSummary(cortexName) {
|
|
234
|
+
const db = getCortexDb(cortexName);
|
|
235
|
+
const row = db.prepare("SELECT content FROM longterm_summary WHERE id = 1").get();
|
|
236
|
+
return row?.content ?? null;
|
|
237
|
+
}
|
|
238
|
+
function setLongtermSummary(cortexName, content) {
|
|
239
|
+
const db = getCortexDb(cortexName);
|
|
240
|
+
db.prepare(
|
|
241
|
+
`INSERT INTO longterm_summary (id, content, updated_at, sync_version)
|
|
242
|
+
VALUES (1, ?, ?, (SELECT COALESCE(MAX(sync_version), 0) + 1 FROM memories))
|
|
243
|
+
ON CONFLICT(id) DO UPDATE SET content = excluded.content, updated_at = excluded.updated_at, sync_version = excluded.sync_version`
|
|
244
|
+
).run(content, (/* @__PURE__ */ new Date()).toISOString());
|
|
245
|
+
}
|
|
246
|
+
function getSyncCursor(cortexName, backend, direction) {
|
|
247
|
+
const db = getCortexDb(cortexName);
|
|
248
|
+
const row = db.prepare(
|
|
249
|
+
"SELECT cursor_value FROM sync_cursors WHERE backend = ? AND direction = ?"
|
|
250
|
+
).get(backend, direction);
|
|
251
|
+
return row?.cursor_value ?? null;
|
|
252
|
+
}
|
|
253
|
+
function setSyncCursor(cortexName, backend, direction, cursorValue) {
|
|
254
|
+
const db = getCortexDb(cortexName);
|
|
255
|
+
db.prepare(
|
|
256
|
+
`INSERT INTO sync_cursors (backend, direction, cursor_value, updated_at)
|
|
257
|
+
VALUES (?, ?, ?, ?)
|
|
258
|
+
ON CONFLICT(backend, direction) DO UPDATE SET cursor_value = excluded.cursor_value, updated_at = excluded.updated_at`
|
|
259
|
+
).run(backend, direction, cursorValue, (/* @__PURE__ */ new Date()).toISOString());
|
|
260
|
+
}
|
|
261
|
+
function getMemoryCount(cortexName) {
|
|
262
|
+
const db = getCortexDb(cortexName);
|
|
263
|
+
const row = db.prepare("SELECT COUNT(*) as count FROM memories WHERE deleted_at IS NULL").get();
|
|
264
|
+
return row.count;
|
|
265
|
+
}
|
|
266
|
+
function getMemoryByEpisodeKey(cortexName, episodeKey) {
|
|
267
|
+
const db = getCortexDb(cortexName);
|
|
268
|
+
const row = db.prepare(
|
|
269
|
+
"SELECT * FROM memories WHERE episode_key = ? AND deleted_at IS NULL LIMIT 1"
|
|
270
|
+
).get(episodeKey);
|
|
271
|
+
return row ?? null;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export {
|
|
275
|
+
getCortexDb,
|
|
276
|
+
closeCortexDb,
|
|
277
|
+
insertMemory,
|
|
278
|
+
insertMemoryIfNotExists,
|
|
279
|
+
getMemories,
|
|
280
|
+
getMemoriesBySyncVersion,
|
|
281
|
+
searchMemories,
|
|
282
|
+
tombstoneMemory,
|
|
283
|
+
getLongtermSummary,
|
|
284
|
+
setLongtermSummary,
|
|
285
|
+
getSyncCursor,
|
|
286
|
+
setSyncCursor,
|
|
287
|
+
getMemoryCount,
|
|
288
|
+
getMemoryByEpisodeKey
|
|
289
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,24 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node --no-warnings=ExperimentalWarning
|
|
2
|
+
import {
|
|
3
|
+
closeCortexDb,
|
|
4
|
+
getCortexDb,
|
|
5
|
+
getLongtermSummary,
|
|
6
|
+
getMemories,
|
|
7
|
+
getMemoriesBySyncVersion,
|
|
8
|
+
getMemoryByEpisodeKey,
|
|
9
|
+
getMemoryCount,
|
|
10
|
+
getSyncCursor,
|
|
11
|
+
insertMemory,
|
|
12
|
+
insertMemoryIfNotExists,
|
|
13
|
+
searchMemories,
|
|
14
|
+
setLongtermSummary,
|
|
15
|
+
setSyncCursor,
|
|
16
|
+
tombstoneMemory
|
|
17
|
+
} from "./chunk-MSOBQE64.js";
|
|
2
18
|
import {
|
|
3
19
|
appendAndCommit,
|
|
4
20
|
countBranchFileLines,
|
|
5
21
|
createOrphanBranch,
|
|
6
22
|
ensureRepoCloned,
|
|
7
|
-
ensureThinkDirs,
|
|
8
23
|
fetchBranch,
|
|
9
24
|
getConfig,
|
|
10
25
|
getConfigDir,
|
|
11
|
-
getCuratorMdPath,
|
|
12
|
-
getEngramDbPath,
|
|
13
|
-
getEngramsDir,
|
|
14
|
-
getLongtermPath,
|
|
15
|
-
getThinkDataDir,
|
|
16
26
|
listBranchFiles,
|
|
17
27
|
listRemoteBranches,
|
|
18
28
|
migrateToBuckets,
|
|
19
29
|
readFileFromBranch,
|
|
20
30
|
saveConfig
|
|
21
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-ICK2JU5B.js";
|
|
32
|
+
import {
|
|
33
|
+
ensureThinkDirs,
|
|
34
|
+
getCuratorMdPath,
|
|
35
|
+
getEngramsDir,
|
|
36
|
+
getLongtermPath,
|
|
37
|
+
getThinkDataDir
|
|
38
|
+
} from "./chunk-DCTG6IK4.js";
|
|
22
39
|
|
|
23
40
|
// src/index.ts
|
|
24
41
|
import fs11 from "fs";
|
|
@@ -153,152 +170,6 @@ function deleteEntriesByContent(pattern) {
|
|
|
153
170
|
|
|
154
171
|
// src/db/engram-queries.ts
|
|
155
172
|
import { v7 as uuidv72 } from "uuid";
|
|
156
|
-
|
|
157
|
-
// src/db/engrams.ts
|
|
158
|
-
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
159
|
-
|
|
160
|
-
// src/db/migrate.ts
|
|
161
|
-
function runMigrations(db2, migrations2) {
|
|
162
|
-
db2.exec(`
|
|
163
|
-
CREATE TABLE IF NOT EXISTS _migrations (
|
|
164
|
-
version INTEGER PRIMARY KEY NOT NULL,
|
|
165
|
-
applied_at TEXT NOT NULL
|
|
166
|
-
) STRICT;
|
|
167
|
-
`);
|
|
168
|
-
const currentVersion = db2.prepare(
|
|
169
|
-
"SELECT COALESCE(MAX(version), 0) as version FROM _migrations"
|
|
170
|
-
).get();
|
|
171
|
-
const pending = migrations2.filter((m) => m.version > currentVersion.version).sort((a, b) => a.version - b.version);
|
|
172
|
-
for (const migration of pending) {
|
|
173
|
-
db2.exec("BEGIN");
|
|
174
|
-
try {
|
|
175
|
-
migration.up(db2);
|
|
176
|
-
db2.prepare("INSERT INTO _migrations (version, applied_at) VALUES (?, ?)").run(
|
|
177
|
-
migration.version,
|
|
178
|
-
(/* @__PURE__ */ new Date()).toISOString()
|
|
179
|
-
);
|
|
180
|
-
db2.exec("COMMIT");
|
|
181
|
-
} catch (err) {
|
|
182
|
-
db2.exec("ROLLBACK");
|
|
183
|
-
throw new Error(`Migration v${migration.version} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// src/db/engrams.ts
|
|
189
|
-
var dbs = /* @__PURE__ */ new Map();
|
|
190
|
-
var migrations = [
|
|
191
|
-
{
|
|
192
|
-
version: 1,
|
|
193
|
-
up: (db2) => {
|
|
194
|
-
db2.exec(`
|
|
195
|
-
CREATE TABLE IF NOT EXISTS engrams (
|
|
196
|
-
id TEXT PRIMARY KEY NOT NULL,
|
|
197
|
-
content TEXT NOT NULL,
|
|
198
|
-
created_at TEXT NOT NULL,
|
|
199
|
-
expires_at TEXT NOT NULL,
|
|
200
|
-
evaluated_at TEXT,
|
|
201
|
-
promoted INTEGER,
|
|
202
|
-
deleted_at TEXT
|
|
203
|
-
) STRICT;
|
|
204
|
-
`);
|
|
205
|
-
db2.exec(`
|
|
206
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS engrams_fts
|
|
207
|
-
USING fts5(content, content='engrams', content_rowid='rowid');
|
|
208
|
-
`);
|
|
209
|
-
db2.exec(`
|
|
210
|
-
CREATE TRIGGER IF NOT EXISTS engrams_ai AFTER INSERT ON engrams BEGIN
|
|
211
|
-
INSERT INTO engrams_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
212
|
-
END;
|
|
213
|
-
`);
|
|
214
|
-
db2.exec(`
|
|
215
|
-
CREATE TRIGGER IF NOT EXISTS engrams_ad AFTER DELETE ON engrams BEGIN
|
|
216
|
-
INSERT INTO engrams_fts(engrams_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
|
|
217
|
-
END;
|
|
218
|
-
`);
|
|
219
|
-
}
|
|
220
|
-
},
|
|
221
|
-
{
|
|
222
|
-
version: 2,
|
|
223
|
-
up: (db2) => {
|
|
224
|
-
db2.exec(`
|
|
225
|
-
CREATE TABLE IF NOT EXISTS memories (
|
|
226
|
-
id TEXT PRIMARY KEY NOT NULL,
|
|
227
|
-
ts TEXT NOT NULL,
|
|
228
|
-
author TEXT NOT NULL,
|
|
229
|
-
content TEXT NOT NULL,
|
|
230
|
-
source_ids TEXT NOT NULL DEFAULT '[]',
|
|
231
|
-
created_at TEXT NOT NULL,
|
|
232
|
-
deleted_at TEXT,
|
|
233
|
-
sync_version INTEGER NOT NULL DEFAULT 0
|
|
234
|
-
) STRICT;
|
|
235
|
-
`);
|
|
236
|
-
db2.exec("CREATE INDEX IF NOT EXISTS idx_memories_ts ON memories(ts);");
|
|
237
|
-
db2.exec("CREATE INDEX IF NOT EXISTS idx_memories_sync_version ON memories(sync_version);");
|
|
238
|
-
db2.exec(`
|
|
239
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts
|
|
240
|
-
USING fts5(content, content='memories', content_rowid='rowid');
|
|
241
|
-
`);
|
|
242
|
-
db2.exec(`
|
|
243
|
-
CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
|
|
244
|
-
INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
245
|
-
END;
|
|
246
|
-
`);
|
|
247
|
-
db2.exec(`
|
|
248
|
-
CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
|
|
249
|
-
INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.rowid, old.content);
|
|
250
|
-
END;
|
|
251
|
-
`);
|
|
252
|
-
db2.exec(`
|
|
253
|
-
CREATE TABLE IF NOT EXISTS longterm_summary (
|
|
254
|
-
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
255
|
-
content TEXT NOT NULL,
|
|
256
|
-
updated_at TEXT NOT NULL,
|
|
257
|
-
sync_version INTEGER NOT NULL DEFAULT 0
|
|
258
|
-
) STRICT;
|
|
259
|
-
`);
|
|
260
|
-
db2.exec(`
|
|
261
|
-
CREATE TABLE IF NOT EXISTS sync_cursors (
|
|
262
|
-
backend TEXT NOT NULL,
|
|
263
|
-
direction TEXT NOT NULL,
|
|
264
|
-
cursor_value TEXT NOT NULL,
|
|
265
|
-
updated_at TEXT NOT NULL,
|
|
266
|
-
PRIMARY KEY (backend, direction)
|
|
267
|
-
) STRICT;
|
|
268
|
-
`);
|
|
269
|
-
}
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
version: 3,
|
|
273
|
-
up: (db2) => {
|
|
274
|
-
db2.exec("ALTER TABLE engrams ADD COLUMN episode_key TEXT;");
|
|
275
|
-
db2.exec("CREATE INDEX IF NOT EXISTS idx_engrams_episode_key ON engrams(episode_key);");
|
|
276
|
-
db2.exec("ALTER TABLE memories ADD COLUMN episode_key TEXT;");
|
|
277
|
-
db2.exec("CREATE INDEX IF NOT EXISTS idx_memories_episode_key ON memories(episode_key);");
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
];
|
|
281
|
-
function getCortexDb(cortexName) {
|
|
282
|
-
const cached = dbs.get(cortexName);
|
|
283
|
-
if (cached) return cached;
|
|
284
|
-
ensureThinkDirs();
|
|
285
|
-
const dbPath = getEngramDbPath(cortexName);
|
|
286
|
-
const db2 = new DatabaseSync2(dbPath);
|
|
287
|
-
db2.exec("PRAGMA journal_mode = WAL");
|
|
288
|
-
db2.exec("PRAGMA synchronous = NORMAL");
|
|
289
|
-
runMigrations(db2, migrations);
|
|
290
|
-
dbs.set(cortexName, db2);
|
|
291
|
-
return db2;
|
|
292
|
-
}
|
|
293
|
-
function closeCortexDb(cortexName) {
|
|
294
|
-
const db2 = dbs.get(cortexName);
|
|
295
|
-
if (db2) {
|
|
296
|
-
db2.close();
|
|
297
|
-
dbs.delete(cortexName);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// src/db/engram-queries.ts
|
|
302
173
|
function insertEngram(cortexName, params) {
|
|
303
174
|
const db2 = getCortexDb(cortexName);
|
|
304
175
|
const id = uuidv72();
|
|
@@ -307,10 +178,12 @@ function insertEngram(cortexName, params) {
|
|
|
307
178
|
const expiresInDays = params.expiresInDays ?? 60;
|
|
308
179
|
const expires_at = new Date(now.getTime() + expiresInDays * 864e5).toISOString();
|
|
309
180
|
const episodeKey = params.episodeKey ?? null;
|
|
181
|
+
const context = params.context ?? null;
|
|
182
|
+
const decisions = params.decisions?.length ? JSON.stringify(params.decisions) : null;
|
|
310
183
|
db2.prepare(
|
|
311
|
-
`INSERT INTO engrams (id, content, created_at, expires_at, episode_key) VALUES (?, ?, ?, ?, ?)`
|
|
312
|
-
).run(id, params.content, created_at, expires_at, episodeKey);
|
|
313
|
-
return { id, content: params.content, created_at, expires_at, evaluated_at: null, promoted: null, deleted_at: null, episode_key: episodeKey };
|
|
184
|
+
`INSERT INTO engrams (id, content, created_at, expires_at, episode_key, context, decisions) VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
185
|
+
).run(id, params.content, created_at, expires_at, episodeKey, context, decisions);
|
|
186
|
+
return { id, content: params.content, created_at, expires_at, evaluated_at: null, promoted: null, deleted_at: null, episode_key: episodeKey, context, decisions };
|
|
314
187
|
}
|
|
315
188
|
function getPendingEngrams(cortexName) {
|
|
316
189
|
const db2 = getCortexDb(cortexName);
|
|
@@ -501,7 +374,7 @@ var logCommand = new Command("log").description("Log a note or entry").argument(
|
|
|
501
374
|
}
|
|
502
375
|
closeDb();
|
|
503
376
|
});
|
|
504
|
-
var syncCommand = new Command("sync").description("Log a sync/work-log entry (shorthand for log --category sync)").argument("<message>", "The message to log").option("-s, --source <source>", "Source of the entry", "manual").option("-t, --tags <tags>", "Comma-separated tags").option("-e, --episode <key>", "Tag this engram with an episode identifier").option("--silent", "Suppress output").action(function(message, opts) {
|
|
377
|
+
var syncCommand = new Command("sync").description("Log a sync/work-log entry (shorthand for log --category sync)").argument("<message>", "The message to log").option("-s, --source <source>", "Source of the entry", "manual").option("-t, --tags <tags>", "Comma-separated tags").option("-e, --episode <key>", "Tag this engram with an episode identifier").option("--context <json>", "Attach structured JSON metadata to this engram").option("-d, --decision <text>", "Record a decision (repeatable)", (val, prev) => [...prev, val], []).option("--silent", "Suppress output").action(function(message, opts) {
|
|
505
378
|
const globalOpts = this.optsWithGlobals();
|
|
506
379
|
const config = getConfig();
|
|
507
380
|
if (config.paused) {
|
|
@@ -516,7 +389,17 @@ var syncCommand = new Command("sync").description("Log a sync/work-log entry (sh
|
|
|
516
389
|
console.log(chalk.yellow(` \u26A0 ${w}`));
|
|
517
390
|
}
|
|
518
391
|
}
|
|
519
|
-
|
|
392
|
+
if (opts.context) {
|
|
393
|
+
try {
|
|
394
|
+
JSON.parse(opts.context);
|
|
395
|
+
} catch {
|
|
396
|
+
console.error(chalk.red("Error: --context must be valid JSON"));
|
|
397
|
+
process.exitCode = 1;
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const decisions = opts.decision?.length ? opts.decision : void 0;
|
|
402
|
+
const engram = insertEngram(cortex, { content: message, episodeKey: opts.episode, context: opts.context, decisions });
|
|
520
403
|
if (!opts.silent) {
|
|
521
404
|
const badge = chalk.cyan(`[${cortex}]`);
|
|
522
405
|
const ts = chalk.gray(engram.created_at.slice(0, 16).replace("T", " "));
|
|
@@ -1099,105 +982,6 @@ import { Command as Command9 } from "commander";
|
|
|
1099
982
|
import chalk9 from "chalk";
|
|
1100
983
|
import readline2 from "readline";
|
|
1101
984
|
|
|
1102
|
-
// src/db/memory-queries.ts
|
|
1103
|
-
import { v7 as uuidv73 } from "uuid";
|
|
1104
|
-
function insertMemory(cortexName, params) {
|
|
1105
|
-
const db2 = getCortexDb(cortexName);
|
|
1106
|
-
const id = params.id ?? uuidv73();
|
|
1107
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1108
|
-
const sourceIds = JSON.stringify(params.source_ids ?? []);
|
|
1109
|
-
const episodeKey = params.episode_key ?? null;
|
|
1110
|
-
db2.prepare(
|
|
1111
|
-
`INSERT INTO memories (id, ts, author, content, source_ids, created_at, deleted_at, sync_version, episode_key)
|
|
1112
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, (SELECT COALESCE(MAX(sync_version), 0) + 1 FROM memories), ?)`
|
|
1113
|
-
).run(id, params.ts, params.author, params.content, sourceIds, now, params.deleted_at ?? null, episodeKey);
|
|
1114
|
-
const row = db2.prepare("SELECT * FROM memories WHERE id = ?").get(id);
|
|
1115
|
-
return row;
|
|
1116
|
-
}
|
|
1117
|
-
function insertMemoryIfNotExists(cortexName, params) {
|
|
1118
|
-
const db2 = getCortexDb(cortexName);
|
|
1119
|
-
const existing = db2.prepare("SELECT id FROM memories WHERE id = ?").get(params.id);
|
|
1120
|
-
if (existing) return false;
|
|
1121
|
-
insertMemory(cortexName, params);
|
|
1122
|
-
return true;
|
|
1123
|
-
}
|
|
1124
|
-
function getMemories(cortexName, params = {}) {
|
|
1125
|
-
const db2 = getCortexDb(cortexName);
|
|
1126
|
-
const conditions = ["deleted_at IS NULL"];
|
|
1127
|
-
const values = [];
|
|
1128
|
-
if (params.since) {
|
|
1129
|
-
conditions.push("ts >= ?");
|
|
1130
|
-
values.push(params.since);
|
|
1131
|
-
}
|
|
1132
|
-
if (params.until) {
|
|
1133
|
-
conditions.push("ts <= ?");
|
|
1134
|
-
values.push(params.until);
|
|
1135
|
-
}
|
|
1136
|
-
const where = `WHERE ${conditions.join(" AND ")}`;
|
|
1137
|
-
if (params.limit) {
|
|
1138
|
-
values.push(params.limit);
|
|
1139
|
-
return db2.prepare(
|
|
1140
|
-
`SELECT * FROM memories ${where} ORDER BY ts ASC LIMIT ?`
|
|
1141
|
-
).all(...values);
|
|
1142
|
-
}
|
|
1143
|
-
return db2.prepare(
|
|
1144
|
-
`SELECT * FROM memories ${where} ORDER BY ts ASC`
|
|
1145
|
-
).all(...values);
|
|
1146
|
-
}
|
|
1147
|
-
function getMemoriesBySyncVersion(cortexName, sinceVersion) {
|
|
1148
|
-
const db2 = getCortexDb(cortexName);
|
|
1149
|
-
return db2.prepare(
|
|
1150
|
-
"SELECT * FROM memories WHERE sync_version > ? ORDER BY sync_version ASC"
|
|
1151
|
-
).all(sinceVersion);
|
|
1152
|
-
}
|
|
1153
|
-
function tombstoneMemory(cortexName, id) {
|
|
1154
|
-
const db2 = getCortexDb(cortexName);
|
|
1155
|
-
db2.prepare(
|
|
1156
|
-
`UPDATE memories SET deleted_at = ?, sync_version = (SELECT COALESCE(MAX(sync_version), 0) + 1 FROM memories)
|
|
1157
|
-
WHERE id = ? AND deleted_at IS NULL`
|
|
1158
|
-
).run((/* @__PURE__ */ new Date()).toISOString(), id);
|
|
1159
|
-
}
|
|
1160
|
-
function getLongtermSummary(cortexName) {
|
|
1161
|
-
const db2 = getCortexDb(cortexName);
|
|
1162
|
-
const row = db2.prepare("SELECT content FROM longterm_summary WHERE id = 1").get();
|
|
1163
|
-
return row?.content ?? null;
|
|
1164
|
-
}
|
|
1165
|
-
function setLongtermSummary(cortexName, content) {
|
|
1166
|
-
const db2 = getCortexDb(cortexName);
|
|
1167
|
-
db2.prepare(
|
|
1168
|
-
`INSERT INTO longterm_summary (id, content, updated_at, sync_version)
|
|
1169
|
-
VALUES (1, ?, ?, (SELECT COALESCE(MAX(sync_version), 0) + 1 FROM memories))
|
|
1170
|
-
ON CONFLICT(id) DO UPDATE SET content = excluded.content, updated_at = excluded.updated_at, sync_version = excluded.sync_version`
|
|
1171
|
-
).run(content, (/* @__PURE__ */ new Date()).toISOString());
|
|
1172
|
-
}
|
|
1173
|
-
function getSyncCursor(cortexName, backend, direction) {
|
|
1174
|
-
const db2 = getCortexDb(cortexName);
|
|
1175
|
-
const row = db2.prepare(
|
|
1176
|
-
"SELECT cursor_value FROM sync_cursors WHERE backend = ? AND direction = ?"
|
|
1177
|
-
).get(backend, direction);
|
|
1178
|
-
return row?.cursor_value ?? null;
|
|
1179
|
-
}
|
|
1180
|
-
function setSyncCursor(cortexName, backend, direction, cursorValue) {
|
|
1181
|
-
const db2 = getCortexDb(cortexName);
|
|
1182
|
-
db2.prepare(
|
|
1183
|
-
`INSERT INTO sync_cursors (backend, direction, cursor_value, updated_at)
|
|
1184
|
-
VALUES (?, ?, ?, ?)
|
|
1185
|
-
ON CONFLICT(backend, direction) DO UPDATE SET cursor_value = excluded.cursor_value, updated_at = excluded.updated_at`
|
|
1186
|
-
).run(backend, direction, cursorValue, (/* @__PURE__ */ new Date()).toISOString());
|
|
1187
|
-
}
|
|
1188
|
-
function getMemoryCount(cortexName) {
|
|
1189
|
-
const db2 = getCortexDb(cortexName);
|
|
1190
|
-
const row = db2.prepare("SELECT COUNT(*) as count FROM memories WHERE deleted_at IS NULL").get();
|
|
1191
|
-
return row.count;
|
|
1192
|
-
}
|
|
1193
|
-
function getMemoryByEpisodeKey(cortexName, episodeKey) {
|
|
1194
|
-
const db2 = getCortexDb(cortexName);
|
|
1195
|
-
const row = db2.prepare(
|
|
1196
|
-
"SELECT * FROM memories WHERE episode_key = ? AND deleted_at IS NULL LIMIT 1"
|
|
1197
|
-
).get(episodeKey);
|
|
1198
|
-
return row ?? null;
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
985
|
// src/lib/curator.ts
|
|
1202
986
|
import fs7 from "fs";
|
|
1203
987
|
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
@@ -1214,6 +998,7 @@ Your task:
|
|
|
1214
998
|
- Blockers encountered or resolved
|
|
1215
999
|
- Clusters \u2014 multiple events around the same topic signal importance
|
|
1216
1000
|
- Weight \u2014 urgency, frustration, or surprise in the language suggests significance
|
|
1001
|
+
- Decisions \u2014 events with explicit decisions attached are high-signal and should almost always be promoted. Preserve the decision rationale in the memory.
|
|
1217
1002
|
4. Routine, administrative, or low-signal events should be dropped.
|
|
1218
1003
|
Dropping is correct, not a failure.
|
|
1219
1004
|
|
|
@@ -1279,7 +1064,24 @@ function assembleCurationPrompt(params) {
|
|
|
1279
1064
|
const longtermText = params.longtermSummary ?? "(no long-term context yet)";
|
|
1280
1065
|
const recentText = params.recentMemories.length > 0 ? params.recentMemories.map((m) => `- [${m.ts}] ${m.author}: ${m.content}`).join("\n") : "(no recent memories)";
|
|
1281
1066
|
const curatorMdText = params.curatorMd ?? "(none provided)";
|
|
1282
|
-
const engramsText = params.pendingEngrams.map((e) =>
|
|
1067
|
+
const engramsText = params.pendingEngrams.map((e) => {
|
|
1068
|
+
let line = `- [${e.created_at}] (id: ${e.id}) ${e.content}`;
|
|
1069
|
+
if (e.decisions) {
|
|
1070
|
+
try {
|
|
1071
|
+
const decisions = JSON.parse(e.decisions);
|
|
1072
|
+
if (decisions.length > 0) {
|
|
1073
|
+
line += `
|
|
1074
|
+
Decisions: ${decisions.map((d) => `"${d}"`).join("; ")}`;
|
|
1075
|
+
}
|
|
1076
|
+
} catch {
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
if (e.context) {
|
|
1080
|
+
line += `
|
|
1081
|
+
Context: ${e.context}`;
|
|
1082
|
+
}
|
|
1083
|
+
return line;
|
|
1084
|
+
}).join("\n");
|
|
1283
1085
|
const userMessage = [
|
|
1284
1086
|
"## Long-term context (compressed history)",
|
|
1285
1087
|
wrapData("longterm-summary", longtermText),
|
|
@@ -1431,7 +1233,24 @@ Output: Return a JSON object with a single "content" field containing your narra
|
|
|
1431
1233
|
|
|
1432
1234
|
Do not include markdown, code fences, or explanation outside the JSON.`;
|
|
1433
1235
|
function assembleEpisodeCurationPrompt(params) {
|
|
1434
|
-
const engramsText = params.pendingEngrams.map((e) =>
|
|
1236
|
+
const engramsText = params.pendingEngrams.map((e) => {
|
|
1237
|
+
let line = `- [${e.created_at}] ${e.content}`;
|
|
1238
|
+
if (e.decisions) {
|
|
1239
|
+
try {
|
|
1240
|
+
const decisions = JSON.parse(e.decisions);
|
|
1241
|
+
if (decisions.length > 0) {
|
|
1242
|
+
line += `
|
|
1243
|
+
Decisions: ${decisions.map((d) => `"${d}"`).join("; ")}`;
|
|
1244
|
+
}
|
|
1245
|
+
} catch {
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
if (e.context) {
|
|
1249
|
+
line += `
|
|
1250
|
+
Context: ${e.context}`;
|
|
1251
|
+
}
|
|
1252
|
+
return line;
|
|
1253
|
+
}).join("\n");
|
|
1435
1254
|
const sections = [
|
|
1436
1255
|
"## Episode",
|
|
1437
1256
|
wrapData("episode-key", params.episodeKey),
|
|
@@ -1698,7 +1517,7 @@ cortexCommand.addCommand(new Command9("setup").description("Configure a sync bac
|
|
|
1698
1517
|
const adapter = getSyncAdapter();
|
|
1699
1518
|
if (adapter) {
|
|
1700
1519
|
try {
|
|
1701
|
-
const { ensureRepoCloned: ensureRepoCloned2 } = await import("./git-
|
|
1520
|
+
const { ensureRepoCloned: ensureRepoCloned2 } = await import("./git-BRGF6DFD.js");
|
|
1702
1521
|
ensureRepoCloned2();
|
|
1703
1522
|
console.log(chalk9.green("\u2713") + " Repo cloned");
|
|
1704
1523
|
} catch (err) {
|
|
@@ -2202,44 +2021,77 @@ var monitorCommand = new Command11("monitor").description("Show what got promote
|
|
|
2202
2021
|
// src/commands/recall.ts
|
|
2203
2022
|
import { Command as Command12 } from "commander";
|
|
2204
2023
|
import chalk12 from "chalk";
|
|
2205
|
-
var recallCommand = new Command12("recall").argument("<query>", "What to recall").description("Search memories and local engrams").option("--days <n>", "Days of memories to include", "14").action(async (query3, opts) => {
|
|
2024
|
+
var recallCommand = new Command12("recall").argument("<query>", "What to recall").description("Search memories and local engrams").option("--engrams", "Also search local engrams (not just memories)").option("--all", "Dump all recent memories + long-term summary (ignores query for memories)").option("--days <n>", "Days of memories to include (only with --all)", "14").option("--limit <n>", "Max results to return", "20").action(async (query3, opts) => {
|
|
2206
2025
|
const config = getConfig();
|
|
2207
2026
|
const cortex = config.cortex?.active;
|
|
2208
2027
|
if (!cortex) {
|
|
2209
2028
|
console.error(chalk12.red("No active cortex. Run: think cortex switch <name>"));
|
|
2210
2029
|
process.exit(1);
|
|
2211
2030
|
}
|
|
2212
|
-
const
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2031
|
+
const limit = parseInt(opts.limit, 10);
|
|
2032
|
+
if (opts.all) {
|
|
2033
|
+
const { getMemories: getMemories2 } = await import("./memory-queries-IPGGUAQW.js");
|
|
2034
|
+
const days = parseInt(opts.days, 10);
|
|
2035
|
+
const cutoff = new Date(Date.now() - days * 864e5).toISOString();
|
|
2036
|
+
const recentMemories = getMemories2(cortex, { since: cutoff });
|
|
2037
|
+
const longterm = getLongtermSummary(cortex);
|
|
2038
|
+
const matchingEngrams = searchEngrams(cortex, query3);
|
|
2039
|
+
if (recentMemories.length > 0) {
|
|
2040
|
+
console.log(chalk12.cyan(`Team memories (last ${days} days):`));
|
|
2041
|
+
for (const m of recentMemories) {
|
|
2042
|
+
const ts = m.ts.slice(0, 16).replace("T", " ");
|
|
2043
|
+
console.log(` ${chalk12.gray(ts)} ${chalk12.dim(m.author + ":")} ${m.content}`);
|
|
2044
|
+
}
|
|
2045
|
+
console.log();
|
|
2046
|
+
}
|
|
2047
|
+
if (longterm) {
|
|
2048
|
+
console.log(chalk12.cyan("Long-term context:"));
|
|
2049
|
+
console.log(` ${longterm}`);
|
|
2050
|
+
console.log();
|
|
2051
|
+
}
|
|
2052
|
+
if (matchingEngrams.length > 0) {
|
|
2053
|
+
console.log(chalk12.cyan(`Matching engrams (local):`));
|
|
2054
|
+
for (const e of matchingEngrams) {
|
|
2055
|
+
const ts = e.created_at.slice(0, 16).replace("T", " ");
|
|
2056
|
+
console.log(` ${chalk12.gray(ts)} ${e.content}`);
|
|
2057
|
+
}
|
|
2058
|
+
console.log();
|
|
2059
|
+
}
|
|
2060
|
+
if (recentMemories.length === 0 && matchingEngrams.length === 0 && !longterm) {
|
|
2061
|
+
console.log(chalk12.dim("No results found."));
|
|
2062
|
+
}
|
|
2063
|
+
closeCortexDb(cortex);
|
|
2064
|
+
return;
|
|
2065
|
+
}
|
|
2066
|
+
const matchingMemories = searchMemories(cortex, query3, limit);
|
|
2067
|
+
if (matchingMemories.length > 0) {
|
|
2068
|
+
console.log(chalk12.cyan(`Matching memories (${matchingMemories.length}):`));
|
|
2069
|
+
for (const m of matchingMemories) {
|
|
2220
2070
|
const ts = m.ts.slice(0, 16).replace("T", " ");
|
|
2221
2071
|
console.log(` ${chalk12.gray(ts)} ${chalk12.dim(m.author + ":")} ${m.content}`);
|
|
2222
2072
|
}
|
|
2223
2073
|
console.log();
|
|
2224
2074
|
} else {
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
if (matchingEngrams.length > 0) {
|
|
2234
|
-
console.log(chalk12.cyan(`Matching engrams (local):`));
|
|
2235
|
-
for (const e of matchingEngrams) {
|
|
2236
|
-
const ts = e.created_at.slice(0, 16).replace("T", " ");
|
|
2237
|
-
console.log(` ${chalk12.gray(ts)} ${e.content}`);
|
|
2075
|
+
const longterm = getLongtermSummary(cortex);
|
|
2076
|
+
if (longterm) {
|
|
2077
|
+
console.log(chalk12.dim("No matching memories. Showing long-term context:"));
|
|
2078
|
+
console.log(` ${longterm}`);
|
|
2079
|
+
console.log();
|
|
2080
|
+
} else {
|
|
2081
|
+
console.log(chalk12.dim("No matching memories."));
|
|
2082
|
+
console.log();
|
|
2238
2083
|
}
|
|
2239
|
-
console.log();
|
|
2240
2084
|
}
|
|
2241
|
-
if (
|
|
2242
|
-
|
|
2085
|
+
if (opts.engrams) {
|
|
2086
|
+
const matchingEngrams = searchEngrams(cortex, query3, limit);
|
|
2087
|
+
if (matchingEngrams.length > 0) {
|
|
2088
|
+
console.log(chalk12.cyan(`Matching engrams (${matchingEngrams.length}):`));
|
|
2089
|
+
for (const e of matchingEngrams) {
|
|
2090
|
+
const ts = e.created_at.slice(0, 16).replace("T", " ");
|
|
2091
|
+
console.log(` ${chalk12.gray(ts)} ${e.content}`);
|
|
2092
|
+
}
|
|
2093
|
+
console.log();
|
|
2094
|
+
}
|
|
2243
2095
|
}
|
|
2244
2096
|
closeCortexDb(cortex);
|
|
2245
2097
|
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node --no-warnings=ExperimentalWarning
|
|
2
|
+
import {
|
|
3
|
+
getLongtermSummary,
|
|
4
|
+
getMemories,
|
|
5
|
+
getMemoriesBySyncVersion,
|
|
6
|
+
getMemoryByEpisodeKey,
|
|
7
|
+
getMemoryCount,
|
|
8
|
+
getSyncCursor,
|
|
9
|
+
insertMemory,
|
|
10
|
+
insertMemoryIfNotExists,
|
|
11
|
+
searchMemories,
|
|
12
|
+
setLongtermSummary,
|
|
13
|
+
setSyncCursor,
|
|
14
|
+
tombstoneMemory
|
|
15
|
+
} from "./chunk-MSOBQE64.js";
|
|
16
|
+
import "./chunk-DCTG6IK4.js";
|
|
17
|
+
export {
|
|
18
|
+
getLongtermSummary,
|
|
19
|
+
getMemories,
|
|
20
|
+
getMemoriesBySyncVersion,
|
|
21
|
+
getMemoryByEpisodeKey,
|
|
22
|
+
getMemoryCount,
|
|
23
|
+
getSyncCursor,
|
|
24
|
+
insertMemory,
|
|
25
|
+
insertMemoryIfNotExists,
|
|
26
|
+
searchMemories,
|
|
27
|
+
setLongtermSummary,
|
|
28
|
+
setSyncCursor,
|
|
29
|
+
tombstoneMemory
|
|
30
|
+
};
|