@cerefox/memory 1.0.2 → 1.0.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.
- package/AGENT_QUICK_REFERENCE.md +2 -1
- package/dist/bin/cerefox.js +120 -44
- package/dist/frontend/assets/{index-CLD16BC4.js → index-Csj-6UHY.js} +2 -2
- package/dist/frontend/assets/{index-CLD16BC4.js.map → index-Csj-6UHY.js.map} +1 -1
- package/dist/frontend/index.html +1 -1
- package/dist/server-assets/_shared/ef-meta/index.ts +11 -1
- package/dist/server-assets/_shared/mcp-tools/_utils.ts +15 -0
- package/dist/server-assets/_shared/mcp-tools/get-help-content.ts +2 -2
- package/dist/server-assets/_shared/mcp-tools/search.ts +47 -2
- package/dist/server-assets/db/rpcs.sql +188 -26
- package/dist/server-assets/db/schema.sql +1 -1
- package/docs/guides/configuration.md +9 -0
- package/package.json +1 -1
package/AGENT_QUICK_REFERENCE.md
CHANGED
|
@@ -28,7 +28,8 @@ Cerefox is a persistent, shared knowledge base. You have **10 MCP tools** (9 of
|
|
|
28
28
|
7. **Deletes are soft (recoverable); purge is web-UI-only.** If you decide to delete, surface it to the user (`I soft-deleted X — recoverable from the Cerefox web UI trash`). You cannot un-do your own delete from agent code by design.
|
|
29
29
|
8. **Cross-doc links inside content**: **always use `[Text](document-uuid)`.** UUIDs are the only fully reliable link form — stable across title changes, never ambiguous, no encoding gotchas. Every `cerefox_search` result shows `[id: <uuid>]` after the title; grab it and use it. Title-based linking (`[Text](<Title With Spaces>)`) is fragile (breaks on colons, parens, ampersands, brackets — silently navigates to wrong page) — **don't write title-based links**; do an extra search to get the UUID instead. Repo-path forms (`[Text](docs/path.md)`) exist for repo-ingested files; don't construct manually. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.
|
|
30
30
|
9. **Concurrency: content updates require `expected_content_hash`.** Pass the `content_hash` you read (shown by `cerefox_get_document`, `cerefox_search`, and `cerefox_metadata_search`) when updating a document. If it's stale you get a **conflict** — re-read the document, merge your changes into the latest content, retry with the new hash. **Never resolve a conflict by overwriting blindly** — the current content includes another writer's work. `last_write_wins: true` skips the check; use it ONLY when an external source of truth makes conflicts meaningless (file re-sync), never to silence a conflict.
|
|
31
|
-
10. **
|
|
31
|
+
10. **Search: prefer a few distinctive terms; heed `below confidence`.** When nothing clears the relevance threshold, `cerefox_search` returns the closest candidates prefixed with a `below confidence` warning instead of an empty set — that flag means **weak signal, not absent knowledge**: check the candidates' scores and titles before concluding the KB lacks the content. A truly empty response means nothing even weakly related exists.
|
|
32
|
+
11. **Project memberships — non-destructive by default**: on `cerefox_ingest` updates, **`project_name` (singular) is a non-destructive add** (ensures membership, preserves others). Use **`project_names` (list)** when you want to set the doc's full project set in one call (destructive replace). For metadata-only project changes without writing content, use **`cerefox_set_document_projects(document_id, project_names)`** — that tool is the destructive-replace contract made explicit. Never call `cerefox_set_document_projects` with a single name when you mean "add" — that would REMOVE the doc from all other projects. When in doubt, use `cerefox_ingest` with singular `project_name`.
|
|
32
33
|
|
|
33
34
|
## Update Workflow (ID-based -- preferred)
|
|
34
35
|
|
package/dist/bin/cerefox.js
CHANGED
|
@@ -7184,7 +7184,7 @@ var exports_meta = {};
|
|
|
7184
7184
|
__export(exports_meta, {
|
|
7185
7185
|
PKG_VERSION: () => PKG_VERSION
|
|
7186
7186
|
});
|
|
7187
|
-
var PKG_VERSION = "1.0.
|
|
7187
|
+
var PKG_VERSION = "1.0.4";
|
|
7188
7188
|
var init_meta = () => {};
|
|
7189
7189
|
|
|
7190
7190
|
// ../../node_modules/.bun/tslib@2.8.1/node_modules/tslib/tslib.js
|
|
@@ -25225,6 +25225,13 @@ function getMinSearchScore() {
|
|
|
25225
25225
|
const n = Number.parseFloat(raw);
|
|
25226
25226
|
return Number.isNaN(n) || n < 0 || n > 1 ? fallback : n;
|
|
25227
25227
|
}
|
|
25228
|
+
function getMinTermCoverage() {
|
|
25229
|
+
const raw = globalThis.process?.env?.CEREFOX_MIN_TERM_COVERAGE;
|
|
25230
|
+
if (raw === undefined || raw === "")
|
|
25231
|
+
return;
|
|
25232
|
+
const n = Number.parseFloat(raw);
|
|
25233
|
+
return Number.isNaN(n) || n < 0 || n > 1 ? undefined : n;
|
|
25234
|
+
}
|
|
25228
25235
|
function applyByteBudget(rows, maxBytes) {
|
|
25229
25236
|
const accepted = [];
|
|
25230
25237
|
let usedBytes = 0;
|
|
@@ -54966,11 +54973,11 @@ var init_get_document = __esm(() => {
|
|
|
54966
54973
|
});
|
|
54967
54974
|
|
|
54968
54975
|
// ../../_shared/mcp-tools/get-help-content.ts
|
|
54969
|
-
var HELP_FULL = '# Cerefox Knowledge Base -- Agent Quick Reference\n\nCerefox is a persistent, shared knowledge base. You have **10 MCP tools** (9 of them have CLI equivalents — `cerefox_get_help` is MCP-only). For the full guide, search Cerefox for "How AI Agents Use Cerefox" or call `cerefox_get_help` to retrieve this content over MCP.\n\n## Tools\n\n| Tool | Purpose | Key params |\n|------|---------|------------|\n| `cerefox_search` | Find documents (hybrid FTS + semantic) | `query` (required), `project_name`, `metadata_filter`, `requestor` |\n| `cerefox_ingest` | Save or update a document | `title`, `content` (required), `document_id` (update by ID), `expected_content_hash` (**required on content updates** — see rule 9), `last_write_wins`, `update_if_exists`, `project_name` (single, non-destructive add on update), `project_names` (list, destructive replace on update), `metadata` (omit on update to keep existing tags; `{}` clears), `author` |\n| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token) | `document_id` (required) |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required) |\n| `cerefox_metadata_search` | Find or list docs by metadata, project, or time (no text query) | `metadata_filter`, `project_name` (list a project\'s docs), `updated_since`, `include_content` — **at least one** of metadata_filter/project_name/updated_since/created_since |\n| `cerefox_list_metadata_keys` | Discover available metadata keys | (none required) |\n| `cerefox_list_projects` | List all projects | (none required) |\n| `cerefox_set_document_projects` | Set doc\'s project memberships to exactly the given list (destructive replace; metadata-only, no content change) | `document_id`, `project_names` (required) |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `author`, `operation`, `since` |\n| `cerefox_get_help` | Retrieve Cerefox conventions (this reference) over MCP. **Call this whenever uncertain.** | `topic` (optional, case-insensitive H2 substring match) |\n\n## Essential Rules\n\n1. **Search before ingesting** -- check if the document exists first.\n2. **Prefer ID-based updates** -- pass `document_id` from search results for deterministic updates. Falls back to title-matching with `update_if_exists: true`.\n3. **Set `author`/`requestor`** to your name on every call (e.g., "Claude Code", "archiver"). On MCP, pass as parameters. On CLI, pass `--author`/`--author-type`/`--requestor` flags, or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE`/`CEREFOX_REQUESTOR_NAME` env vars set in the user\'s `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **Add metadata** -- at minimum `type` ("decision-log", "research", "design-doc") and `status` ("active", "draft").\n6. **Write structured Markdown** with H1/H2/H3 headings for good chunking and search.\n7. **Deletes are soft (recoverable); purge is web-UI-only.** If you decide to delete, surface it to the user (`I soft-deleted X — recoverable from the Cerefox web UI trash`). You cannot un-do your own delete from agent code by design.\n8. **Cross-doc links inside content**: **always use `[Text](document-uuid)`.** UUIDs are the only fully reliable link form — stable across title changes, never ambiguous, no encoding gotchas. Every `cerefox_search` result shows `[id: <uuid>]` after the title; grab it and use it. Title-based linking (`[Text](<Title With Spaces>)`) is fragile (breaks on colons, parens, ampersands, brackets — silently navigates to wrong page) — **don\'t write title-based links**; do an extra search to get the UUID instead. Repo-path forms (`[Text](docs/path.md)`) exist for repo-ingested files; don\'t construct manually. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n9. **Concurrency: content updates require `expected_content_hash`.** Pass the `content_hash` you read (shown by `cerefox_get_document`, `cerefox_search`, and `cerefox_metadata_search`) when updating a document. If it\'s stale you get a **conflict** — re-read the document, merge your changes into the latest content, retry with the new hash. **Never resolve a conflict by overwriting blindly** — the current content includes another writer\'s work. `last_write_wins: true` skips the check; use it ONLY when an external source of truth makes conflicts meaningless (file re-sync), never to silence a conflict.\n10. **Project memberships — non-destructive by default**: on `cerefox_ingest` updates, **`project_name` (singular) is a non-destructive add** (ensures membership, preserves others). Use **`project_names` (list)** when you want to set the doc\'s full project set in one call (destructive replace). For metadata-only project changes without writing content, use **`cerefox_set_document_projects(document_id, project_names)`** — that tool is the destructive-replace contract made explicit. Never call `cerefox_set_document_projects` with a single name when you mean "add" — that would REMOVE the doc from all other projects. When in doubt, use `cerefox_ingest` with singular `project_name`.\n\n## Update Workflow (ID-based -- preferred)\n\n```\nsearch("topic") -> find doc [id: abc123] -> get_document(abc123) -> note its content_hash -> modify ->\ningest(title="Same Title", content="...", document_id="abc123",\n expected_content_hash="<the hash you read>", author="my-agent")\n```\n\nOn a **conflict** error: get_document again (fresh content + fresh hash) -> merge your changes -> retry with the new hash.\n\n## Update Workflow (title-based -- fallback)\n\n```\nsearch("topic") -> find doc (note its hash) -> modify ->\ningest(title="Same Title", content="...", update_if_exists=true,\n expected_content_hash="<the hash you read>", author="my-agent")\n```\n\n## Catch-Up Workflow\n\n```\nmetadata_search(metadata_filter={"type": "decision-log"}, updated_since="2026-03-28T00:00:00Z")\n```\n\n## CLI fallback (when MCP is unavailable)\n\nIf `cerefox_search` is not in your tool list, your user has likely installed the Cerefox CLI. The canonical invocation is plain **`cerefox <subcommand>`** (the TypeScript CLI, installed via `npm install -g @cerefox/memory`). It uses a resource-verb shape (`cerefox document get`, `cerefox project list`, …).\n\nSame operations, same conventions. Full reference: [`docs/guides/cli.md`](docs/guides/cli.md). CLI flag names match MCP parameter names exactly (e.g. `metadata_filter` ↔ `--metadata-filter`); common flags also have single-letter short forms (`-f`, `-p`, `-c`, `-m`, `-u`, `-a`, `-r`). Use the canonical long name (what `--help` shows) or its short form — there are no long-form aliases like `--filter` or `--count`.\n\n| MCP tool | CLI |\n|---|---|\n| `cerefox_search` | `cerefox search "<q>" --requestor "<your-name>"` |\n| `cerefox_ingest` (paste) | `printf \'...\' \\| cerefox document ingest --paste --title "<t>" --author "<your-name>" --author-type agent` |\n| `cerefox_ingest` (update by ID) | `printf \'...\' \\| cerefox document ingest --paste --title "<t>" --document-id "<uuid>" --expected-content-hash "<hash>" --author "<your-name>" --author-type agent` |\n| `cerefox_get_document` | `cerefox document get <id> --version-id <vid> --requestor "<your-name>"` |\n| `cerefox_list_versions` | `cerefox document version list <id> --requestor "<your-name>"` |\n| `cerefox_list_projects` | `cerefox project list --requestor "<your-name>"` |\n| `cerefox_list_metadata_keys` | `cerefox metadata keys` |\n| `cerefox_metadata_search` | `cerefox metadata search --metadata-filter \'<json>\' --requestor "<your-name>"` (list a project: `cerefox document list --project <name>`) |\n| `cerefox_set_document_projects` | `cerefox document set-projects <id> <name...> --author "<your-name>" --author-type agent` (or `--clear` to remove all) |\n| `cerefox_get_audit_log` | `cerefox audit list --requestor "<your-name>"` (add `--json` for scripted access) |\n| `cerefox_get_help` | `cerefox guides show agent-quick-reference` (or `cerefox guides list` for the full bundled-docs index) |\n\n**Set identity on every call**, exactly as you would on MCP:\n- Writes (`document ingest`, `document ingest-dir`): `--author "<your-name>" --author-type agent`\n- Reads: `--requestor "<your-name>"`\n\nOr have your user set `CEREFOX_AUTHOR_NAME` / `CEREFOX_AUTHOR_TYPE` / `CEREFOX_REQUESTOR_NAME` in their `.env` to apply defaults once.\n', HELP_SECTIONS, HELP_SECTION_HEADINGS;
|
|
54976
|
+
var HELP_FULL = '# Cerefox Knowledge Base -- Agent Quick Reference\n\nCerefox is a persistent, shared knowledge base. You have **10 MCP tools** (9 of them have CLI equivalents — `cerefox_get_help` is MCP-only). For the full guide, search Cerefox for "How AI Agents Use Cerefox" or call `cerefox_get_help` to retrieve this content over MCP.\n\n## Tools\n\n| Tool | Purpose | Key params |\n|------|---------|------------|\n| `cerefox_search` | Find documents (hybrid FTS + semantic) | `query` (required), `project_name`, `metadata_filter`, `requestor` |\n| `cerefox_ingest` | Save or update a document | `title`, `content` (required), `document_id` (update by ID), `expected_content_hash` (**required on content updates** — see rule 9), `last_write_wins`, `update_if_exists`, `project_name` (single, non-destructive add on update), `project_names` (list, destructive replace on update), `metadata` (omit on update to keep existing tags; `{}` clears), `author` |\n| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token) | `document_id` (required) |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required) |\n| `cerefox_metadata_search` | Find or list docs by metadata, project, or time (no text query) | `metadata_filter`, `project_name` (list a project\'s docs), `updated_since`, `include_content` — **at least one** of metadata_filter/project_name/updated_since/created_since |\n| `cerefox_list_metadata_keys` | Discover available metadata keys | (none required) |\n| `cerefox_list_projects` | List all projects | (none required) |\n| `cerefox_set_document_projects` | Set doc\'s project memberships to exactly the given list (destructive replace; metadata-only, no content change) | `document_id`, `project_names` (required) |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `author`, `operation`, `since` |\n| `cerefox_get_help` | Retrieve Cerefox conventions (this reference) over MCP. **Call this whenever uncertain.** | `topic` (optional, case-insensitive H2 substring match) |\n\n## Essential Rules\n\n1. **Search before ingesting** -- check if the document exists first.\n2. **Prefer ID-based updates** -- pass `document_id` from search results for deterministic updates. Falls back to title-matching with `update_if_exists: true`.\n3. **Set `author`/`requestor`** to your name on every call (e.g., "Claude Code", "archiver"). On MCP, pass as parameters. On CLI, pass `--author`/`--author-type`/`--requestor` flags, or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE`/`CEREFOX_REQUESTOR_NAME` env vars set in the user\'s `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **Add metadata** -- at minimum `type` ("decision-log", "research", "design-doc") and `status` ("active", "draft").\n6. **Write structured Markdown** with H1/H2/H3 headings for good chunking and search.\n7. **Deletes are soft (recoverable); purge is web-UI-only.** If you decide to delete, surface it to the user (`I soft-deleted X — recoverable from the Cerefox web UI trash`). You cannot un-do your own delete from agent code by design.\n8. **Cross-doc links inside content**: **always use `[Text](document-uuid)`.** UUIDs are the only fully reliable link form — stable across title changes, never ambiguous, no encoding gotchas. Every `cerefox_search` result shows `[id: <uuid>]` after the title; grab it and use it. Title-based linking (`[Text](<Title With Spaces>)`) is fragile (breaks on colons, parens, ampersands, brackets — silently navigates to wrong page) — **don\'t write title-based links**; do an extra search to get the UUID instead. Repo-path forms (`[Text](docs/path.md)`) exist for repo-ingested files; don\'t construct manually. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n9. **Concurrency: content updates require `expected_content_hash`.** Pass the `content_hash` you read (shown by `cerefox_get_document`, `cerefox_search`, and `cerefox_metadata_search`) when updating a document. If it\'s stale you get a **conflict** — re-read the document, merge your changes into the latest content, retry with the new hash. **Never resolve a conflict by overwriting blindly** — the current content includes another writer\'s work. `last_write_wins: true` skips the check; use it ONLY when an external source of truth makes conflicts meaningless (file re-sync), never to silence a conflict.\n10. **Search: prefer a few distinctive terms; heed `below confidence`.** When nothing clears the relevance threshold, `cerefox_search` returns the closest candidates prefixed with a `below confidence` warning instead of an empty set — that flag means **weak signal, not absent knowledge**: check the candidates\' scores and titles before concluding the KB lacks the content. A truly empty response means nothing even weakly related exists.\n11. **Project memberships — non-destructive by default**: on `cerefox_ingest` updates, **`project_name` (singular) is a non-destructive add** (ensures membership, preserves others). Use **`project_names` (list)** when you want to set the doc\'s full project set in one call (destructive replace). For metadata-only project changes without writing content, use **`cerefox_set_document_projects(document_id, project_names)`** — that tool is the destructive-replace contract made explicit. Never call `cerefox_set_document_projects` with a single name when you mean "add" — that would REMOVE the doc from all other projects. When in doubt, use `cerefox_ingest` with singular `project_name`.\n\n## Update Workflow (ID-based -- preferred)\n\n```\nsearch("topic") -> find doc [id: abc123] -> get_document(abc123) -> note its content_hash -> modify ->\ningest(title="Same Title", content="...", document_id="abc123",\n expected_content_hash="<the hash you read>", author="my-agent")\n```\n\nOn a **conflict** error: get_document again (fresh content + fresh hash) -> merge your changes -> retry with the new hash.\n\n## Update Workflow (title-based -- fallback)\n\n```\nsearch("topic") -> find doc (note its hash) -> modify ->\ningest(title="Same Title", content="...", update_if_exists=true,\n expected_content_hash="<the hash you read>", author="my-agent")\n```\n\n## Catch-Up Workflow\n\n```\nmetadata_search(metadata_filter={"type": "decision-log"}, updated_since="2026-03-28T00:00:00Z")\n```\n\n## CLI fallback (when MCP is unavailable)\n\nIf `cerefox_search` is not in your tool list, your user has likely installed the Cerefox CLI. The canonical invocation is plain **`cerefox <subcommand>`** (the TypeScript CLI, installed via `npm install -g @cerefox/memory`). It uses a resource-verb shape (`cerefox document get`, `cerefox project list`, …).\n\nSame operations, same conventions. Full reference: [`docs/guides/cli.md`](docs/guides/cli.md). CLI flag names match MCP parameter names exactly (e.g. `metadata_filter` ↔ `--metadata-filter`); common flags also have single-letter short forms (`-f`, `-p`, `-c`, `-m`, `-u`, `-a`, `-r`). Use the canonical long name (what `--help` shows) or its short form — there are no long-form aliases like `--filter` or `--count`.\n\n| MCP tool | CLI |\n|---|---|\n| `cerefox_search` | `cerefox search "<q>" --requestor "<your-name>"` |\n| `cerefox_ingest` (paste) | `printf \'...\' \\| cerefox document ingest --paste --title "<t>" --author "<your-name>" --author-type agent` |\n| `cerefox_ingest` (update by ID) | `printf \'...\' \\| cerefox document ingest --paste --title "<t>" --document-id "<uuid>" --expected-content-hash "<hash>" --author "<your-name>" --author-type agent` |\n| `cerefox_get_document` | `cerefox document get <id> --version-id <vid> --requestor "<your-name>"` |\n| `cerefox_list_versions` | `cerefox document version list <id> --requestor "<your-name>"` |\n| `cerefox_list_projects` | `cerefox project list --requestor "<your-name>"` |\n| `cerefox_list_metadata_keys` | `cerefox metadata keys` |\n| `cerefox_metadata_search` | `cerefox metadata search --metadata-filter \'<json>\' --requestor "<your-name>"` (list a project: `cerefox document list --project <name>`) |\n| `cerefox_set_document_projects` | `cerefox document set-projects <id> <name...> --author "<your-name>" --author-type agent` (or `--clear` to remove all) |\n| `cerefox_get_audit_log` | `cerefox audit list --requestor "<your-name>"` (add `--json` for scripted access) |\n| `cerefox_get_help` | `cerefox guides show agent-quick-reference` (or `cerefox guides list` for the full bundled-docs index) |\n\n**Set identity on every call**, exactly as you would on MCP:\n- Writes (`document ingest`, `document ingest-dir`): `--author "<your-name>" --author-type agent`\n- Reads: `--requestor "<your-name>"`\n\nOr have your user set `CEREFOX_AUTHOR_NAME` / `CEREFOX_AUTHOR_TYPE` / `CEREFOX_REQUESTOR_NAME` in their `.env` to apply defaults once.\n', HELP_SECTIONS, HELP_SECTION_HEADINGS;
|
|
54970
54977
|
var init_get_help_content = __esm(() => {
|
|
54971
54978
|
HELP_SECTIONS = {
|
|
54972
54979
|
Tools: "## Tools\n\n| Tool | Purpose | Key params |\n|------|---------|------------|\n| `cerefox_search` | Find documents (hybrid FTS + semantic) | `query` (required), `project_name`, `metadata_filter`, `requestor` |\n| `cerefox_ingest` | Save or update a document | `title`, `content` (required), `document_id` (update by ID), `expected_content_hash` (**required on content updates** — see rule 9), `last_write_wins`, `update_if_exists`, `project_name` (single, non-destructive add on update), `project_names` (list, destructive replace on update), `metadata` (omit on update to keep existing tags; `{}` clears), `author` |\n| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token) | `document_id` (required) |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required) |\n| `cerefox_metadata_search` | Find or list docs by metadata, project, or time (no text query) | `metadata_filter`, `project_name` (list a project's docs), `updated_since`, `include_content` — **at least one** of metadata_filter/project_name/updated_since/created_since |\n| `cerefox_list_metadata_keys` | Discover available metadata keys | (none required) |\n| `cerefox_list_projects` | List all projects | (none required) |\n| `cerefox_set_document_projects` | Set doc's project memberships to exactly the given list (destructive replace; metadata-only, no content change) | `document_id`, `project_names` (required) |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `author`, `operation`, `since` |\n| `cerefox_get_help` | Retrieve Cerefox conventions (this reference) over MCP. **Call this whenever uncertain.** | `topic` (optional, case-insensitive H2 substring match) |",
|
|
54973
|
-
"Essential Rules": '## Essential Rules\n\n1. **Search before ingesting** -- check if the document exists first.\n2. **Prefer ID-based updates** -- pass `document_id` from search results for deterministic updates. Falls back to title-matching with `update_if_exists: true`.\n3. **Set `author`/`requestor`** to your name on every call (e.g., "Claude Code", "archiver"). On MCP, pass as parameters. On CLI, pass `--author`/`--author-type`/`--requestor` flags, or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE`/`CEREFOX_REQUESTOR_NAME` env vars set in the user\'s `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **Add metadata** -- at minimum `type` ("decision-log", "research", "design-doc") and `status` ("active", "draft").\n6. **Write structured Markdown** with H1/H2/H3 headings for good chunking and search.\n7. **Deletes are soft (recoverable); purge is web-UI-only.** If you decide to delete, surface it to the user (`I soft-deleted X — recoverable from the Cerefox web UI trash`). You cannot un-do your own delete from agent code by design.\n8. **Cross-doc links inside content**: **always use `[Text](document-uuid)`.** UUIDs are the only fully reliable link form — stable across title changes, never ambiguous, no encoding gotchas. Every `cerefox_search` result shows `[id: <uuid>]` after the title; grab it and use it. Title-based linking (`[Text](<Title With Spaces>)`) is fragile (breaks on colons, parens, ampersands, brackets — silently navigates to wrong page) — **don\'t write title-based links**; do an extra search to get the UUID instead. Repo-path forms (`[Text](docs/path.md)`) exist for repo-ingested files; don\'t construct manually. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n9. **Concurrency: content updates require `expected_content_hash`.** Pass the `content_hash` you read (shown by `cerefox_get_document`, `cerefox_search`, and `cerefox_metadata_search`) when updating a document. If it\'s stale you get a **conflict** — re-read the document, merge your changes into the latest content, retry with the new hash. **Never resolve a conflict by overwriting blindly** — the current content includes another writer\'s work. `last_write_wins: true` skips the check; use it ONLY when an external source of truth makes conflicts meaningless (file re-sync), never to silence a conflict.\n10. **Project memberships — non-destructive by default**: on `cerefox_ingest` updates, **`project_name` (singular) is a non-destructive add** (ensures membership, preserves others). Use **`project_names` (list)** when you want to set the doc\'s full project set in one call (destructive replace). For metadata-only project changes without writing content, use **`cerefox_set_document_projects(document_id, project_names)`** — that tool is the destructive-replace contract made explicit. Never call `cerefox_set_document_projects` with a single name when you mean "add" — that would REMOVE the doc from all other projects. When in doubt, use `cerefox_ingest` with singular `project_name`.',
|
|
54980
|
+
"Essential Rules": '## Essential Rules\n\n1. **Search before ingesting** -- check if the document exists first.\n2. **Prefer ID-based updates** -- pass `document_id` from search results for deterministic updates. Falls back to title-matching with `update_if_exists: true`.\n3. **Set `author`/`requestor`** to your name on every call (e.g., "Claude Code", "archiver"). On MCP, pass as parameters. On CLI, pass `--author`/`--author-type`/`--requestor` flags, or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE`/`CEREFOX_REQUESTOR_NAME` env vars set in the user\'s `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **Add metadata** -- at minimum `type` ("decision-log", "research", "design-doc") and `status` ("active", "draft").\n6. **Write structured Markdown** with H1/H2/H3 headings for good chunking and search.\n7. **Deletes are soft (recoverable); purge is web-UI-only.** If you decide to delete, surface it to the user (`I soft-deleted X — recoverable from the Cerefox web UI trash`). You cannot un-do your own delete from agent code by design.\n8. **Cross-doc links inside content**: **always use `[Text](document-uuid)`.** UUIDs are the only fully reliable link form — stable across title changes, never ambiguous, no encoding gotchas. Every `cerefox_search` result shows `[id: <uuid>]` after the title; grab it and use it. Title-based linking (`[Text](<Title With Spaces>)`) is fragile (breaks on colons, parens, ampersands, brackets — silently navigates to wrong page) — **don\'t write title-based links**; do an extra search to get the UUID instead. Repo-path forms (`[Text](docs/path.md)`) exist for repo-ingested files; don\'t construct manually. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n9. **Concurrency: content updates require `expected_content_hash`.** Pass the `content_hash` you read (shown by `cerefox_get_document`, `cerefox_search`, and `cerefox_metadata_search`) when updating a document. If it\'s stale you get a **conflict** — re-read the document, merge your changes into the latest content, retry with the new hash. **Never resolve a conflict by overwriting blindly** — the current content includes another writer\'s work. `last_write_wins: true` skips the check; use it ONLY when an external source of truth makes conflicts meaningless (file re-sync), never to silence a conflict.\n10. **Search: prefer a few distinctive terms; heed `below confidence`.** When nothing clears the relevance threshold, `cerefox_search` returns the closest candidates prefixed with a `below confidence` warning instead of an empty set — that flag means **weak signal, not absent knowledge**: check the candidates\' scores and titles before concluding the KB lacks the content. A truly empty response means nothing even weakly related exists.\n11. **Project memberships — non-destructive by default**: on `cerefox_ingest` updates, **`project_name` (singular) is a non-destructive add** (ensures membership, preserves others). Use **`project_names` (list)** when you want to set the doc\'s full project set in one call (destructive replace). For metadata-only project changes without writing content, use **`cerefox_set_document_projects(document_id, project_names)`** — that tool is the destructive-replace contract made explicit. Never call `cerefox_set_document_projects` with a single name when you mean "add" — that would REMOVE the doc from all other projects. When in doubt, use `cerefox_ingest` with singular `project_name`.',
|
|
54974
54981
|
"Update Workflow (ID-based -- preferred)": `## Update Workflow (ID-based -- preferred)
|
|
54975
54982
|
|
|
54976
54983
|
\`\`\`
|
|
@@ -55572,6 +55579,8 @@ async function handler9(supabase, args, ctx) {
|
|
|
55572
55579
|
const mode = args.mode ?? "docs";
|
|
55573
55580
|
const alpha = args.alpha ?? 0.7;
|
|
55574
55581
|
const min_score = args.min_score ?? getMinSearchScore();
|
|
55582
|
+
const min_term_coverage = args.min_term_coverage ?? getMinTermCoverage();
|
|
55583
|
+
const coverageParam = min_term_coverage !== undefined ? { p_min_term_coverage: min_term_coverage } : {};
|
|
55575
55584
|
const metadata_filter = args.metadata_filter ?? null;
|
|
55576
55585
|
const requested_max_bytes = args.max_bytes;
|
|
55577
55586
|
const ceiling = getMaxResponseBytes();
|
|
@@ -55603,7 +55612,8 @@ async function handler9(supabase, args, ctx) {
|
|
|
55603
55612
|
p_query_text: query,
|
|
55604
55613
|
p_match_count: match_count,
|
|
55605
55614
|
p_project_id: projectId,
|
|
55606
|
-
...metaFilterParam
|
|
55615
|
+
...metaFilterParam,
|
|
55616
|
+
...coverageParam
|
|
55607
55617
|
};
|
|
55608
55618
|
} else if (mode === "hybrid") {
|
|
55609
55619
|
rpcName = "cerefox_hybrid_search";
|
|
@@ -55615,7 +55625,8 @@ async function handler9(supabase, args, ctx) {
|
|
|
55615
55625
|
p_use_upgrade: false,
|
|
55616
55626
|
p_project_id: projectId,
|
|
55617
55627
|
p_min_score: min_score,
|
|
55618
|
-
...metaFilterParam
|
|
55628
|
+
...metaFilterParam,
|
|
55629
|
+
...coverageParam
|
|
55619
55630
|
};
|
|
55620
55631
|
} else {
|
|
55621
55632
|
rpcName = "cerefox_search_docs";
|
|
@@ -55626,7 +55637,8 @@ async function handler9(supabase, args, ctx) {
|
|
|
55626
55637
|
p_alpha: alpha,
|
|
55627
55638
|
p_project_id: projectId,
|
|
55628
55639
|
p_min_score: min_score,
|
|
55629
|
-
...metaFilterParam
|
|
55640
|
+
...metaFilterParam,
|
|
55641
|
+
...coverageParam
|
|
55630
55642
|
};
|
|
55631
55643
|
}
|
|
55632
55644
|
const { data, error: error2 } = await supabase.rpc(rpcName, rpcParams);
|
|
@@ -55644,10 +55656,12 @@ async function handler9(supabase, args, ctx) {
|
|
|
55644
55656
|
if (accepted.length === 0)
|
|
55645
55657
|
return "No results found.";
|
|
55646
55658
|
const rows = accepted;
|
|
55659
|
+
const belowConfidence = rows.length > 0 && rows.every((r) => r.below_confidence === true);
|
|
55647
55660
|
const parts = rows.map((row) => {
|
|
55648
55661
|
const title = row.doc_title ?? "Untitled";
|
|
55649
55662
|
const docId = row.document_id ? ` [id: ${row.document_id}]` : "";
|
|
55650
|
-
const
|
|
55663
|
+
const rawScore = row.best_score ?? row.score;
|
|
55664
|
+
const score = rawScore != null ? ` (score: ${rawScore.toFixed(3)})` : "";
|
|
55651
55665
|
const partial = row.is_partial ? ` -- partial (${row.chunk_count} of ${(row.total_chars ?? 0).toLocaleString()} chars)` : "";
|
|
55652
55666
|
const hash = row.content_hash ? `
|
|
55653
55667
|
hash: ${row.content_hash}` : "";
|
|
@@ -55660,6 +55674,11 @@ ${row.full_content ?? ""}`;
|
|
|
55660
55674
|
---
|
|
55661
55675
|
|
|
55662
55676
|
`);
|
|
55677
|
+
if (belowConfidence) {
|
|
55678
|
+
output = `⚠ No results cleared the confidence threshold. Showing the closest ${rows.length} ` + `candidate(s) with scores — judge relevance yourself; a low score means weak signal, ` + `not necessarily absent knowledge.
|
|
55679
|
+
|
|
55680
|
+
` + output;
|
|
55681
|
+
}
|
|
55663
55682
|
if (truncated) {
|
|
55664
55683
|
output += `
|
|
55665
55684
|
|
|
@@ -55692,6 +55711,23 @@ var init_search = __esm(() => {
|
|
|
55692
55711
|
description: 'Optional JSONB containment filter. Only documents whose metadata contains ALL specified key-value pairs are returned. Example: {"type": "decision", "status": "active"}. Call cerefox_list_metadata_keys first to discover available keys and values. Omit to search all documents.',
|
|
55693
55712
|
additionalProperties: { type: "string" }
|
|
55694
55713
|
},
|
|
55714
|
+
mode: {
|
|
55715
|
+
type: "string",
|
|
55716
|
+
enum: ["docs", "hybrid", "fts", "semantic"],
|
|
55717
|
+
description: "Search mode (default: docs — full reconstructed documents). hybrid: ranked chunks; fts: keyword-only (no embedding); semantic: vector-only."
|
|
55718
|
+
},
|
|
55719
|
+
alpha: {
|
|
55720
|
+
type: "number",
|
|
55721
|
+
description: "Hybrid fusion weight 0–1 (default 0.7): 1 = pure semantic, 0 = pure keyword."
|
|
55722
|
+
},
|
|
55723
|
+
min_score: {
|
|
55724
|
+
type: "number",
|
|
55725
|
+
description: "Minimum cosine similarity for vector-side results (default: server-configured, 0.5 OpenAI / 0.6 local embedder)."
|
|
55726
|
+
},
|
|
55727
|
+
min_term_coverage: {
|
|
55728
|
+
type: "number",
|
|
55729
|
+
description: "Keyword OR-fallback confidence bar 0–1 (default 0.5): fraction of the query's meaningful terms a result must match to count as a confident hit; weaker matches return flagged below-confidence. 0 = any matching term. Needs schema ≥ 0.9.1."
|
|
55730
|
+
},
|
|
55695
55731
|
max_bytes: {
|
|
55696
55732
|
type: "integer",
|
|
55697
55733
|
description: "Optional response size budget in bytes. Results are dropped whole until the budget is satisfied; a truncated flag is set when results are dropped. Defaults to the server maximum (200000). Pass a smaller value if your context window is limited. Values above the server maximum are silently capped."
|
|
@@ -69025,10 +69061,29 @@ init_cli_core();
|
|
|
69025
69061
|
|
|
69026
69062
|
// src/cli/commands/backup.ts
|
|
69027
69063
|
init_cli_core();
|
|
69028
|
-
init_client();
|
|
69029
69064
|
import { existsSync as existsSync2, mkdirSync, writeFileSync } from "node:fs";
|
|
69030
69065
|
import { homedir as homedir2 } from "node:os";
|
|
69031
69066
|
import { join as join2, resolve } from "node:path";
|
|
69067
|
+
|
|
69068
|
+
// ../../_shared/db-client/paginate.ts
|
|
69069
|
+
async function fetchAllPages(makeQuery, batchSize = 200) {
|
|
69070
|
+
const results = [];
|
|
69071
|
+
let offset = 0;
|
|
69072
|
+
for (;; ) {
|
|
69073
|
+
const { data, error } = await makeQuery(offset, offset + batchSize - 1);
|
|
69074
|
+
if (error)
|
|
69075
|
+
throw new Error(error.message ?? JSON.stringify(error));
|
|
69076
|
+
const page = data ?? [];
|
|
69077
|
+
results.push(...page);
|
|
69078
|
+
if (page.length < batchSize)
|
|
69079
|
+
break;
|
|
69080
|
+
offset += batchSize;
|
|
69081
|
+
}
|
|
69082
|
+
return results;
|
|
69083
|
+
}
|
|
69084
|
+
|
|
69085
|
+
// src/cli/commands/backup.ts
|
|
69086
|
+
init_client();
|
|
69032
69087
|
function expandHome(path) {
|
|
69033
69088
|
if (path === "~")
|
|
69034
69089
|
return homedir2();
|
|
@@ -69049,21 +69104,25 @@ async function action(options) {
|
|
|
69049
69104
|
const filename = `cerefox-${stamp}${options.label ? "-" + options.label : ""}.json`;
|
|
69050
69105
|
const dest = join2(outDir, filename);
|
|
69051
69106
|
const client = getClient();
|
|
69052
|
-
|
|
69053
|
-
|
|
69054
|
-
|
|
69055
|
-
|
|
69107
|
+
let docs;
|
|
69108
|
+
try {
|
|
69109
|
+
docs = await fetchAllPages((from, to) => client.raw.from("cerefox_documents").select("id, title, content_hash, source, metadata, total_chars, chunk_count, " + "review_status, created_at, updated_at, deleted_at").is("deleted_at", null).order("created_at", { ascending: true }).order("id", { ascending: true }).range(from, to));
|
|
69110
|
+
} catch (err) {
|
|
69111
|
+
throw systemError(`Document fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
69112
|
+
}
|
|
69056
69113
|
let chunkTotal = 0;
|
|
69057
69114
|
const enriched = [];
|
|
69058
69115
|
for (let i = 0;i < docs.length; i++) {
|
|
69059
69116
|
const doc = docs[i];
|
|
69060
69117
|
const docId = doc.id;
|
|
69061
|
-
|
|
69062
|
-
|
|
69063
|
-
|
|
69118
|
+
let chunks;
|
|
69119
|
+
try {
|
|
69120
|
+
chunks = await fetchAllPages((from, to) => client.raw.from("cerefox_chunks").select("*").eq("document_id", docId).is("version_id", null).order("chunk_index", { ascending: true }).range(from, to));
|
|
69121
|
+
} catch (err) {
|
|
69122
|
+
throw systemError(`Chunk fetch failed for ${docId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
69064
69123
|
}
|
|
69065
|
-
chunkTotal +=
|
|
69066
|
-
enriched.push({ ...doc, chunks
|
|
69124
|
+
chunkTotal += chunks.length;
|
|
69125
|
+
enriched.push({ ...doc, chunks });
|
|
69067
69126
|
if (process.stdout.isTTY) {
|
|
69068
69127
|
process.stderr.write(`\r Dumping documents: ${i + 1}/${docs.length} (${chunkTotal} chunks so far)…`);
|
|
69069
69128
|
}
|
|
@@ -74941,7 +75000,8 @@ import { homedir as homedir6 } from "node:os";
|
|
|
74941
75000
|
import { join as join9 } from "node:path";
|
|
74942
75001
|
|
|
74943
75002
|
// ../../_shared/ef-meta/index.ts
|
|
74944
|
-
var EF_VERSION = "1.0.
|
|
75003
|
+
var EF_VERSION = "1.0.4";
|
|
75004
|
+
var EF_LAST_CHANGED = "1.0.4";
|
|
74945
75005
|
|
|
74946
75006
|
// src/cli/util/checks.ts
|
|
74947
75007
|
init_config();
|
|
@@ -75458,6 +75518,13 @@ async function checkEdgeFunctionsCompat() {
|
|
|
75458
75518
|
hint: "Update the Edge Functions (see remediation below)."
|
|
75459
75519
|
};
|
|
75460
75520
|
case "above-min-but-old":
|
|
75521
|
+
if (compareSemver(deployed, EF_LAST_CHANGED) >= 0) {
|
|
75522
|
+
return {
|
|
75523
|
+
name: "edge functions",
|
|
75524
|
+
status: "ok",
|
|
75525
|
+
detail: `Deployed EF v${deployed} (≥ required v${compat.edgeFunctions.min}; ` + `matches the latest server-side changes — the newer bundled label ` + `v${EF_VERSION} is cosmetic).`
|
|
75526
|
+
};
|
|
75527
|
+
}
|
|
75461
75528
|
return {
|
|
75462
75529
|
name: "edge functions",
|
|
75463
75530
|
status: "skipped",
|
|
@@ -77224,18 +77291,22 @@ async function action27(options) {
|
|
|
77224
77291
|
}
|
|
77225
77292
|
const reindexAll = Boolean(options.all);
|
|
77226
77293
|
const dryRun = Boolean(options.dryRun);
|
|
77227
|
-
let query = supabase.from("cerefox_chunks").select("id, document_id, content, embedder_primary, cerefox_documents(title)").is("version_id", null);
|
|
77228
|
-
if (options.documentId) {
|
|
77229
|
-
query = query.eq("document_id", options.documentId);
|
|
77230
|
-
}
|
|
77231
77294
|
const targetModel = activeEmbedderName();
|
|
77232
|
-
|
|
77233
|
-
|
|
77295
|
+
let chunks;
|
|
77296
|
+
try {
|
|
77297
|
+
chunks = await fetchAllPages((from, to) => {
|
|
77298
|
+
let query = supabase.from("cerefox_chunks").select("id, document_id, content, embedder_primary, cerefox_documents(title)").is("version_id", null);
|
|
77299
|
+
if (options.documentId) {
|
|
77300
|
+
query = query.eq("document_id", options.documentId);
|
|
77301
|
+
}
|
|
77302
|
+
if (!reindexAll) {
|
|
77303
|
+
query = query.neq("embedder_primary", targetModel);
|
|
77304
|
+
}
|
|
77305
|
+
return query.order("id", { ascending: true }).range(from, to);
|
|
77306
|
+
}, 1000);
|
|
77307
|
+
} catch (err) {
|
|
77308
|
+
throw systemError(`Failed to list chunks: ${err instanceof Error ? err.message : String(err)}`);
|
|
77234
77309
|
}
|
|
77235
|
-
const { data, error: error3 } = await query;
|
|
77236
|
-
if (error3)
|
|
77237
|
-
throw systemError(`Failed to list chunks: ${error3.message}`);
|
|
77238
|
-
const chunks = data ?? [];
|
|
77239
77310
|
if (chunks.length === 0) {
|
|
77240
77311
|
println(c.dim("(nothing to reindex)"));
|
|
77241
77312
|
return;
|
|
@@ -77405,6 +77476,8 @@ async function action29(query, options) {
|
|
|
77405
77476
|
const matchCount = parsePositiveInt(options.matchCount, "--match-count", 5);
|
|
77406
77477
|
const alpha = parseFloat01(options.alpha, "--alpha", 0.7);
|
|
77407
77478
|
const minScore = parseFloat01(options.minScore, "--min-score", getMinSearchScore());
|
|
77479
|
+
const envCoverage = getMinTermCoverage();
|
|
77480
|
+
const coverageParam = options.minTermCoverage !== undefined ? { p_min_term_coverage: parseFloat01(options.minTermCoverage, "--min-term-coverage", envCoverage ?? 0.5) } : envCoverage !== undefined ? { p_min_term_coverage: envCoverage } : {};
|
|
77408
77481
|
const maxBytes = parseNonNegativeInt(options.maxBytes, "--max-bytes", getMaxResponseBytes());
|
|
77409
77482
|
const mode = options.mode ?? "docs";
|
|
77410
77483
|
if (!["docs", "hybrid", "fts"].includes(mode)) {
|
|
@@ -77435,7 +77508,8 @@ async function action29(query, options) {
|
|
|
77435
77508
|
p_query_text: query,
|
|
77436
77509
|
p_match_count: matchCount,
|
|
77437
77510
|
p_project_id: projectId,
|
|
77438
|
-
...metaFilterParam
|
|
77511
|
+
...metaFilterParam,
|
|
77512
|
+
...coverageParam
|
|
77439
77513
|
};
|
|
77440
77514
|
} else if (mode === "hybrid") {
|
|
77441
77515
|
rpcName = "cerefox_hybrid_search";
|
|
@@ -77447,7 +77521,8 @@ async function action29(query, options) {
|
|
|
77447
77521
|
p_use_upgrade: false,
|
|
77448
77522
|
p_project_id: projectId,
|
|
77449
77523
|
p_min_score: minScore,
|
|
77450
|
-
...metaFilterParam
|
|
77524
|
+
...metaFilterParam,
|
|
77525
|
+
...coverageParam
|
|
77451
77526
|
};
|
|
77452
77527
|
} else {
|
|
77453
77528
|
rpcName = "cerefox_search_docs";
|
|
@@ -77458,7 +77533,8 @@ async function action29(query, options) {
|
|
|
77458
77533
|
p_alpha: alpha,
|
|
77459
77534
|
p_project_id: projectId,
|
|
77460
77535
|
p_min_score: minScore,
|
|
77461
|
-
...metaFilterParam
|
|
77536
|
+
...metaFilterParam,
|
|
77537
|
+
...coverageParam
|
|
77462
77538
|
};
|
|
77463
77539
|
}
|
|
77464
77540
|
const results = await client.rpc(rpcName, rpcParams);
|
|
@@ -77509,6 +77585,11 @@ async function action29(query, options) {
|
|
|
77509
77585
|
println("No results found.");
|
|
77510
77586
|
return;
|
|
77511
77587
|
}
|
|
77588
|
+
const belowConfidence = accepted.length > 0 && accepted.every((r) => r.below_confidence === true);
|
|
77589
|
+
if (belowConfidence) {
|
|
77590
|
+
println(c.yellow(`⚠ No results cleared the confidence threshold — showing the closest ` + `${accepted.length} candidate(s). Judge relevance by the scores below.`));
|
|
77591
|
+
println("");
|
|
77592
|
+
}
|
|
77512
77593
|
for (const row of accepted) {
|
|
77513
77594
|
if (mode === "docs") {
|
|
77514
77595
|
const doc2 = row;
|
|
@@ -77552,7 +77633,7 @@ async function action29(query, options) {
|
|
|
77552
77633
|
}
|
|
77553
77634
|
}
|
|
77554
77635
|
function registerSearch(program2) {
|
|
77555
|
-
program2.command("search").description("Search the knowledge base (hybrid FTS + semantic).").argument("<query>", "Natural-language search query.").option("-c, --match-count <n>", "Maximum number of documents to return.", "5").option("-p, --project-name <name>", "Filter results to a specific project.").option("-f, --metadata-filter <json>", "JSON containment filter; only docs whose metadata contains ALL pairs are returned.").option("--mode <mode>", "Search mode: docs (default), hybrid, fts.", "docs").option("--alpha <float>", "Semantic weight 0..1 (default: 0.7).", "0.7").option("--min-score <float>", "Minimum cosine similarity threshold (default: CEREFOX_MIN_SEARCH_SCORE; else 0.5, or 0.6 with the local embedder).").option("--max-bytes <n>", "Response size budget in bytes (default: CEREFOX_MAX_RESPONSE_BYTES or 200000).").option("-r, --requestor <name>", "Agent / user name (recorded in usage log).").option("--json", "Emit machine-readable JSON instead of the default text.").option("--only-metadata", "List matching docs (id, score, chunks, chars, partial/full) WITHOUT their content — like the web UI's collapsed result list. Grab a [id:…] then `cerefox document get <id>`.").action(action29);
|
|
77636
|
+
program2.command("search").description("Search the knowledge base (hybrid FTS + semantic).").argument("<query>", "Natural-language search query.").option("-c, --match-count <n>", "Maximum number of documents to return.", "5").option("-p, --project-name <name>", "Filter results to a specific project.").option("-f, --metadata-filter <json>", "JSON containment filter; only docs whose metadata contains ALL pairs are returned.").option("--mode <mode>", "Search mode: docs (default), hybrid, fts.", "docs").option("--alpha <float>", "Semantic weight 0..1 (default: 0.7).", "0.7").option("--min-score <float>", "Minimum cosine similarity threshold (default: CEREFOX_MIN_SEARCH_SCORE; else 0.5, or 0.6 with the local embedder).").option("--min-term-coverage <float>", "OR-fallback keyword matches must cover at least this fraction of the query's meaningful terms to count as confident hits (default: CEREFOX_MIN_TERM_COVERAGE; else the server default 0.5; needs schema ≥ 0.9.1).").option("--max-bytes <n>", "Response size budget in bytes (default: CEREFOX_MAX_RESPONSE_BYTES or 200000).").option("-r, --requestor <name>", "Agent / user name (recorded in usage log).").option("--json", "Emit machine-readable JSON instead of the default text.").option("--only-metadata", "List matching docs (id, score, chunks, chars, partial/full) WITHOUT their content — like the web UI's collapsed result list. Grab a [id:…] then `cerefox document get <id>`.").action(action29);
|
|
77556
77637
|
}
|
|
77557
77638
|
|
|
77558
77639
|
// src/cli/commands/self-update.ts
|
|
@@ -81424,10 +81505,8 @@ async function getProjectDocCounts(ctx, projectIds) {
|
|
|
81424
81505
|
if (projectIds.length === 0)
|
|
81425
81506
|
return { active, deleted };
|
|
81426
81507
|
try {
|
|
81427
|
-
const
|
|
81428
|
-
|
|
81429
|
-
throw error3;
|
|
81430
|
-
for (const row of data ?? []) {
|
|
81508
|
+
const rows = await fetchAllPages((from, to) => ctx.supabase.from("cerefox_document_projects").select("project_id, cerefox_documents(deleted_at)").in("project_id", projectIds).order("document_id", { ascending: true }).order("project_id", { ascending: true }).range(from, to));
|
|
81509
|
+
for (const row of rows) {
|
|
81431
81510
|
const pid = row.project_id;
|
|
81432
81511
|
if (!(pid in active))
|
|
81433
81512
|
continue;
|
|
@@ -81471,16 +81550,13 @@ async function getRecentDocAuthors(ctx, docIds) {
|
|
|
81471
81550
|
return out;
|
|
81472
81551
|
}
|
|
81473
81552
|
async function countDocumentsForProject(ctx, projectId) {
|
|
81474
|
-
const {
|
|
81553
|
+
const { count, error: error3 } = await ctx.supabase.from("cerefox_document_projects").select("document_id, cerefox_documents!inner(deleted_at)", {
|
|
81554
|
+
count: "exact",
|
|
81555
|
+
head: true
|
|
81556
|
+
}).eq("project_id", projectId).is("cerefox_documents.deleted_at", null);
|
|
81475
81557
|
if (error3)
|
|
81476
81558
|
throw error3;
|
|
81477
|
-
|
|
81478
|
-
for (const row of data ?? []) {
|
|
81479
|
-
if (row.cerefox_documents && row.cerefox_documents.deleted_at === null) {
|
|
81480
|
-
n += 1;
|
|
81481
|
-
}
|
|
81482
|
-
}
|
|
81483
|
-
return n;
|
|
81559
|
+
return count ?? 0;
|
|
81484
81560
|
}
|
|
81485
81561
|
function dashboardDocFromRow(row, projectIds) {
|
|
81486
81562
|
return {
|