open-think 0.2.1 → 0.2.3

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.
@@ -136,8 +136,8 @@ function createOrphanBranch(branchName) {
136
136
  } catch {
137
137
  }
138
138
  const repoPath = getRepoPath();
139
- fs3.writeFileSync(path3.join(repoPath, "memories.jsonl"), "", "utf-8");
140
- runGit(["add", "memories.jsonl"]);
139
+ fs3.writeFileSync(path3.join(repoPath, "000001.jsonl"), "", "utf-8");
140
+ runGit(["add", "000001.jsonl"]);
141
141
  runGit(["commit", "-m", `init: create cortex ${branchName}`]);
142
142
  runGit(["push", "--set-upstream", "origin", branchName]);
143
143
  }
@@ -151,9 +151,9 @@ function readFileFromBranch(branchName, filePath) {
151
151
  return null;
152
152
  }
153
153
  }
154
- function appendAndCommit(branchName, newLines, commitMessage, maxRetries = 3) {
154
+ function appendAndCommit(branchName, newLines, commitMessage, maxRetries = 3, targetFile = "memories.jsonl") {
155
155
  const repoPath = getRepoPath();
156
- const memoriesPath = path3.join(repoPath, "memories.jsonl");
156
+ const filePath = path3.join(repoPath, targetFile);
157
157
  try {
158
158
  runGit(["switch", branchName]);
159
159
  } catch {
@@ -168,12 +168,12 @@ function appendAndCommit(branchName, newLines, commitMessage, maxRetries = 3) {
168
168
  runGit(["rebase", "--abort"]);
169
169
  } catch {
170
170
  }
171
- throw new Error(`Rebase conflict on ${branchName}. This should not happen with append-only files \u2014 check for manual edits to memories.jsonl.`);
171
+ throw new Error(`Rebase conflict on ${branchName}. This should not happen with append-only files.`);
172
172
  }
173
173
  }
174
174
  const content = newLines.join("\n") + "\n";
175
- fs3.appendFileSync(memoriesPath, content, "utf-8");
176
- runGit(["add", "memories.jsonl"]);
175
+ fs3.appendFileSync(filePath, content, "utf-8");
176
+ runGit(["add", targetFile]);
177
177
  runGit(["commit", "-m", commitMessage]);
178
178
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
179
179
  try {
@@ -194,6 +194,58 @@ function listRemoteBranches() {
194
194
  const output = runGit(["ls-remote", "--heads", "origin"]);
195
195
  return output.trim().split("\n").filter(Boolean).map((line) => line.split(" ")[1]?.replace("refs/heads/", "")).filter(Boolean);
196
196
  }
197
+ function listBranchFiles(branchName, extension) {
198
+ try {
199
+ const output = runGit(["ls-tree", "--name-only", `origin/${branchName}`]);
200
+ let files = output.split("\n").filter(Boolean);
201
+ if (extension) {
202
+ files = files.filter((f) => f.endsWith(extension));
203
+ }
204
+ return files.sort();
205
+ } catch {
206
+ return [];
207
+ }
208
+ }
209
+ function countBranchFileLines(branchName, filePath) {
210
+ const content = readFileFromBranch(branchName, filePath);
211
+ if (!content) return 0;
212
+ return content.trim().split("\n").filter(Boolean).length;
213
+ }
214
+ function migrateToBuckets(branchName) {
215
+ const repoPath = getRepoPath();
216
+ try {
217
+ runGit(["switch", branchName]);
218
+ } catch {
219
+ runGit(["switch", "-c", branchName, `origin/${branchName}`]);
220
+ }
221
+ try {
222
+ runGit(["pull", "--rebase", "origin", branchName]);
223
+ } catch {
224
+ }
225
+ const legacyPath = path3.join(repoPath, "memories.jsonl");
226
+ const bucketPath = path3.join(repoPath, "000001.jsonl");
227
+ if (fs3.existsSync(legacyPath) && !fs3.existsSync(bucketPath)) {
228
+ const preMigrationRef = runGit(["rev-parse", "HEAD"]);
229
+ fs3.renameSync(legacyPath, bucketPath);
230
+ runGit(["add", "-A"]);
231
+ runGit(["commit", "-m", "migrate: memories.jsonl -> 000001.jsonl"]);
232
+ for (let attempt = 1; attempt <= 3; attempt++) {
233
+ try {
234
+ runGit(["push", "origin", branchName]);
235
+ return;
236
+ } catch {
237
+ if (attempt === 3) {
238
+ try {
239
+ runGit(["reset", "--hard", preMigrationRef]);
240
+ } catch {
241
+ }
242
+ throw new Error("Migration push failed after 3 attempts \u2014 local commit rolled back");
243
+ }
244
+ runGit(["pull", "--rebase", "origin", branchName]);
245
+ }
246
+ }
247
+ }
248
+ }
197
249
 
198
250
  export {
199
251
  getThinkDataDir,
@@ -212,5 +264,8 @@ export {
212
264
  readFileFromBranch,
213
265
  appendAndCommit,
214
266
  getFileLog,
215
- listRemoteBranches
267
+ listRemoteBranches,
268
+ listBranchFiles,
269
+ countBranchFileLines,
270
+ migrateToBuckets
216
271
  };
@@ -2,20 +2,26 @@
2
2
  import {
3
3
  appendAndCommit,
4
4
  branchExists,
5
+ countBranchFileLines,
5
6
  createOrphanBranch,
6
7
  ensureRepoCloned,
7
8
  fetchBranch,
8
9
  getFileLog,
10
+ listBranchFiles,
9
11
  listRemoteBranches,
12
+ migrateToBuckets,
10
13
  readFileFromBranch
11
- } from "./chunk-K2FT7ZHJ.js";
14
+ } from "./chunk-N4VAGRBF.js";
12
15
  export {
13
16
  appendAndCommit,
14
17
  branchExists,
18
+ countBranchFileLines,
15
19
  createOrphanBranch,
16
20
  ensureRepoCloned,
17
21
  fetchBranch,
18
22
  getFileLog,
23
+ listBranchFiles,
19
24
  listRemoteBranches,
25
+ migrateToBuckets,
20
26
  readFileFromBranch
21
27
  };
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node --no-warnings=ExperimentalWarning
2
2
  import {
3
3
  appendAndCommit,
4
+ countBranchFileLines,
4
5
  createOrphanBranch,
5
6
  ensureRepoCloned,
6
7
  ensureThinkDirs,
@@ -12,10 +13,12 @@ import {
12
13
  getEngramsDir,
13
14
  getLongtermPath,
14
15
  getThinkDataDir,
16
+ listBranchFiles,
15
17
  listRemoteBranches,
18
+ migrateToBuckets,
16
19
  readFileFromBranch,
17
20
  saveConfig
18
- } from "./chunk-K2FT7ZHJ.js";
21
+ } from "./chunk-N4VAGRBF.js";
19
22
 
20
23
  // src/index.ts
21
24
  import fs11 from "fs";
@@ -1498,13 +1501,45 @@ var GitSyncAdapter = class {
1498
1501
  const config = getConfig();
1499
1502
  return !!config.cortex?.repo;
1500
1503
  }
1504
+ ensureMigrated(cortex, branchFiles) {
1505
+ const hasNumbered = branchFiles.some((f) => /^\d{6}\.jsonl$/.test(f));
1506
+ if (!hasNumbered) {
1507
+ const hasLegacy = readFileFromBranch(cortex, "memories.jsonl") !== null;
1508
+ if (hasLegacy) {
1509
+ migrateToBuckets(cortex);
1510
+ }
1511
+ }
1512
+ }
1513
+ determineBucketFile(cortex, branchFiles) {
1514
+ const config = getConfig();
1515
+ const bucketSize = config.cortex?.bucketSize ?? 500;
1516
+ const numbered = branchFiles.filter((f) => /^\d{6}\.jsonl$/.test(f));
1517
+ if (numbered.length === 0) return "000001.jsonl";
1518
+ const latestFile = numbered[numbered.length - 1];
1519
+ const lineCount = countBranchFileLines(cortex, latestFile);
1520
+ if (lineCount >= bucketSize) {
1521
+ const nextNum = parseInt(latestFile.replace(".jsonl", ""), 10) + 1;
1522
+ return String(nextNum).padStart(6, "0") + ".jsonl";
1523
+ }
1524
+ return latestFile;
1525
+ }
1501
1526
  async push(cortex) {
1502
1527
  const result = { pushed: 0, pulled: 0, errors: [] };
1503
1528
  ensureRepoCloned();
1529
+ fetchBranch(cortex);
1504
1530
  const cursorStr = getSyncCursor(cortex, "git", "push");
1505
1531
  const lastVersion = cursorStr ? parseInt(cursorStr, 10) : 0;
1532
+ const branchFiles = listBranchFiles(cortex, ".jsonl");
1533
+ try {
1534
+ this.ensureMigrated(cortex, branchFiles);
1535
+ } catch (err) {
1536
+ result.errors.push(`Migration failed: ${err instanceof Error ? err.message : String(err)}`);
1537
+ return result;
1538
+ }
1539
+ const currentFiles = branchFiles.some((f) => /^\d{6}\.jsonl$/.test(f)) ? branchFiles : listBranchFiles(cortex, ".jsonl");
1506
1540
  const newMemories = getMemoriesBySyncVersion(cortex, lastVersion);
1507
1541
  if (newMemories.length === 0) return result;
1542
+ const targetFile = this.determineBucketFile(cortex, currentFiles);
1508
1543
  const newLines = newMemories.map((m) => JSON.stringify({
1509
1544
  ts: m.ts,
1510
1545
  author: m.author,
@@ -1518,7 +1553,7 @@ var GitSyncAdapter = class {
1518
1553
  const maxVersion = Math.max(...newMemories.map((m) => m.sync_version));
1519
1554
  setSyncCursor(cortex, "git", "push", String(maxVersion));
1520
1555
  try {
1521
- appendAndCommit(cortex, newLines, commitMsg);
1556
+ appendAndCommit(cortex, newLines, commitMsg, 3, targetFile);
1522
1557
  result.pushed = newMemories.length;
1523
1558
  } catch (err) {
1524
1559
  setSyncCursor(cortex, "git", "push", String(lastVersion));
@@ -1526,16 +1561,7 @@ var GitSyncAdapter = class {
1526
1561
  }
1527
1562
  return result;
1528
1563
  }
1529
- async pull(cortex) {
1530
- const result = { pushed: 0, pulled: 0, errors: [] };
1531
- try {
1532
- ensureRepoCloned();
1533
- fetchBranch(cortex);
1534
- } catch (err) {
1535
- result.errors.push(err instanceof Error ? err.message : String(err));
1536
- return result;
1537
- }
1538
- const memoriesRaw = readFileFromBranch(cortex, "memories.jsonl") ?? "";
1564
+ processMemories(cortex, memoriesRaw, result) {
1539
1565
  const memories = parseMemoriesJsonl(memoriesRaw);
1540
1566
  for (const m of memories) {
1541
1567
  const id = deterministicId(m.ts, m.author, m.content);
@@ -1553,6 +1579,55 @@ var GitSyncAdapter = class {
1553
1579
  });
1554
1580
  if (wasInserted) result.pulled++;
1555
1581
  }
1582
+ }
1583
+ async pull(cortex) {
1584
+ const result = { pushed: 0, pulled: 0, errors: [] };
1585
+ try {
1586
+ ensureRepoCloned();
1587
+ fetchBranch(cortex);
1588
+ } catch (err) {
1589
+ result.errors.push(err instanceof Error ? err.message : String(err));
1590
+ return result;
1591
+ }
1592
+ const config = getConfig();
1593
+ const onboardingDepth = config.cortex?.onboardingDepth ?? 1500;
1594
+ const bucketSize = config.cortex?.bucketSize ?? 500;
1595
+ const files = listBranchFiles(cortex, ".jsonl").filter((f) => /^\d{6}\.jsonl$/.test(f)).sort();
1596
+ if (files.length === 0) {
1597
+ const memoriesRaw = readFileFromBranch(cortex, "memories.jsonl") ?? "";
1598
+ if (memoriesRaw) {
1599
+ this.processMemories(cortex, memoriesRaw, result);
1600
+ }
1601
+ return result;
1602
+ }
1603
+ const pullCursor = getSyncCursor(cortex, "git", "pull_file");
1604
+ let filesToRead;
1605
+ if (!pullCursor) {
1606
+ const numFiles = Math.ceil(onboardingDepth / bucketSize);
1607
+ filesToRead = files.slice(-numFiles);
1608
+ } else {
1609
+ const cursorIndex = files.indexOf(pullCursor);
1610
+ if (cursorIndex === -1) {
1611
+ const numFiles = Math.ceil(onboardingDepth / bucketSize);
1612
+ filesToRead = files.slice(-numFiles);
1613
+ } else {
1614
+ filesToRead = files.slice(cursorIndex);
1615
+ }
1616
+ }
1617
+ let lastReadFile = null;
1618
+ for (const file of filesToRead) {
1619
+ const raw = readFileFromBranch(cortex, file);
1620
+ if (raw === null) {
1621
+ break;
1622
+ }
1623
+ lastReadFile = file;
1624
+ if (raw.trim()) {
1625
+ this.processMemories(cortex, raw, result);
1626
+ }
1627
+ }
1628
+ if (lastReadFile) {
1629
+ setSyncCursor(cortex, "git", "pull_file", lastReadFile);
1630
+ }
1556
1631
  return result;
1557
1632
  }
1558
1633
  async sync(cortex) {
@@ -1623,7 +1698,7 @@ cortexCommand.addCommand(new Command9("setup").description("Configure a sync bac
1623
1698
  const adapter = getSyncAdapter();
1624
1699
  if (adapter) {
1625
1700
  try {
1626
- const { ensureRepoCloned: ensureRepoCloned2 } = await import("./git-Y3N244VA.js");
1701
+ const { ensureRepoCloned: ensureRepoCloned2 } = await import("./git-R4CVMKV7.js");
1627
1702
  ensureRepoCloned2();
1628
1703
  console.log(chalk9.green("\u2713") + " Repo cloned");
1629
1704
  } catch (err) {
@@ -2172,7 +2247,52 @@ var recallCommand = new Command12("recall").argument("<query>", "What to recall"
2172
2247
  // src/commands/memory.ts
2173
2248
  import { Command as Command13 } from "commander";
2174
2249
  import chalk13 from "chalk";
2175
- var memoryCommand = new Command13("memory").description("Show current memories from local store").option("--history", "Show recent memory timeline").action(async (opts) => {
2250
+ var addCommand = new Command13("add").description("Add a memory directly, bypassing curation").argument("<message>", "The memory content").option("--no-push", "Skip pushing to remote after adding").option("--silent", "Suppress output").action(async function(message, opts) {
2251
+ const globalOpts = this.optsWithGlobals();
2252
+ const config = getConfig();
2253
+ const cortex = globalOpts.cortex ?? config.cortex?.active;
2254
+ if (!cortex) {
2255
+ console.error(chalk13.red("No active cortex. Run: think cortex switch <name>"));
2256
+ process.exit(1);
2257
+ }
2258
+ const author = config.cortex?.author ?? "unknown";
2259
+ const validated = validateEngramContent(message);
2260
+ message = validated.content;
2261
+ if (!opts.silent && validated.warnings.length > 0) {
2262
+ for (const w of validated.warnings) {
2263
+ console.log(chalk13.yellow(` \u26A0 ${w}`));
2264
+ }
2265
+ }
2266
+ const row = insertMemory(cortex, {
2267
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2268
+ author,
2269
+ content: message,
2270
+ source_ids: []
2271
+ });
2272
+ if (!opts.silent) {
2273
+ const badge = chalk13.cyan(`[${cortex}]`);
2274
+ const ts = chalk13.gray(row.ts.slice(0, 16).replace("T", " "));
2275
+ console.log(`${chalk13.green("\u2713")} ${badge} memory added ${ts}`);
2276
+ console.log(` ${row.content}`);
2277
+ }
2278
+ if (opts.push) {
2279
+ const adapter = getSyncAdapter();
2280
+ if (adapter?.isAvailable()) {
2281
+ try {
2282
+ const result = await adapter.push(cortex);
2283
+ if (!opts.silent && result.pushed > 0) {
2284
+ console.log(chalk13.dim(` Pushed ${result.pushed} memories to ${adapter.name}`));
2285
+ }
2286
+ } catch {
2287
+ if (!opts.silent) {
2288
+ console.log(chalk13.dim(" Push skipped (remote unavailable) \u2014 will push on next sync"));
2289
+ }
2290
+ }
2291
+ }
2292
+ }
2293
+ closeCortexDb(cortex);
2294
+ });
2295
+ async function showMemories(opts) {
2176
2296
  const config = getConfig();
2177
2297
  const cortex = config.cortex?.active;
2178
2298
  if (!cortex) {
@@ -2200,7 +2320,11 @@ var memoryCommand = new Command13("memory").description("Show current memories f
2200
2320
  console.log(chalk13.dim(`
2201
2321
  ${memories.length} memories`));
2202
2322
  closeCortexDb(cortex);
2323
+ }
2324
+ var memoryCommand = new Command13("memory").description("Show current memories from local store").option("--history", "Show recent memory timeline").action(async (opts) => {
2325
+ await showMemories(opts);
2203
2326
  });
2327
+ memoryCommand.addCommand(addCommand);
2204
2328
 
2205
2329
  // src/commands/curator-cmd.ts
2206
2330
  import { Command as Command14 } from "commander";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-think",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "description": "Local-first CLI that gives AI agents persistent, curated memory",
6
6
  "bin": {