@cerefox/memory 1.1.0-beta.3 → 1.1.0-beta.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.
- package/dist/bin/cerefox.js +209 -15
- package/dist/frontend/assets/{index-BLqrpbNz.js → index-Co28kX04.js} +30 -30
- package/dist/frontend/assets/index-Co28kX04.js.map +1 -0
- package/dist/frontend/index.html +1 -1
- package/docs/guides/configuration.md +31 -4
- package/docs/guides/content-format.md +18 -0
- package/package.json +1 -1
- package/dist/frontend/assets/index-BLqrpbNz.js.map +0 -1
package/dist/bin/cerefox.js
CHANGED
|
@@ -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.
|
|
7441
|
+
var PKG_VERSION = "1.1.0-beta.5";
|
|
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}
|
|
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
|
-
|
|
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
|
-
|
|
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: 200,
|
|
78193
|
+
unit: "document",
|
|
78194
|
+
batchHint: "run it in batches with --limit 100"
|
|
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
|
-
|
|
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)) {
|
|
@@ -82330,6 +82384,107 @@ function registerAuditUsageRoutes(app, ctx) {
|
|
|
82330
82384
|
}
|
|
82331
82385
|
|
|
82332
82386
|
// src/web/routes/config.ts
|
|
82387
|
+
import { homedir as homedir9 } from "node:os";
|
|
82388
|
+
import { sep } from "node:path";
|
|
82389
|
+
|
|
82390
|
+
// ../../_shared/config-catalog/index.ts
|
|
82391
|
+
var CONFIG_CATALOG = [
|
|
82392
|
+
{
|
|
82393
|
+
key: "usage_tracking_enabled",
|
|
82394
|
+
description: "Log reads and writes to cerefox_usage_log.",
|
|
82395
|
+
kind: "boolean",
|
|
82396
|
+
defaultValue: "false",
|
|
82397
|
+
group: "Governance"
|
|
82398
|
+
},
|
|
82399
|
+
{
|
|
82400
|
+
key: "require_requestor_identity",
|
|
82401
|
+
description: "Require a requestor/author on MCP tool calls.",
|
|
82402
|
+
kind: "boolean",
|
|
82403
|
+
defaultValue: "false",
|
|
82404
|
+
group: "Governance",
|
|
82405
|
+
highImpact: true,
|
|
82406
|
+
impactNote: "Agents that do not send a requestor will start getting errors. Confirm your MCP clients identify themselves before enabling."
|
|
82407
|
+
},
|
|
82408
|
+
{
|
|
82409
|
+
key: "requestor_identity_format",
|
|
82410
|
+
description: "Regex the requestor/author must match. Only enforced while “require requestor identity” is on.",
|
|
82411
|
+
kind: "string",
|
|
82412
|
+
defaultValue: "",
|
|
82413
|
+
group: "Governance"
|
|
82414
|
+
},
|
|
82415
|
+
{
|
|
82416
|
+
key: "min_search_score",
|
|
82417
|
+
description: "Minimum cosine similarity for vector-side results. Use 0.6 with the local embedder.",
|
|
82418
|
+
kind: "number",
|
|
82419
|
+
defaultValue: "0.5",
|
|
82420
|
+
min: 0,
|
|
82421
|
+
max: 1,
|
|
82422
|
+
group: "Retrieval"
|
|
82423
|
+
},
|
|
82424
|
+
{
|
|
82425
|
+
key: "min_term_coverage",
|
|
82426
|
+
description: "Fraction of a query's meaningful terms a keyword OR-fallback match must cover to count as confident.",
|
|
82427
|
+
kind: "number",
|
|
82428
|
+
defaultValue: "0.5",
|
|
82429
|
+
min: 0,
|
|
82430
|
+
max: 1,
|
|
82431
|
+
group: "Retrieval"
|
|
82432
|
+
},
|
|
82433
|
+
{
|
|
82434
|
+
key: "search_alpha",
|
|
82435
|
+
description: "Hybrid fusion weight: 1 = pure semantic, 0 = pure keyword.",
|
|
82436
|
+
kind: "number",
|
|
82437
|
+
defaultValue: "0.7",
|
|
82438
|
+
min: 0,
|
|
82439
|
+
max: 1,
|
|
82440
|
+
group: "Retrieval"
|
|
82441
|
+
},
|
|
82442
|
+
{
|
|
82443
|
+
key: "relations_enabled",
|
|
82444
|
+
description: "Expose the four document-relation tools to agents. The feature is dormant until enabled.",
|
|
82445
|
+
kind: "boolean",
|
|
82446
|
+
defaultValue: "false",
|
|
82447
|
+
group: "Features",
|
|
82448
|
+
highImpact: true,
|
|
82449
|
+
impactNote: "This adds four tools (set/delete/get relations, get neighbours) to every connected agent's tool list — local MCP, remote MCP, and Edge Functions alike. Relations data and schema are always present; this switch only controls whether agents can see and use the tools. Turning it back off hides them again without deleting anything."
|
|
82450
|
+
}
|
|
82451
|
+
];
|
|
82452
|
+
function configKeySpec(key) {
|
|
82453
|
+
return CONFIG_CATALOG.find((k) => k.key === key);
|
|
82454
|
+
}
|
|
82455
|
+
var CONFIG_KEYS2 = CONFIG_CATALOG.map((k) => k.key);
|
|
82456
|
+
function validateConfigValue(key, value) {
|
|
82457
|
+
const spec = configKeySpec(key);
|
|
82458
|
+
if (!spec)
|
|
82459
|
+
return `Unknown config key: ${key}`;
|
|
82460
|
+
if (spec.kind === "boolean") {
|
|
82461
|
+
return value === "true" || value === "false" ? null : `${key} must be "true" or "false" (got ${JSON.stringify(value)}).`;
|
|
82462
|
+
}
|
|
82463
|
+
if (spec.kind === "number") {
|
|
82464
|
+
const n = Number(value);
|
|
82465
|
+
if (!Number.isFinite(n)) {
|
|
82466
|
+
return `${key} must be a number (got ${JSON.stringify(value)}).`;
|
|
82467
|
+
}
|
|
82468
|
+
if (spec.min !== undefined && n < spec.min) {
|
|
82469
|
+
return `${key} must be ≥ ${spec.min} (got ${n}).`;
|
|
82470
|
+
}
|
|
82471
|
+
if (spec.max !== undefined && n > spec.max) {
|
|
82472
|
+
return `${key} must be ≤ ${spec.max} (got ${n}).`;
|
|
82473
|
+
}
|
|
82474
|
+
return null;
|
|
82475
|
+
}
|
|
82476
|
+
if (key === "requestor_identity_format" && value.length > 0) {
|
|
82477
|
+
try {
|
|
82478
|
+
new RegExp(value);
|
|
82479
|
+
} catch (err) {
|
|
82480
|
+
return `${key} must be a valid regular expression: ${err instanceof Error ? err.message : String(err)}`;
|
|
82481
|
+
}
|
|
82482
|
+
}
|
|
82483
|
+
return null;
|
|
82484
|
+
}
|
|
82485
|
+
|
|
82486
|
+
// src/web/routes/config.ts
|
|
82487
|
+
init_config();
|
|
82333
82488
|
function unwrapScalarRpc(data) {
|
|
82334
82489
|
if (typeof data === "string")
|
|
82335
82490
|
return data;
|
|
@@ -82339,7 +82494,43 @@ function unwrapScalarRpc(data) {
|
|
|
82339
82494
|
}
|
|
82340
82495
|
return null;
|
|
82341
82496
|
}
|
|
82497
|
+
var ENV_OVERRIDES = {
|
|
82498
|
+
min_search_score: "CEREFOX_MIN_SEARCH_SCORE",
|
|
82499
|
+
min_term_coverage: "CEREFOX_MIN_TERM_COVERAGE",
|
|
82500
|
+
search_alpha: "CEREFOX_SEARCH_ALPHA"
|
|
82501
|
+
};
|
|
82342
82502
|
function registerConfigRoutes(app, ctx) {
|
|
82503
|
+
app.get("/api/v1/config", async (c2) => {
|
|
82504
|
+
const entries = await Promise.all(CONFIG_CATALOG.map(async (spec) => {
|
|
82505
|
+
const { data, error: error3 } = await ctx.supabase.rpc("cerefox_get_config", {
|
|
82506
|
+
p_key: spec.key
|
|
82507
|
+
});
|
|
82508
|
+
const stored = error3 ? null : unwrapScalarRpc(data);
|
|
82509
|
+
const envVar = ENV_OVERRIDES[spec.key];
|
|
82510
|
+
const envValue = envVar ? (process.env[envVar] ?? "").trim() : "";
|
|
82511
|
+
return {
|
|
82512
|
+
key: spec.key,
|
|
82513
|
+
value: stored,
|
|
82514
|
+
effective: stored ?? spec.defaultValue,
|
|
82515
|
+
description: spec.description,
|
|
82516
|
+
kind: spec.kind,
|
|
82517
|
+
default: spec.defaultValue,
|
|
82518
|
+
min: spec.min ?? null,
|
|
82519
|
+
max: spec.max ?? null,
|
|
82520
|
+
group: spec.group,
|
|
82521
|
+
high_impact: spec.highImpact ?? false,
|
|
82522
|
+
impact_note: spec.impactNote ?? null,
|
|
82523
|
+
env_override: envValue ? { name: envVar, value: envValue } : null
|
|
82524
|
+
};
|
|
82525
|
+
}));
|
|
82526
|
+
let configFile = null;
|
|
82527
|
+
try {
|
|
82528
|
+
const abs = resolveEnvFile();
|
|
82529
|
+
const home = homedir9();
|
|
82530
|
+
configFile = abs === home || abs.startsWith(home + sep) ? `~${abs.slice(home.length)}` : abs;
|
|
82531
|
+
} catch {}
|
|
82532
|
+
return c2.json({ keys: entries, config_file: configFile });
|
|
82533
|
+
});
|
|
82343
82534
|
app.get("/api/v1/config/:key", async (c2) => {
|
|
82344
82535
|
const key = c2.req.param("key");
|
|
82345
82536
|
const { data, error: error3 } = await ctx.supabase.rpc("cerefox_get_config", {
|
|
@@ -82358,6 +82549,9 @@ function registerConfigRoutes(app, ctx) {
|
|
|
82358
82549
|
return c2.json({ detail: "Invalid JSON body" }, 400);
|
|
82359
82550
|
}
|
|
82360
82551
|
const value = typeof body.value === "string" ? body.value : String(body.value ?? "");
|
|
82552
|
+
const invalid = validateConfigValue(key, value);
|
|
82553
|
+
if (invalid)
|
|
82554
|
+
return c2.json({ detail: invalid }, 400);
|
|
82361
82555
|
const { error: error3 } = await ctx.supabase.rpc("cerefox_set_config", {
|
|
82362
82556
|
p_key: key,
|
|
82363
82557
|
p_value: value
|
|
@@ -84017,9 +84211,9 @@ import {
|
|
|
84017
84211
|
rmSync,
|
|
84018
84212
|
writeFileSync as writeFileSync7
|
|
84019
84213
|
} from "node:fs";
|
|
84020
|
-
import { homedir as
|
|
84214
|
+
import { homedir as homedir10 } from "node:os";
|
|
84021
84215
|
import { join as join18 } from "node:path";
|
|
84022
|
-
function resolveStateDir(override = process.env.CEREFOX_CONFIG_DIR, home =
|
|
84216
|
+
function resolveStateDir(override = process.env.CEREFOX_CONFIG_DIR, home = homedir10()) {
|
|
84023
84217
|
override = (override ?? "").trim();
|
|
84024
84218
|
if (!override)
|
|
84025
84219
|
return join18(home, ".cerefox");
|