@cerefox/memory 1.1.0-beta.3 → 1.1.0-beta.4

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.
@@ -7438,7 +7438,7 @@ var exports_meta = {};
7438
7438
  __export(exports_meta, {
7439
7439
  PKG_VERSION: () => PKG_VERSION
7440
7440
  });
7441
- var PKG_VERSION = "1.1.0-beta.3";
7441
+ var PKG_VERSION = "1.1.0-beta.4";
7442
7442
  var init_meta = () => {};
7443
7443
 
7444
7444
  // ../../_shared/config/paths.ts
@@ -75712,11 +75712,10 @@ function checkConfig() {
75712
75712
  modeDetail = ` (mode 0${mode.toString(8)})`;
75713
75713
  } catch {}
75714
75714
  }
75715
- const envLabel = (process.env.CEREFOX_ENV_LABEL ?? "").trim();
75716
75715
  return {
75717
75716
  name: "config",
75718
75717
  status: "ok",
75719
- detail: `${envPath}${modeDetail}${envLabel ? ` [${envLabel.toUpperCase()}]` : ""}`
75718
+ detail: `${envPath}${modeDetail}`
75720
75719
  };
75721
75720
  }
75722
75721
  async function checkSupabase() {
@@ -76231,7 +76230,8 @@ async function action15(options) {
76231
76230
  if (options.json) {
76232
76231
  printJson(results);
76233
76232
  } else {
76234
- println("Cerefox doctor");
76233
+ const envLabel = (process.env.CEREFOX_ENV_LABEL ?? "").trim();
76234
+ println(envLabel ? `Cerefox doctor ${c.yellow(`[${envLabel.toUpperCase()}]`)}` : "Cerefox doctor");
76235
76235
  println("");
76236
76236
  const nameWidth = Math.max(...results.map((r) => r.name.length));
76237
76237
  for (const r of results) {
@@ -76793,7 +76793,8 @@ class IngestionPipeline {
76793
76793
  author = "unknown",
76794
76794
  authorType = "user",
76795
76795
  expectedContentHash,
76796
- lastWriteWins = false
76796
+ lastWriteWins = false,
76797
+ forceRechunk = false
76797
76798
  } = opts;
76798
76799
  const listFormProvided = projectIds !== undefined && projectIds !== null || projectNames !== undefined && projectNames !== null;
76799
76800
  const getOrCreate = (name) => this.db.getOrCreateProject(name);
@@ -76816,7 +76817,8 @@ class IngestionPipeline {
76816
76817
  author,
76817
76818
  authorType,
76818
76819
  expectedContentHash,
76819
- lastWriteWins
76820
+ lastWriteWins,
76821
+ forceRechunk
76820
76822
  });
76821
76823
  if (!listFormProvided && (projectId || projectName)) {
76822
76824
  const singular = await resolveProjectIds({ projectId, projectName }, getOrCreate);
@@ -76941,7 +76943,8 @@ class IngestionPipeline {
76941
76943
  author = "unknown",
76942
76944
  authorType = "user",
76943
76945
  expectedContentHash,
76944
- lastWriteWins = false
76946
+ lastWriteWins = false,
76947
+ forceRechunk = false
76945
76948
  } = opts;
76946
76949
  const existing = await this.db.getDocumentById(documentId);
76947
76950
  if (!existing) {
@@ -76966,7 +76969,7 @@ class IngestionPipeline {
76966
76969
  }
76967
76970
  const actualChunks = await this.db.listChunksForDocument(documentId);
76968
76971
  const hasChunks = actualChunks.length > 0;
76969
- if (contentUnchanged && hasChunks) {
76972
+ if (contentUnchanged && hasChunks && !forceRechunk) {
76970
76973
  const oldTitle = existing.title ?? "";
76971
76974
  const titleChanged = oldTitle !== title;
76972
76975
  const updates = { title };
@@ -77053,7 +77056,7 @@ class IngestionPipeline {
77053
77056
  sourceLabel: source,
77054
77057
  retentionHours: this.settings.versionRetentionHours,
77055
77058
  cleanupEnabled: this.settings.versionCleanupEnabled,
77056
- expectedContentHash: expectedContentHash ?? null,
77059
+ expectedContentHash: expectedContentHash ?? (forceRechunk && contentUnchanged ? existing.content_hash : null),
77057
77060
  lastWriteWins
77058
77061
  });
77059
77062
  let finalProjectIds;
@@ -78032,6 +78035,25 @@ function registerMetadataSearch(program2) {
78032
78035
  init_dist4();
78033
78036
  init_cli_core();
78034
78037
  init_config();
78038
+
78039
+ // src/cli/util/bulk-write-warning.ts
78040
+ init_cli_core();
78041
+ function warnLargeBulkWrite(opts) {
78042
+ if (opts.count < opts.threshold)
78043
+ return false;
78044
+ println("");
78045
+ println(c.yellow("⚠ ") + `${opts.count.toLocaleString()} ${opts.unit}(s) is a large bulk rewrite.`);
78046
+ println(c.dim(` On Supabase (especially the free tier and small compute add-ons) this can
78047
+ ` + ` deplete the project's Disk IO Budget. Symptoms: slower responses, CPU
78048
+ ` + ` climbing on IO wait, and in the worst case a briefly unresponsive
78049
+ ` + " instance. It recovers on its own once the budget refills."));
78050
+ println(c.dim(` Gentler: ${opts.batchHint}, leaving time between runs and ideally
78051
+ ` + " picking a quiet period. The work is resumable — re-run to continue."));
78052
+ println("");
78053
+ return true;
78054
+ }
78055
+
78056
+ // src/cli/commands/reindex.ts
78035
78057
  async function action27(options) {
78036
78058
  const settings = loadSettings();
78037
78059
  if (!settings.supabaseUrl || !settings.supabaseKey) {
@@ -78070,6 +78092,12 @@ async function action27(options) {
78070
78092
  return;
78071
78093
  }
78072
78094
  println(c.bold(`Reindexing ${chunks.length} chunk(s) ${reindexAll ? "(--all)" : "(stale only)"}${dryRun ? " — DRY RUN" : ""}`));
78095
+ warnLargeBulkWrite({
78096
+ count: chunks.length,
78097
+ threshold: 1000,
78098
+ unit: "chunk",
78099
+ batchHint: "reindex in stages with --document-id"
78100
+ });
78073
78101
  if (dryRun) {
78074
78102
  const byDoc = new Map;
78075
78103
  for (const ch of chunks)
@@ -78139,7 +78167,16 @@ async function action28(options) {
78139
78167
  } catch (err) {
78140
78168
  throw systemError(`Could not list legacy chunks: ${err instanceof Error ? err.message : String(err)}`);
78141
78169
  }
78142
- const docIds = [...new Set(legacyChunkRows.map((r) => r.document_id))];
78170
+ let liveIds = new Set;
78171
+ try {
78172
+ const liveRows = await fetchAllPages((from, to) => supabase.from("cerefox_documents").select("id").is("deleted_at", null).order("id", { ascending: true }).range(from, to));
78173
+ liveIds = new Set(liveRows.map((r) => r.id));
78174
+ } catch (err) {
78175
+ throw systemError(`Could not list documents: ${err instanceof Error ? err.message : String(err)}`);
78176
+ }
78177
+ const allLegacy = [...new Set(legacyChunkRows.map((r) => r.document_id))];
78178
+ const docIds = allLegacy.filter((id) => liveIds.has(id));
78179
+ const trashedSkipped = allLegacy.length - docIds.length;
78143
78180
  const limit = options.limit ? parsePositiveInt(options.limit, "--limit", docIds.length) : docIds.length;
78144
78181
  const targets = docIds.slice(0, limit);
78145
78182
  if (targets.length === 0) {
@@ -78147,6 +78184,15 @@ async function action28(options) {
78147
78184
  return;
78148
78185
  }
78149
78186
  println(c.bold(`${docIds.length} document(s) on the legacy format` + (targets.length < docIds.length ? `; converting ${targets.length} (--limit)` : "")));
78187
+ if (trashedSkipped > 0) {
78188
+ println(c.dim(` (${trashedSkipped} trashed document(s) on the legacy format ignored)`));
78189
+ }
78190
+ warnLargeBulkWrite({
78191
+ count: targets.length,
78192
+ threshold: 500,
78193
+ unit: "document",
78194
+ batchHint: "run it in batches with --limit 200"
78195
+ });
78150
78196
  if (options.dryRun) {
78151
78197
  println(c.yellow("⚠ --dry-run: nothing was written."));
78152
78198
  println(c.dim(" Each document would be re-chunked and RE-EMBEDDED (embedding spend)."));
@@ -78182,16 +78228,24 @@ async function action28(options) {
78182
78228
  continue;
78183
78229
  }
78184
78230
  try {
78185
- await pipeline2.ingestText({
78231
+ const result = await pipeline2.ingestText({
78186
78232
  text: doc2.full_content,
78187
78233
  title: doc2.doc_title,
78188
78234
  documentId: id,
78189
78235
  source: "migrate-format",
78190
78236
  author,
78191
78237
  authorType,
78192
- expectedContentHash: doc2.content_hash
78238
+ expectedContentHash: doc2.content_hash,
78239
+ forceRechunk: true
78193
78240
  });
78194
- converted++;
78241
+ if (result.reindexed) {
78242
+ converted++;
78243
+ } else {
78244
+ failures.push({
78245
+ document: `${doc2.doc_title} (${id})`,
78246
+ reason: `pipeline reported no re-chunk (action=${result.action}); format not advanced`
78247
+ });
78248
+ }
78195
78249
  } catch (err) {
78196
78250
  const message = err instanceof Error ? err.message : String(err);
78197
78251
  if (/conflict/i.test(message)) {
@@ -265,7 +265,7 @@ cerefox document get <document-id> --version-id <version-id>
265
265
  | Variable | Default | Description |
266
266
  |----------|---------|-------------|
267
267
  | `CEREFOX_BACKUP_DIR` | `~/.cerefox/backups` | Local directory where file system backups are stored. Created automatically if it doesn't exist. **Use an absolute path** — a relative value (such as the pre-v0.3.0 `./backups`) resolves against the current working directory, so snapshots scatter depending on where you run the command; `backup create` warns when it sees one. Does **not** follow `CEREFOX_CONFIG_DIR`, so a second environment must set it explicitly. |
268
- | `CEREFOX_ENV_LABEL` | _(unset)_ | Names a non-production environment (e.g. `staging`). Purely cosmetic and inert when unset. When set: the web UI shows a banner on every page, `doctor` appends `[LABEL]` to its config line, `backup create` puts the label in the snapshot filename and payload, and `backup restore` warns when a snapshot's environment differs from the target's. See [`staging-env.md`](staging-env.md). |
268
+ | `CEREFOX_ENV_LABEL` | _(unset)_ | Names a non-production environment (e.g. `staging`). Purely cosmetic and inert when unset. When set: the web UI shows a banner on every page, `doctor` shows `[LABEL]` on its title line, `backup create` puts the label in the snapshot filename and payload, and `backup restore` warns when a snapshot's environment differs from the target's. See [`staging-env.md`](staging-env.md). |
269
269
  | `CEREFOX_VERSION_RETENTION_HOURS` | `48` | How long to retain archived document versions (hours). The most recent version is always kept regardless of this setting. |
270
270
 
271
271
  ---
@@ -49,6 +49,24 @@ in — even after the current version has moved to format 2.
49
49
  > existing chunk rows; it never re-chunks, so it cannot advance the stored
50
50
  > format. Earlier versions of this guide said otherwise (#164).
51
51
 
52
+ > **Large stores: batch it.** Converting a few hundred documents or more is a
53
+ > heavy bulk rewrite — each one is re-chunked, its previous chunks archived as
54
+ > a version snapshot, and new rows inserted. A contributor reindexing a
55
+ > ~1,300-document store on Supabase depleted the project's **Disk IO Budget**
56
+ > and got a warning email: slower responses, CPU climbing on IO wait, and
57
+ > potentially a briefly unresponsive instance (it recovers once the budget
58
+ > refills). Nothing is corrupted, but prefer `--limit 200` in stages during a
59
+ > quiet period. The command warns above 500 documents and is resumable.
60
+
61
+ > **Requires v1.1.0-beta.4 or later.** On **v1.0.7 through v1.1.0-beta.3**
62
+ > `migrate-format` printed `Converted N` while converting nothing: it
63
+ > re-ingests byte-identical content by design, and the pipeline answered an
64
+ > unchanged content hash with a metadata-only update — no re-chunk, so no
65
+ > format advance. If you ran it on one of those versions, the documents are
66
+ > untouched and still on format 1 (no data was harmed and no embedding spend
67
+ > was incurred); re-run it on a current build. Check with `cerefox doctor`,
68
+ > which reports the real count.
69
+
52
70
  `cerefox doctor` reports how many documents still use the legacy format — purely
53
71
  informational, never a failure. A fresh install shows zero.
54
72
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cerefox/memory",
3
- "version": "1.1.0-beta.3",
3
+ "version": "1.1.0-beta.4",
4
4
  "description": "Cerefox — user-owned shared memory for AI agents. CLI + stdio MCP server + web UI + ingestion for a knowledge base on your own Supabase project (or fully self-hosted with Cerefox Local).",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/fstamatelopoulos/cerefox",