@cerefox/memory 1.17.2 → 1.17.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_GUIDE.md +5 -5
- package/AGENT_QUICK_REFERENCE.md +1 -1
- package/dist/bin/cerefox.js +21 -20
- package/dist/frontend/assets/{index-C5878rMW.js → index-D1kdLLr8.js} +27 -27
- package/dist/frontend/assets/{index-C5878rMW.js.map → index-D1kdLLr8.js.map} +1 -1
- package/dist/frontend/index.html +1 -1
- package/dist/server-assets/_shared/ef-meta/index.ts +5 -5
- package/dist/server-assets/_shared/embeddings/index.ts +1 -1
- package/dist/server-assets/_shared/embeddings/onnx-embedder.ts +2 -2
- package/dist/server-assets/_shared/ingest/pipeline-helpers.ts +1 -1
- package/dist/server-assets/_shared/mcp-tools/_utils.ts +2 -2
- package/dist/server-assets/_shared/mcp-tools/audit-log.ts +1 -1
- package/dist/server-assets/_shared/mcp-tools/get-help-content.ts +2 -2
- package/dist/server-assets/_shared/mcp-tools/identity.ts +1 -1
- package/dist/server-assets/_shared/mcp-tools/ingest.ts +1 -1
- package/dist/server-assets/_shared/mcp-tools/relations.ts +3 -3
- package/dist/server-assets/_shared/mcp-tools/search.ts +1 -1
- package/dist/server-assets/_shared/partial-edits/index.ts +1 -1
- package/dist/server-assets/supabase/functions/cerefox-ingest/index.ts +1 -1
- package/dist/server-assets/supabase/functions/cerefox-mcp/index.ts +1 -1
- package/dist/server-assets/supabase/functions/cerefox-search/index.ts +1 -1
- package/docs/api/openapi.json +10 -3
- package/docs/guides/api.md +4 -4
- package/docs/guides/cli.md +2 -2
- package/docs/guides/configuration.md +4 -4
- package/docs/guides/connect-agents.md +3 -3
- package/docs/guides/ops-scripts.md +3 -3
- package/docs/guides/response-limits.md +3 -3
- package/docs/guides/staging-env.md +2 -2
- package/docs/guides/upgrading.md +2 -2
- package/package.json +1 -1
package/dist/frontend/index.html
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700&display=swap"
|
|
16
16
|
/>
|
|
17
17
|
<title>Cerefox</title>
|
|
18
|
-
<script type="module" crossorigin src="/app/assets/index-
|
|
18
|
+
<script type="module" crossorigin src="/app/assets/index-D1kdLLr8.js"></script>
|
|
19
19
|
<link rel="stylesheet" crossorigin href="/app/assets/index-B0H_N4if.css">
|
|
20
20
|
</head>
|
|
21
21
|
<body>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* doesn't touch `supabase/functions/` leaves it alone).
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
export const EF_VERSION = "1.17.
|
|
21
|
+
export const EF_VERSION = "1.17.4";
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* The Cerefox RELEASE version — what `cerefox --version` reports and what npm
|
|
@@ -30,23 +30,23 @@ export const EF_VERSION = "1.17.2";
|
|
|
30
30
|
* would have told a beta tester the last stable number. Since the whole point
|
|
31
31
|
* of surfacing a version to agents (`cerefox_get_help(topic: "server")`) is
|
|
32
32
|
* letting them tell a stale client from a real capability gap, the number has
|
|
33
|
-
* to be the one the user would
|
|
33
|
+
* to be the one the user would recognize.
|
|
34
34
|
*
|
|
35
35
|
* Lives here rather than in `packages/memory/src/meta.ts` because `_shared/`
|
|
36
36
|
* is imported by the Deno Edge Functions, which cannot reach into the npm
|
|
37
37
|
* package.
|
|
38
38
|
*/
|
|
39
|
-
export const CEREFOX_VERSION = "1.17.
|
|
39
|
+
export const CEREFOX_VERSION = "1.17.4";
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* The most recent version whose EF-side SOURCE actually changed (#127).
|
|
43
43
|
* `EF_VERSION` bumps unconditionally at stable cuts (so stable deployments
|
|
44
44
|
* never display a pre-release label), which means a version delta no longer
|
|
45
|
-
* implies the deployed
|
|
45
|
+
* implies the deployed behavior differs. This constant is bumped by
|
|
46
46
|
* `cut_release.ts` ONLY when EF source changed since the last tag; doctor
|
|
47
47
|
* uses it to stay silent on label-only drift.
|
|
48
48
|
*/
|
|
49
|
-
export const EF_LAST_CHANGED = "1.17.
|
|
49
|
+
export const EF_LAST_CHANGED = "1.17.3";
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* The 8 peer EFs the cerefox-mcp aggregator probes (excludes cerefox-mcp
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* the module never reads env vars directly.
|
|
11
11
|
*
|
|
12
12
|
* Mirrors `supabase/functions/cerefox-mcp/embeddings.ts` exactly for v0.4.0
|
|
13
|
-
* (extraction commit; no
|
|
13
|
+
* (extraction commit; no behavior change). Future tweaks live here.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
export const OPENAI_EMBEDDING_URL = "https://api.openai.com/v1/embeddings";
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Ported from cfcf's proven `OnnxEmbedder` (packages/core/src/clio/embedders/),
|
|
5
5
|
* adapted per docs/research/local-embedder-design.md:
|
|
6
|
-
* - single model, no
|
|
6
|
+
* - single model, no catalog: `nomic-ai/nomic-embed-text-v1.5` q8 — 768-dim
|
|
7
7
|
* (matches Cerefox's `vector(768)` schema; no schema change), ~130 MB.
|
|
8
8
|
* - nomic task prefixes per role: a query embeds as `search_query: <text>`,
|
|
9
9
|
* a stored chunk as `search_document: <text>` (asymmetric model).
|
|
@@ -388,7 +388,7 @@ export async function warmup(): Promise<void> {
|
|
|
388
388
|
|
|
389
389
|
/**
|
|
390
390
|
* Embed texts with the local nomic model, applying the role prefix.
|
|
391
|
-
* Mean pooling + L2
|
|
391
|
+
* Mean pooling + L2 normalization (sentence-transformers convention; nomic
|
|
392
392
|
* expects both). Returns plain `number[][]` to match the OpenAI path.
|
|
393
393
|
*/
|
|
394
394
|
/**
|
|
@@ -56,7 +56,7 @@ export function contentHash(text: string): string {
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Derive a default `source_path` from a document title when none was
|
|
59
|
-
* provided (e.g. paste ingestion). Matches Python's
|
|
59
|
+
* provided (e.g. paste ingestion). Matches Python's behavior exactly:
|
|
60
60
|
*
|
|
61
61
|
* slug = re.sub(r"[^\w\s-]", "", title.lower())
|
|
62
62
|
* slug = re.sub(r"[\s_-]+", "-", slug).strip("-") or "document"
|
|
@@ -279,12 +279,12 @@ export function logUsage(supabase: MCPSupabaseClient, params: LogUsageParams): v
|
|
|
279
279
|
* surfaces and each hand-written copy was wrong in its own way (#267, #268):
|
|
280
280
|
*
|
|
281
281
|
* - **Non-numeric means unset, not unbounded.** `Math.min("lots", CEILING)` is
|
|
282
|
-
* `NaN`; `NaN` compares false against every `>` check and
|
|
282
|
+
* `NaN`; `NaN` compares false against every `>` check and serializes to JSON
|
|
283
283
|
* `null`, and `p_max_bytes NULL` means NO limit in Postgres. So the one
|
|
284
284
|
* parameter that exists to bound a reply, handed a word, removed the bound.
|
|
285
285
|
* - **`null` and `undefined` mean unset too.** `Number(null)` is `0`, which is
|
|
286
286
|
* finite, so a clamp to `>= 1` turned an explicitly-null budget into a
|
|
287
|
-
* ONE-BYTE budget — a client that
|
|
287
|
+
* ONE-BYTE budget — a client that serializes optional fields as `null` asked
|
|
288
288
|
* for content and got none.
|
|
289
289
|
* - **A real number of zero or less means "almost nothing", and is honoured.**
|
|
290
290
|
* Falling back to the ceiling there would hand a caller whose allowance had
|
|
@@ -29,7 +29,7 @@ async function handler(
|
|
|
29
29
|
): Promise<string> {
|
|
30
30
|
// v1.13.1: the filter is `by_author`; `author` is the caller's identity here
|
|
31
31
|
// as on every other tool. The pre-1.13.1 shape (requestor + author-as-filter)
|
|
32
|
-
// is
|
|
32
|
+
// is recognized and keeps its meaning — see auditLogIdentity().
|
|
33
33
|
const { identity, byAuthor } = auditLogIdentity(args);
|
|
34
34
|
const params: Record<string, unknown> = {};
|
|
35
35
|
if (args.document_id) params.p_document_id = args.document_id;
|
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
* docs/specs/polish-and-distribution-design.md §10d.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
export const HELP_FULL = "# Cerefox Knowledge Base -- Agent Quick Reference\n\nCerefox is a persistent, shared knowledge base. You have **15 core MCP tools** (14 with CLI equivalents — `cerefox_get_help` is MCP-only), plus 4 dormant relation tools that appear only when `relations_enabled` is on. 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`, `author` |\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 10), `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_insert` | **Add** to a document without resending it. Cannot destroy content. | `document_id`, `text`, `position` (`end_of_document`/`end_of_section`/`after_heading`/`before_heading`), `expected_content_hash` (required), `anchor_heading` (unless `end_of_document`), `section_part`, `author` |\n| `cerefox_edit` | **Change** parts of a document: 1..n operations applied atomically | `document_id`, `operations` (`insert`/`replace_section`/`delete_section`/`rename_section`), `expected_content_hash` (required), `author` |\n| `cerefox_delete_document` | **Soft**-delete a document (to trash; excluded from search; permanent purge is human-only) | `document_id`, `expected_content_hash` (**required** — a delete must follow a read), `reason` (recorded in the audit log — give one), `author` |\n| `cerefox_restore_document` | Restore a soft-deleted document from the trash (audited inverse of delete; no-op if not deleted) | `document_id` (required), `reason` (recorded in the audit log), `author` |\n| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token), or with `outline: true` just its heading paths, sizes and hash, or with `section: \"## Heading\"` one section's text | `document_id` (required), `outline`, `section`, `section_part`, `author` |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required), `author` |\n| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type`, `author` |\n| `cerefox_get_relations` ⚑ | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` ⚑ | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\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_metadata` | Change tags WITHOUT resending content. **Merges** by default; a `null` value removes a key | `document_id`, `metadata` (required), `replace` (rare: set exactly this object), `author` |\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), `author` |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `by_author` (filter), `operation`, `since`, `author` |\n| `cerefox_get_help` | Retrieve Cerefox conventions (this reference) over MCP. **Call this whenever uncertain.** | `topic` (optional, case-insensitive H2 substring match) |\n\n⚑ **Opt-in — usually absent.** The four relation tools are hidden unless the\noperator enables them (`relations_enabled`). **Trust your own tool list**: if\nthey are not in it, the feature is switched off for this deployment. That is\nnormal, not an error, and not something to work around.\n\n## Editing part of a document (prefer this over re-sending)\n\n**Re-sending a whole document to change part of it is the main way agents lose\ndata.** You have to reproduce the untouched remainder verbatim, and any drift\nsilently rewrites content nobody asked you to touch — which the caller cannot\ndiff. Use the partial-edit tools instead:\n\n1. **Learn the anchors** — `cerefox_get_document(document_id, outline: true)`.\n Returns heading paths, per-section sizes and the `content_hash`, without the\n body. The paths it returns are exactly what `anchor_heading` accepts.\n2. **Add** → `cerefox_insert`. `end_of_document` is a plain append;\n `end_of_section` adds inside a named section. It is structurally incapable of\n removing anything, so \"I meant to append\" cannot become \"I replaced the file\".\n3. **Look before you overwrite** — `cerefox_get_document(document_id,\n section: \"## Heading\")` returns exactly the text a `replace_section` on that\n anchor would destroy. The outline gives you a section's *size*, never its\n *text*, so on a document you did not write yourself this is the difference\n between a replace and a blind overwrite.\n4. **Change or remove** → `cerefox_edit`. Put changes that belong together in\n ONE call: they apply atomically, so a table row and the total it feeds cannot\n end up disagreeing. To change a single line, `replace_section` on its\n smallest enclosing heading — that is the intended granularity, not a\n workaround. To fix a stale heading (`## OPEN TODOs (as of ...)`), use\n `rename_section`: it changes the heading text and leaves the body and\n position alone.\n5. All of them require `expected_content_hash` and **have no last-write-wins**. A\n conflict means someone else changed the document; re-read and decide, do not\n force it.\n\n**A section runs to the next same-or-higher heading, or to the end of the\ndocument.** So `end_of_document` inserts land inside the *last* section, and\nreplacing or deleting that section removes them too. A large shrink in the\nresponse is your warning; `cerefox_list_versions` has the previous content.\n\n**When an anchor is ambiguous the tool refuses and hands you the options** — a\nrepeated heading returns the qualifying paths, and a section with both its own\ncontent and sub-sections returns both `section_part` choices. That is a\nrecoverable answer, not a failure: retry with what it gave you.\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`** to your name on every call, reads and writes alike (e.g., \"Claude Code\", \"archiver\"). Same parameter on every tool. (`requestor` is still accepted everywhere as the pre-1.13.1 alias.) On the CLI it is `--author` on every command too (plus `--author-type` on writes); or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE` env vars set in the user's `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **`max_bytes` has to fit a whole document.** Search returns COMPLETE documents, so a budget smaller than the top hit fits nothing. You get a header-only list saying so (never \"No results found.\" — that means the store really has nothing). Raise `max_bytes`, or read one document with `cerefox_get_document` using `outline: true` or `section`.\n6. **Add metadata** -- at minimum `type` (\"decision-log\", \"research\", \"design-doc\") and `status` (\"active\", \"draft\").\n7. **Write for retrieval: the title does real work.** The document title is indexed at the **highest weight** *and* prepended to every chunk before it is embedded, so it steers both halves of hybrid search (keyword and semantic) for every chunk in the document. Name the document with the distinctive terms someone would actually search for -- \"Cerefox Response Size Limits\", not \"Notes\" or \"Update 3\". Renaming re-indexes and re-embeds, so correcting a vague title is cheap and takes effect immediately. Write the characters themselves: `&` and `<>`, never `&` or `<>` (entities are stored literally and the write reply will say so). Inside the content, write structured Markdown with H1/H2/H3 headings: the chunker follows them, and a chunk's own heading carries the same weight as the title.\n8. **Deletes are soft (recoverable); permanent purge is web-UI-only.** `cerefox_delete_document` requires the document's `content_hash` as you read it (read before you delete) and takes a `reason` — give one; it is what the human reviewing the trash sees. `cerefox_restore_document` undoes a mistaken delete (also audited, also takes a `reason`). Always surface deletes AND restores to the user. Once a human purges from the web UI, the document is gone for good; so is one auto-purged after `trash_retention_days`, if the operator has turned trash auto-purge on.\n9. **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. **The server validates `](uuid)` links on every write** (v1.7.0): a link to a nonexistent id rejects the write, naming the offender — that means you mangled the UUID; re-read the source and correct it, do not retry unchanged. Example ids go in backticks (code is not validated). `[[Wikilinks]]` may dangle. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n10. **Concurrency: content updates require `expected_content_hash`.** Pass the `content_hash` you last saw — every read shows one (`cerefox_get_document` incl. outline mode, `cerefox_search`, `cerefox_metadata_search`) and **every write returns the new one, including create** (v1.3.0, #189), so after writing you already hold the token for your next edit; no re-read needed. 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.\n11. **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.\n12. **Relations express how documents relate; lifecycle tells you if knowledge is still good.** Use `cerefox_set_relation` when one document supersedes, contradicts, references, or continues another. `supersedes` marks the target **superseded**; `contradicts` marks **both** stale; `related_to`/`duplicates`/`contradicts` are symmetric (both directions written). Any other type string is accepted without special behaviour. When a search result or `cerefox_get_relations` shows a neighbour marked `[superseded]` or `[stale]`, say so rather than presenting it as current.\n13. **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`). 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>\" --author \"<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> --author \"<your-name>\"` |\n| `cerefox_list_versions` | `cerefox document version list <id> --author \"<your-name>\"` |\n| `cerefox_list_projects` | `cerefox project list --author \"<your-name>\"` |\n| `cerefox_list_metadata_keys` | `cerefox metadata keys` |\n| `cerefox_insert` | `cerefox document insert <id> -t \"<text>\" -p <position> -a \"<anchor-heading>\" -e \"<hash>\" --author \"<your-name>\" --author-type agent` |\n| `cerefox_edit` | `cerefox document edit-parts <id> --operations '<json>' -e \"<hash>\" --author \"<your-name>\" --author-type agent` |\n| `cerefox_delete_document` | `cerefox document delete <id> --reason \"<why>\" --author \"<your-name>\" --author-type agent --yes` (confirms interactively instead of requiring the hash) |\n| `cerefox_restore_document` | `cerefox document restore <id> --reason \"<why>\" --author \"<your-name>\" --author-type agent` |\n| `cerefox_metadata_search` | `cerefox metadata search --metadata-filter '<json>' --author \"<your-name>\"` (list a project: `cerefox document list --project <name>`) |\n| `cerefox_set_document_metadata` | `cerefox document set-metadata <id> --set key=value` (also `--remove key`, `--json '{...}'`, `--replace`) |\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 --author \"<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: `--author \"<your-name>\"` (the same flag; `--requestor` still works as a hidden alias)\n\nOr have your user set `CEREFOX_AUTHOR_NAME` / `CEREFOX_AUTHOR_TYPE` / `CEREFOX_REQUESTOR_NAME` in their `.env` to apply defaults once.\n\n## Timestamps are UTC\n\nEvery timestamp Cerefox returns — `created_at` on audit entries, version\nhistory, document metadata — is **UTC**, and now carries its `Z` marker so it\ncannot be mistaken for local time.\n\n**When you write a date into a document's CONTENT, use your own clock, not a\nCerefox timestamp.** These are different things: a timestamp records when the\nserver stored something; a date in a log entry or a heading is authored content\nand belongs to your timezone. An agent working a Pacific afternoon read\n`2026-08-11` from version history, wrote \"8/11\" into its entries, and put a\nday's work in the future — the timestamp was correct, and copying it into\ncontent was not.\n\nCerefox deliberately does not convert to local time on the API or MCP paths.\n\"Local\" has no server-side meaning: the remote MCP server runs in a cloud\nfunction whose local time *is* UTC, while a local MCP server runs in yours, so\nthe same document would report two different times depending on transport. The\nweb UI converts because a browser knows the viewer's timezone; nothing\nserver-side does.\n\n## Mistakes that have actually happened\n\nEach of these comes from a real agent session, and each is easy to make.\n\n- **`cerefox_ingest` always replaces the ENTIRE document.** Never a section.\n Before sending, check that the tool name matches the intent: if the intent is\n \"change one section\", the call is `cerefox_edit` with `replace_section`. A\n section-sized edit sent as a full ingest truncated a 13,000-character index to\n a single word. It was recovered from version history within the minute, but\n only because it was noticed immediately.\n\n- **Do not include the anchor's own heading in your text.** `replace_section`\n keeps the heading and `insert` places your text inside the section, so\n including it produces two. This is now refused rather than silently applied,\n but the shape is worth knowing: it happened twice in one session, the second\n time while trying to repair the first. A *deeper* sub-heading inside your text\n is fine.\n\n- **Content between sections belongs to the section ABOVE it.** A section runs\n to the next heading of the same or higher level, so a `---` rule, a note, or\n any trailing text sitting just above the next heading is part of the section\n before it — even when it visually reads as belonging below. Replacing that\n section takes it too. An agent hit exactly this: a `---` that separated two\n major sections disappeared when the section above it was replaced. The write\n was correct by the addressing rules; the surprise is that \"the end of this\n section\" is further down the page than it looks. Note the loss warning will\n not catch it if your replacement text is longer than what it replaced, since\n there is then no net loss to report.\n\n- **To change only tags, use `cerefox_set_document_metadata`, never `cerefox_ingest`.**\n Ingest replaces the whole document, so re-sending it to set one tag carries the\n full transcription risk for no reason. The metadata tool merges: the keys you\n pass are set, everything else is left alone, so you do not need to read the\n document first and cannot drop a tag another agent set. Pass `null` as a value\n to remove a key.\n\n- **Never partial-edit to fix a partial edit.** If a write leaves unexpected\n structure, stop. Use `cerefox_list_versions`, retrieve the last good version,\n and re-ingest cleanly. Repairing edits with more edits compounds the damage.\n\n- **A rejected batch is safe.** Operations in one `cerefox_edit` are\n all-or-nothing: if any is invalid, nothing is written. A refusal costs you a\n retry, not data — so prefer one call for changes that belong together, and do\n not split a batch to \"make it more likely to succeed\".\n\n- **Read before replacing.** `cerefox_get_document(section: \"## Heading\")`\n returns exactly what a `replace_section` on that anchor would overwrite. Use it\n for any section you did not write in this session. The outline gives a\n section's *size*, never its *text*.\n\n- **Verify after writing** — read the result back before reporting success, and\n report what the read actually shows.\n\n- **Partial edits cannot change a document's stored TITLE.** `rename_section`\n changes a heading inside the content; the title is a separate field and still\n needs `cerefox_ingest`.\n\n- **If a capability seems missing from one server, suspect your client first.**\n Local and remote run the same code. **Every `cerefox_get_help()` response\n begins with the server's version and the operations it registers** — you do\n not need a special topic, and the *absence* of that block is itself an answer:\n a server that does not print it predates v1.5.0. If that\n disagrees with your tool list, the client is holding a list it fetched before\n an upgrade — clients cache it at connect time. Ask the user to restart the\n client. Do not record a capability difference between servers as a fact; every\n such report so far has been a stale client.\n- Long inline bodies can arrive with literal `\\n`/`\\\"` (the author over-escaped; Cerefox stores bytes faithfully). For long or quote-dense content, ingest from a file or build incrementally with `cerefox_insert`; read back multi-line writes.\n";
|
|
14
|
+
export const HELP_FULL = "# Cerefox Knowledge Base -- Agent Quick Reference\n\nCerefox is a persistent, shared knowledge base. You have **15 core MCP tools** (14 with CLI equivalents — `cerefox_get_help` is MCP-only), plus 4 dormant relation tools that appear only when `relations_enabled` is on. 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`, `author` |\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 10), `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_insert` | **Add** to a document without resending it. Cannot destroy content. | `document_id`, `text`, `position` (`end_of_document`/`end_of_section`/`after_heading`/`before_heading`), `expected_content_hash` (required), `anchor_heading` (unless `end_of_document`), `section_part`, `author` |\n| `cerefox_edit` | **Change** parts of a document: 1..n operations applied atomically | `document_id`, `operations` (`insert`/`replace_section`/`delete_section`/`rename_section`), `expected_content_hash` (required), `author` |\n| `cerefox_delete_document` | **Soft**-delete a document (to trash; excluded from search; permanent purge is human-only) | `document_id`, `expected_content_hash` (**required** — a delete must follow a read), `reason` (recorded in the audit log — give one), `author` |\n| `cerefox_restore_document` | Restore a soft-deleted document from the trash (audited inverse of delete; no-op if not deleted) | `document_id` (required), `reason` (recorded in the audit log), `author` |\n| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token), or with `outline: true` just its heading paths, sizes and hash, or with `section: \"## Heading\"` one section's text | `document_id` (required), `outline`, `section`, `section_part`, `author` |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required), `author` |\n| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type`, `author` |\n| `cerefox_get_relations` ⚑ | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` ⚑ | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\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_metadata` | Change tags WITHOUT resending content. **Merges** by default; a `null` value removes a key | `document_id`, `metadata` (required), `replace` (rare: set exactly this object), `author` |\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), `author` |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `by_author` (filter), `operation`, `since`, `author` |\n| `cerefox_get_help` | Retrieve Cerefox conventions (this reference) over MCP. **Call this whenever uncertain.** | `topic` (optional, case-insensitive H2 substring match) |\n\n⚑ **Opt-in — usually absent.** The four relation tools are hidden unless the\noperator enables them (`relations_enabled`). **Trust your own tool list**: if\nthey are not in it, the feature is switched off for this deployment. That is\nnormal, not an error, and not something to work around.\n\n## Editing part of a document (prefer this over re-sending)\n\n**Re-sending a whole document to change part of it is the main way agents lose\ndata.** You have to reproduce the untouched remainder verbatim, and any drift\nsilently rewrites content nobody asked you to touch — which the caller cannot\ndiff. Use the partial-edit tools instead:\n\n1. **Learn the anchors** — `cerefox_get_document(document_id, outline: true)`.\n Returns heading paths, per-section sizes and the `content_hash`, without the\n body. The paths it returns are exactly what `anchor_heading` accepts.\n2. **Add** → `cerefox_insert`. `end_of_document` is a plain append;\n `end_of_section` adds inside a named section. It is structurally incapable of\n removing anything, so \"I meant to append\" cannot become \"I replaced the file\".\n3. **Look before you overwrite** — `cerefox_get_document(document_id,\n section: \"## Heading\")` returns exactly the text a `replace_section` on that\n anchor would destroy. The outline gives you a section's *size*, never its\n *text*, so on a document you did not write yourself this is the difference\n between a replace and a blind overwrite.\n4. **Change or remove** → `cerefox_edit`. Put changes that belong together in\n ONE call: they apply atomically, so a table row and the total it feeds cannot\n end up disagreeing. To change a single line, `replace_section` on its\n smallest enclosing heading — that is the intended granularity, not a\n workaround. To fix a stale heading (`## OPEN TODOs (as of ...)`), use\n `rename_section`: it changes the heading text and leaves the body and\n position alone.\n5. All of them require `expected_content_hash` and **have no last-write-wins**. A\n conflict means someone else changed the document; re-read and decide, do not\n force it.\n\n**A section runs to the next same-or-higher heading, or to the end of the\ndocument.** So `end_of_document` inserts land inside the *last* section, and\nreplacing or deleting that section removes them too. A large shrink in the\nresponse is your warning; `cerefox_list_versions` has the previous content.\n\n**When an anchor is ambiguous the tool refuses and hands you the options** — a\nrepeated heading returns the qualifying paths, and a section with both its own\ncontent and sub-sections returns both `section_part` choices. That is a\nrecoverable answer, not a failure: retry with what it gave you.\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`** to your name on every call, reads and writes alike (e.g., \"Claude Code\", \"archiver\"). Same parameter on every tool. (`requestor` is still accepted everywhere as the pre-1.13.1 alias.) On the CLI it is `--author` on every command too (plus `--author-type` on writes); or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE` env vars set in the user's `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **`max_bytes` has to fit a whole document.** Search returns COMPLETE documents, so a budget smaller than the top hit fits nothing. You get a header-only list saying so (never \"No results found.\" — that means the store really has nothing). Raise `max_bytes`, or read one document with `cerefox_get_document` using `outline: true` or `section`.\n6. **Add metadata** -- at minimum `type` (\"decision-log\", \"research\", \"design-doc\") and `status` (\"active\", \"draft\").\n7. **Write for retrieval: the title does real work.** The document title is indexed at the **highest weight** *and* prepended to every chunk before it is embedded, so it steers both halves of hybrid search (keyword and semantic) for every chunk in the document. Name the document with the distinctive terms someone would actually search for -- \"Cerefox Response Size Limits\", not \"Notes\" or \"Update 3\". Renaming re-indexes and re-embeds, so correcting a vague title is cheap and takes effect immediately. Write the characters themselves: `&` and `<>`, never `&` or `<>` (entities are stored literally and the write reply will say so). Inside the content, write structured Markdown with H1/H2/H3 headings: the chunker follows them, and a chunk's own heading carries the same weight as the title.\n8. **Deletes are soft (recoverable); permanent purge is web-UI-only.** `cerefox_delete_document` requires the document's `content_hash` as you read it (read before you delete) and takes a `reason` — give one; it is what the human reviewing the trash sees. `cerefox_restore_document` undoes a mistaken delete (also audited, also takes a `reason`). Always surface deletes AND restores to the user. Once a human purges from the web UI, the document is gone for good; so is one auto-purged after `trash_retention_days`, if the operator has turned trash auto-purge on.\n9. **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. **The server validates `](uuid)` links on every write** (v1.7.0): a link to a nonexistent id rejects the write, naming the offender — that means you mangled the UUID; re-read the source and correct it, do not retry unchanged. Example ids go in backticks (code is not validated). `[[Wikilinks]]` may dangle. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n10. **Concurrency: content updates require `expected_content_hash`.** Pass the `content_hash` you last saw — every read shows one (`cerefox_get_document` incl. outline mode, `cerefox_search`, `cerefox_metadata_search`) and **every write returns the new one, including create** (v1.3.0, #189), so after writing you already hold the token for your next edit; no re-read needed. 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.\n11. **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.\n12. **Relations express how documents relate; lifecycle tells you if knowledge is still good.** Use `cerefox_set_relation` when one document supersedes, contradicts, references, or continues another. `supersedes` marks the target **superseded**; `contradicts` marks **both** stale; `related_to`/`duplicates`/`contradicts` are symmetric (both directions written). Any other type string is accepted without special behavior. When a search result or `cerefox_get_relations` shows a neighbour marked `[superseded]` or `[stale]`, say so rather than presenting it as current.\n13. **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`). 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>\" --author \"<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> --author \"<your-name>\"` |\n| `cerefox_list_versions` | `cerefox document version list <id> --author \"<your-name>\"` |\n| `cerefox_list_projects` | `cerefox project list --author \"<your-name>\"` |\n| `cerefox_list_metadata_keys` | `cerefox metadata keys` |\n| `cerefox_insert` | `cerefox document insert <id> -t \"<text>\" -p <position> -a \"<anchor-heading>\" -e \"<hash>\" --author \"<your-name>\" --author-type agent` |\n| `cerefox_edit` | `cerefox document edit-parts <id> --operations '<json>' -e \"<hash>\" --author \"<your-name>\" --author-type agent` |\n| `cerefox_delete_document` | `cerefox document delete <id> --reason \"<why>\" --author \"<your-name>\" --author-type agent --yes` (confirms interactively instead of requiring the hash) |\n| `cerefox_restore_document` | `cerefox document restore <id> --reason \"<why>\" --author \"<your-name>\" --author-type agent` |\n| `cerefox_metadata_search` | `cerefox metadata search --metadata-filter '<json>' --author \"<your-name>\"` (list a project: `cerefox document list --project <name>`) |\n| `cerefox_set_document_metadata` | `cerefox document set-metadata <id> --set key=value` (also `--remove key`, `--json '{...}'`, `--replace`) |\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 --author \"<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: `--author \"<your-name>\"` (the same flag; `--requestor` still works as a hidden alias)\n\nOr have your user set `CEREFOX_AUTHOR_NAME` / `CEREFOX_AUTHOR_TYPE` / `CEREFOX_REQUESTOR_NAME` in their `.env` to apply defaults once.\n\n## Timestamps are UTC\n\nEvery timestamp Cerefox returns — `created_at` on audit entries, version\nhistory, document metadata — is **UTC**, and now carries its `Z` marker so it\ncannot be mistaken for local time.\n\n**When you write a date into a document's CONTENT, use your own clock, not a\nCerefox timestamp.** These are different things: a timestamp records when the\nserver stored something; a date in a log entry or a heading is authored content\nand belongs to your timezone. An agent working a Pacific afternoon read\n`2026-08-11` from version history, wrote \"8/11\" into its entries, and put a\nday's work in the future — the timestamp was correct, and copying it into\ncontent was not.\n\nCerefox deliberately does not convert to local time on the API or MCP paths.\n\"Local\" has no server-side meaning: the remote MCP server runs in a cloud\nfunction whose local time *is* UTC, while a local MCP server runs in yours, so\nthe same document would report two different times depending on transport. The\nweb UI converts because a browser knows the viewer's timezone; nothing\nserver-side does.\n\n## Mistakes that have actually happened\n\nEach of these comes from a real agent session, and each is easy to make.\n\n- **`cerefox_ingest` always replaces the ENTIRE document.** Never a section.\n Before sending, check that the tool name matches the intent: if the intent is\n \"change one section\", the call is `cerefox_edit` with `replace_section`. A\n section-sized edit sent as a full ingest truncated a 13,000-character index to\n a single word. It was recovered from version history within the minute, but\n only because it was noticed immediately.\n\n- **Do not include the anchor's own heading in your text.** `replace_section`\n keeps the heading and `insert` places your text inside the section, so\n including it produces two. This is now refused rather than silently applied,\n but the shape is worth knowing: it happened twice in one session, the second\n time while trying to repair the first. A *deeper* sub-heading inside your text\n is fine.\n\n- **Content between sections belongs to the section ABOVE it.** A section runs\n to the next heading of the same or higher level, so a `---` rule, a note, or\n any trailing text sitting just above the next heading is part of the section\n before it — even when it visually reads as belonging below. Replacing that\n section takes it too. An agent hit exactly this: a `---` that separated two\n major sections disappeared when the section above it was replaced. The write\n was correct by the addressing rules; the surprise is that \"the end of this\n section\" is further down the page than it looks. Note the loss warning will\n not catch it if your replacement text is longer than what it replaced, since\n there is then no net loss to report.\n\n- **To change only tags, use `cerefox_set_document_metadata`, never `cerefox_ingest`.**\n Ingest replaces the whole document, so re-sending it to set one tag carries the\n full transcription risk for no reason. The metadata tool merges: the keys you\n pass are set, everything else is left alone, so you do not need to read the\n document first and cannot drop a tag another agent set. Pass `null` as a value\n to remove a key.\n\n- **Never partial-edit to fix a partial edit.** If a write leaves unexpected\n structure, stop. Use `cerefox_list_versions`, retrieve the last good version,\n and re-ingest cleanly. Repairing edits with more edits compounds the damage.\n\n- **A rejected batch is safe.** Operations in one `cerefox_edit` are\n all-or-nothing: if any is invalid, nothing is written. A refusal costs you a\n retry, not data — so prefer one call for changes that belong together, and do\n not split a batch to \"make it more likely to succeed\".\n\n- **Read before replacing.** `cerefox_get_document(section: \"## Heading\")`\n returns exactly what a `replace_section` on that anchor would overwrite. Use it\n for any section you did not write in this session. The outline gives a\n section's *size*, never its *text*.\n\n- **Verify after writing** — read the result back before reporting success, and\n report what the read actually shows.\n\n- **Partial edits cannot change a document's stored TITLE.** `rename_section`\n changes a heading inside the content; the title is a separate field and still\n needs `cerefox_ingest`.\n\n- **If a capability seems missing from one server, suspect your client first.**\n Local and remote run the same code. **Every `cerefox_get_help()` response\n begins with the server's version and the operations it registers** — you do\n not need a special topic, and the *absence* of that block is itself an answer:\n a server that does not print it predates v1.5.0. If that\n disagrees with your tool list, the client is holding a list it fetched before\n an upgrade — clients cache it at connect time. Ask the user to restart the\n client. Do not record a capability difference between servers as a fact; every\n such report so far has been a stale client.\n- Long inline bodies can arrive with literal `\\n`/`\\\"` (the author over-escaped; Cerefox stores bytes faithfully). For long or quote-dense content, ingest from a file or build incrementally with `cerefox_insert`; read back multi-line writes.\n";
|
|
15
15
|
|
|
16
16
|
/** Sections keyed by their H2 heading text (lower-cased for matching). */
|
|
17
17
|
export const HELP_SECTIONS: Record<string, string> = {
|
|
18
18
|
"Tools": "## Tools\n\n| Tool | Purpose | Key params |\n|------|---------|------------|\n| `cerefox_search` | Find documents (hybrid FTS + semantic) | `query` (required), `project_name`, `metadata_filter`, `author` |\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 10), `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_insert` | **Add** to a document without resending it. Cannot destroy content. | `document_id`, `text`, `position` (`end_of_document`/`end_of_section`/`after_heading`/`before_heading`), `expected_content_hash` (required), `anchor_heading` (unless `end_of_document`), `section_part`, `author` |\n| `cerefox_edit` | **Change** parts of a document: 1..n operations applied atomically | `document_id`, `operations` (`insert`/`replace_section`/`delete_section`/`rename_section`), `expected_content_hash` (required), `author` |\n| `cerefox_delete_document` | **Soft**-delete a document (to trash; excluded from search; permanent purge is human-only) | `document_id`, `expected_content_hash` (**required** — a delete must follow a read), `reason` (recorded in the audit log — give one), `author` |\n| `cerefox_restore_document` | Restore a soft-deleted document from the trash (audited inverse of delete; no-op if not deleted) | `document_id` (required), `reason` (recorded in the audit log), `author` |\n| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token), or with `outline: true` just its heading paths, sizes and hash, or with `section: \"## Heading\"` one section's text | `document_id` (required), `outline`, `section`, `section_part`, `author` |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required), `author` |\n| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type`, `author` |\n| `cerefox_get_relations` ⚑ | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` ⚑ | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\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_metadata` | Change tags WITHOUT resending content. **Merges** by default; a `null` value removes a key | `document_id`, `metadata` (required), `replace` (rare: set exactly this object), `author` |\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), `author` |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `by_author` (filter), `operation`, `since`, `author` |\n| `cerefox_get_help` | Retrieve Cerefox conventions (this reference) over MCP. **Call this whenever uncertain.** | `topic` (optional, case-insensitive H2 substring match) |\n\n⚑ **Opt-in — usually absent.** The four relation tools are hidden unless the\noperator enables them (`relations_enabled`). **Trust your own tool list**: if\nthey are not in it, the feature is switched off for this deployment. That is\nnormal, not an error, and not something to work around.",
|
|
19
19
|
"Editing part of a document (prefer this over re-sending)": "## Editing part of a document (prefer this over re-sending)\n\n**Re-sending a whole document to change part of it is the main way agents lose\ndata.** You have to reproduce the untouched remainder verbatim, and any drift\nsilently rewrites content nobody asked you to touch — which the caller cannot\ndiff. Use the partial-edit tools instead:\n\n1. **Learn the anchors** — `cerefox_get_document(document_id, outline: true)`.\n Returns heading paths, per-section sizes and the `content_hash`, without the\n body. The paths it returns are exactly what `anchor_heading` accepts.\n2. **Add** → `cerefox_insert`. `end_of_document` is a plain append;\n `end_of_section` adds inside a named section. It is structurally incapable of\n removing anything, so \"I meant to append\" cannot become \"I replaced the file\".\n3. **Look before you overwrite** — `cerefox_get_document(document_id,\n section: \"## Heading\")` returns exactly the text a `replace_section` on that\n anchor would destroy. The outline gives you a section's *size*, never its\n *text*, so on a document you did not write yourself this is the difference\n between a replace and a blind overwrite.\n4. **Change or remove** → `cerefox_edit`. Put changes that belong together in\n ONE call: they apply atomically, so a table row and the total it feeds cannot\n end up disagreeing. To change a single line, `replace_section` on its\n smallest enclosing heading — that is the intended granularity, not a\n workaround. To fix a stale heading (`## OPEN TODOs (as of ...)`), use\n `rename_section`: it changes the heading text and leaves the body and\n position alone.\n5. All of them require `expected_content_hash` and **have no last-write-wins**. A\n conflict means someone else changed the document; re-read and decide, do not\n force it.\n\n**A section runs to the next same-or-higher heading, or to the end of the\ndocument.** So `end_of_document` inserts land inside the *last* section, and\nreplacing or deleting that section removes them too. A large shrink in the\nresponse is your warning; `cerefox_list_versions` has the previous content.\n\n**When an anchor is ambiguous the tool refuses and hands you the options** — a\nrepeated heading returns the qualifying paths, and a section with both its own\ncontent and sub-sections returns both `section_part` choices. That is a\nrecoverable answer, not a failure: retry with what it gave you.",
|
|
20
|
-
"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`** to your name on every call, reads and writes alike (e.g., \"Claude Code\", \"archiver\"). Same parameter on every tool. (`requestor` is still accepted everywhere as the pre-1.13.1 alias.) On the CLI it is `--author` on every command too (plus `--author-type` on writes); or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE` env vars set in the user's `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **`max_bytes` has to fit a whole document.** Search returns COMPLETE documents, so a budget smaller than the top hit fits nothing. You get a header-only list saying so (never \"No results found.\" — that means the store really has nothing). Raise `max_bytes`, or read one document with `cerefox_get_document` using `outline: true` or `section`.\n6. **Add metadata** -- at minimum `type` (\"decision-log\", \"research\", \"design-doc\") and `status` (\"active\", \"draft\").\n7. **Write for retrieval: the title does real work.** The document title is indexed at the **highest weight** *and* prepended to every chunk before it is embedded, so it steers both halves of hybrid search (keyword and semantic) for every chunk in the document. Name the document with the distinctive terms someone would actually search for -- \"Cerefox Response Size Limits\", not \"Notes\" or \"Update 3\". Renaming re-indexes and re-embeds, so correcting a vague title is cheap and takes effect immediately. Write the characters themselves: `&` and `<>`, never `&` or `<>` (entities are stored literally and the write reply will say so). Inside the content, write structured Markdown with H1/H2/H3 headings: the chunker follows them, and a chunk's own heading carries the same weight as the title.\n8. **Deletes are soft (recoverable); permanent purge is web-UI-only.** `cerefox_delete_document` requires the document's `content_hash` as you read it (read before you delete) and takes a `reason` — give one; it is what the human reviewing the trash sees. `cerefox_restore_document` undoes a mistaken delete (also audited, also takes a `reason`). Always surface deletes AND restores to the user. Once a human purges from the web UI, the document is gone for good; so is one auto-purged after `trash_retention_days`, if the operator has turned trash auto-purge on.\n9. **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. **The server validates `](uuid)` links on every write** (v1.7.0): a link to a nonexistent id rejects the write, naming the offender — that means you mangled the UUID; re-read the source and correct it, do not retry unchanged. Example ids go in backticks (code is not validated). `[[Wikilinks]]` may dangle. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n10. **Concurrency: content updates require `expected_content_hash`.** Pass the `content_hash` you last saw — every read shows one (`cerefox_get_document` incl. outline mode, `cerefox_search`, `cerefox_metadata_search`) and **every write returns the new one, including create** (v1.3.0, #189), so after writing you already hold the token for your next edit; no re-read needed. 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.\n11. **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.\n12. **Relations express how documents relate; lifecycle tells you if knowledge is still good.** Use `cerefox_set_relation` when one document supersedes, contradicts, references, or continues another. `supersedes` marks the target **superseded**; `contradicts` marks **both** stale; `related_to`/`duplicates`/`contradicts` are symmetric (both directions written). Any other type string is accepted without special
|
|
20
|
+
"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`** to your name on every call, reads and writes alike (e.g., \"Claude Code\", \"archiver\"). Same parameter on every tool. (`requestor` is still accepted everywhere as the pre-1.13.1 alias.) On the CLI it is `--author` on every command too (plus `--author-type` on writes); or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE` env vars set in the user's `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **`max_bytes` has to fit a whole document.** Search returns COMPLETE documents, so a budget smaller than the top hit fits nothing. You get a header-only list saying so (never \"No results found.\" — that means the store really has nothing). Raise `max_bytes`, or read one document with `cerefox_get_document` using `outline: true` or `section`.\n6. **Add metadata** -- at minimum `type` (\"decision-log\", \"research\", \"design-doc\") and `status` (\"active\", \"draft\").\n7. **Write for retrieval: the title does real work.** The document title is indexed at the **highest weight** *and* prepended to every chunk before it is embedded, so it steers both halves of hybrid search (keyword and semantic) for every chunk in the document. Name the document with the distinctive terms someone would actually search for -- \"Cerefox Response Size Limits\", not \"Notes\" or \"Update 3\". Renaming re-indexes and re-embeds, so correcting a vague title is cheap and takes effect immediately. Write the characters themselves: `&` and `<>`, never `&` or `<>` (entities are stored literally and the write reply will say so). Inside the content, write structured Markdown with H1/H2/H3 headings: the chunker follows them, and a chunk's own heading carries the same weight as the title.\n8. **Deletes are soft (recoverable); permanent purge is web-UI-only.** `cerefox_delete_document` requires the document's `content_hash` as you read it (read before you delete) and takes a `reason` — give one; it is what the human reviewing the trash sees. `cerefox_restore_document` undoes a mistaken delete (also audited, also takes a `reason`). Always surface deletes AND restores to the user. Once a human purges from the web UI, the document is gone for good; so is one auto-purged after `trash_retention_days`, if the operator has turned trash auto-purge on.\n9. **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. **The server validates `](uuid)` links on every write** (v1.7.0): a link to a nonexistent id rejects the write, naming the offender — that means you mangled the UUID; re-read the source and correct it, do not retry unchanged. Example ids go in backticks (code is not validated). `[[Wikilinks]]` may dangle. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n10. **Concurrency: content updates require `expected_content_hash`.** Pass the `content_hash` you last saw — every read shows one (`cerefox_get_document` incl. outline mode, `cerefox_search`, `cerefox_metadata_search`) and **every write returns the new one, including create** (v1.3.0, #189), so after writing you already hold the token for your next edit; no re-read needed. 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.\n11. **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.\n12. **Relations express how documents relate; lifecycle tells you if knowledge is still good.** Use `cerefox_set_relation` when one document supersedes, contradicts, references, or continues another. `supersedes` marks the target **superseded**; `contradicts` marks **both** stale; `related_to`/`duplicates`/`contradicts` are symmetric (both directions written). Any other type string is accepted without special behavior. When a search result or `cerefox_get_relations` shows a neighbour marked `[superseded]` or `[stale]`, say so rather than presenting it as current.\n13. **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`.",
|
|
21
21
|
"Update Workflow (ID-based -- preferred)": "## 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.",
|
|
22
22
|
"Update Workflow (title-based -- fallback)": "## 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```",
|
|
23
23
|
"Catch-Up Workflow": "## Catch-Up Workflow\n\n```\nmetadata_search(metadata_filter={\"type\": \"decision-log\"}, updated_since=\"2026-03-28T00:00:00Z\")\n```",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* worked stops working; it is no longer listed in any schema. No exceptions:
|
|
14
14
|
* on cerefox_get_audit_log, where `author` used to be the entries FILTER,
|
|
15
15
|
* the filter is now `by_author` so that `author` means the same thing there
|
|
16
|
-
* as everywhere else. (That one is a real
|
|
16
|
+
* as everywhere else. (That one is a real behavior change for a caller that
|
|
17
17
|
* filtered with `author`; it is called out in the CHANGELOG.)
|
|
18
18
|
*/
|
|
19
19
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - Neither → no change.
|
|
13
13
|
*
|
|
14
14
|
* Mirrors `supabase/functions/cerefox-mcp/tools/ingest.ts` for v0.4.0
|
|
15
|
-
* extraction (no
|
|
15
|
+
* extraction (no behavior change).
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { escapedContentNote, escapedTitleNote } from "./escape-heuristic.ts";
|
|
@@ -17,7 +17,7 @@ import { McpInvalidParams, type ToolContext, type ToolDefinition } from "./types
|
|
|
17
17
|
import type { MCPSupabaseClient } from "./types.ts";
|
|
18
18
|
import { AUTHOR_PARAM_READ, AUTHOR_PARAM_WRITE, DEFAULT_IDENTITY, callerIdentity } from "./identity.ts";
|
|
19
19
|
|
|
20
|
-
/** Relation types that carry
|
|
20
|
+
/** Relation types that carry behavior; any other string is accepted too. */
|
|
21
21
|
const KNOWN_TYPES =
|
|
22
22
|
"related_to, references, supersedes, contradicts, duplicates, part_of, follows, reply_to";
|
|
23
23
|
|
|
@@ -78,10 +78,10 @@ export const setRelationTool: ToolDefinition = {
|
|
|
78
78
|
name: "cerefox_set_relation",
|
|
79
79
|
description:
|
|
80
80
|
"Link two documents with a typed, directed relation (source → target). " +
|
|
81
|
-
`Known types with
|
|
81
|
+
`Known types with behavior: ${KNOWN_TYPES}. Symmetric types (related_to, ` +
|
|
82
82
|
"contradicts, duplicates) write both directions. `supersedes` marks the target " +
|
|
83
83
|
"superseded; `contradicts` marks both stale. Any other type string is accepted " +
|
|
84
|
-
"and stored, just without special
|
|
84
|
+
"and stored, just without special behavior. Re-setting the same edge updates it.",
|
|
85
85
|
/** Upsert: re-running with the same edge changes nothing. Adds an edge; removes nothing. */
|
|
86
86
|
annotations: {
|
|
87
87
|
title: "Link two documents",
|
|
@@ -182,7 +182,7 @@ async function handler(
|
|
|
182
182
|
const query = args.query as string;
|
|
183
183
|
const project_name = args.project_name as string | undefined;
|
|
184
184
|
// Sanitised, then clamped, exactly as the Edge Function does it. Clamping
|
|
185
|
-
// alone left `NaN` for a non-numeric value, which
|
|
185
|
+
// alone left `NaN` for a non-numeric value, which serializes to JSON null,
|
|
186
186
|
// and `LIMIT NULL` in Postgres means NO limit — the unbounded work the
|
|
187
187
|
// clamp exists to prevent, reachable because the local MCP server passes
|
|
188
188
|
// tool arguments through unvalidated (#265).
|
|
@@ -558,7 +558,7 @@ function applyOne(
|
|
|
558
558
|
}
|
|
559
559
|
if (m[1].length !== node.level) throw new HeadingLevelChangeError(node.heading, next);
|
|
560
560
|
|
|
561
|
-
// Replace the heading LINE only. Not spliceBlock: that
|
|
561
|
+
// Replace the heading LINE only. Not spliceBlock: that normalizes
|
|
562
562
|
// surrounding blank lines, which is right for a block of body text and
|
|
563
563
|
// wrong for a single line whose neighbours are the section's own spacing.
|
|
564
564
|
const hadNewline = content[node.bodyStart - 1] === "\n";
|
|
@@ -198,7 +198,7 @@ async function embedBatch(texts: string[], apiKey: string): Promise<number[][]>
|
|
|
198
198
|
throw lastError ?? new Error(`Embedding API failed after ${EMBEDDING_MAX_RETRIES} attempts`);
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
// ── Content
|
|
201
|
+
// ── Content normalization + hash (SHA-256 hex) ────────────────────────────
|
|
202
202
|
// Must stay in sync with pipeline.py::_normalize / _hash.
|
|
203
203
|
// Converts CRLF (and bare CR) to LF, strips leading/trailing whitespace, and
|
|
204
204
|
// collapses 3+ consecutive newlines to two. The CRLF step is required because
|
|
@@ -10,7 +10,7 @@ import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
|
|
10
10
|
* As of v0.4.0 (iter-22): the per-tool handlers live in `_shared/mcp-tools/`
|
|
11
11
|
* (relative to the repo root) and are imported here verbatim. The new local
|
|
12
12
|
* TS MCP server (`@cerefox/memory`, `packages/memory/`) uses the same
|
|
13
|
-
* modules — single source of truth for tool
|
|
13
|
+
* modules — single source of truth for tool behavior across the two
|
|
14
14
|
* transports. This file's only responsibility is the MCP protocol surface
|
|
15
15
|
* (JSON-RPC over HTTP) + Cerefox's identity-enforcement wrapper.
|
|
16
16
|
*
|
|
@@ -359,7 +359,7 @@ Deno.serve(async (req: Request) => {
|
|
|
359
359
|
}
|
|
360
360
|
|
|
361
361
|
// Apply byte budget — drop whole results (never truncate mid-doc) to stay
|
|
362
|
-
// under the limit. This mirrors the local MCP server's truncation
|
|
362
|
+
// under the limit. This mirrors the local MCP server's truncation behavior.
|
|
363
363
|
const matched = (data ?? []) as Array<Record<string, unknown>>;
|
|
364
364
|
const { accepted, truncated, usedBytes } = applyByteBudget(matched, max_bytes);
|
|
365
365
|
|
package/docs/api/openapi.json
CHANGED
|
@@ -715,7 +715,7 @@
|
|
|
715
715
|
},
|
|
716
716
|
"value": {
|
|
717
717
|
"type": "string",
|
|
718
|
-
"description": "Stored as text; validated against the key's
|
|
718
|
+
"description": "Stored as text; validated against the key's catalog entry."
|
|
719
719
|
}
|
|
720
720
|
},
|
|
721
721
|
"required": [
|
|
@@ -3850,7 +3850,7 @@
|
|
|
3850
3850
|
},
|
|
3851
3851
|
"/api/v1/preferences": {
|
|
3852
3852
|
"get": {
|
|
3853
|
-
"summary": "Web UI preferences (currently the
|
|
3853
|
+
"summary": "Web UI preferences (currently the color scheme), stored in a file in the user-state dir, not the database. Registers even when Supabase is not configured.",
|
|
3854
3854
|
"operationId": "get_preferences",
|
|
3855
3855
|
"responses": {
|
|
3856
3856
|
"200": {
|
|
@@ -3903,7 +3903,7 @@
|
|
|
3903
3903
|
}
|
|
3904
3904
|
},
|
|
3905
3905
|
"put": {
|
|
3906
|
-
"summary": "Web UI preferences (currently the
|
|
3906
|
+
"summary": "Web UI preferences (currently the color scheme), stored in a file in the user-state dir, not the database. Registers even when Supabase is not configured.",
|
|
3907
3907
|
"operationId": "put_preferences",
|
|
3908
3908
|
"responses": {
|
|
3909
3909
|
"200": {
|
|
@@ -5257,6 +5257,13 @@
|
|
|
5257
5257
|
},
|
|
5258
5258
|
"truncated": {
|
|
5259
5259
|
"type": "boolean"
|
|
5260
|
+
},
|
|
5261
|
+
"embedder": {
|
|
5262
|
+
"type": "string",
|
|
5263
|
+
"enum": [
|
|
5264
|
+
"openai",
|
|
5265
|
+
"local"
|
|
5266
|
+
]
|
|
5260
5267
|
}
|
|
5261
5268
|
},
|
|
5262
5269
|
"required": [
|
package/docs/guides/api.md
CHANGED
|
@@ -139,7 +139,7 @@ whatever `requestor` arrives. Any client can send any name, including a name
|
|
|
139
139
|
another client uses.
|
|
140
140
|
|
|
141
141
|
They exist for **attribution and record-keeping**: a legible audit trail, usage
|
|
142
|
-
analytics that can tell callers apart, and the review-queue
|
|
142
|
+
analytics that can tell callers apart, and the review-queue behavior of
|
|
143
143
|
`author_type: agent` while the review workflow is on. They are not a security measure, they do not establish an
|
|
144
144
|
authenticated identity, and no access decision is made on them. Treat a name in
|
|
145
145
|
the audit log as "what the caller said", never as "who the caller was".
|
|
@@ -171,7 +171,7 @@ asserted against a running server by `api-schema-truth.test.ts`, so the spec
|
|
|
171
171
|
describes what the API returns rather than what someone believed it returned.
|
|
172
172
|
|
|
173
173
|
Coverage is stated in the document itself under `x-cerefox-coverage`. Endpoints
|
|
174
|
-
with no
|
|
174
|
+
with no modeled shape are listed there and get no response schema rather than
|
|
175
175
|
an invented one — a spec that guesses is worse than one that admits a gap.
|
|
176
176
|
|
|
177
177
|
Base URL is wherever `cerefox web` is listening (`http://127.0.0.1:8000` by
|
|
@@ -182,7 +182,7 @@ default; Cerefox Local picks its own port and `cerefox-local status` prints it).
|
|
|
182
182
|
| `GET /version` | Server version and environment label. |
|
|
183
183
|
| `GET /schema-version` | Deployed schema version. |
|
|
184
184
|
| `GET /openapi.json` | This API's OpenAPI 3.1 description, served by the version you are talking to. Every `/api/v1` response points at it with `Link: </api/v1/openapi.json>; rel="service-desc"` (RFC 8631). |
|
|
185
|
-
| `GET /search?q=…` | Hybrid search (FTS + semantic). `&review_status=approved\|pending_review` filters server-side; `400` while the review workflow is off. |
|
|
185
|
+
| `GET /search?q=…` | Hybrid search (FTS + semantic). `&review_status=approved\|pending_review` filters server-side; `400` while the review workflow is off. The response's `embedder` (`openai` or `local`) says which scale the scores are on. |
|
|
186
186
|
| `GET /dashboard`, `GET /dashboard/recent-docs` | Dashboard aggregates. |
|
|
187
187
|
| `GET /documents/{id}` | Full document, with metadata, projects and versions. |
|
|
188
188
|
| `GET /documents/{id}/chunks` | The document's chunks. |
|
|
@@ -205,7 +205,7 @@ default; Cerefox Local picks its own port and `cerefox-local status` prints it).
|
|
|
205
205
|
| `GET /projects/{id}/documents` | A project's documents. |
|
|
206
206
|
| `GET /config`, `GET /config/{key}`, `PUT /config/{key}` | Runtime config. |
|
|
207
207
|
| `GET /audit-log` | Audit trail, filterable. |
|
|
208
|
-
| `GET /preferences`, `PUT /preferences` | Web UI preferences (currently the
|
|
208
|
+
| `GET /preferences`, `PUT /preferences` | Web UI preferences (currently the color scheme), stored in a file in the user-state dir, not the database. Registers even when Supabase is not configured. |
|
|
209
209
|
| `GET /docs`, `GET /docs/{path}` | The guides bundled with this server: a listing, and one document's markdown. Serves the shipped copies, so it answers the same content regardless of what is in the knowledge base. |
|
|
210
210
|
| `GET /usage-log`, `GET /usage-log/summary`, `GET /usage-log/export.csv` | Usage queries. |
|
|
211
211
|
|
package/docs/guides/cli.md
CHANGED
|
@@ -785,7 +785,7 @@ These flat commands handle install, configuration, and health. Run any with `--h
|
|
|
785
785
|
|
|
786
786
|
## Environment variables
|
|
787
787
|
|
|
788
|
-
The CLI reads its own runtime config from environment (or `.env`). See [`configuration.md`](configuration.md) for the full list. Most relevant to CLI
|
|
788
|
+
The CLI reads its own runtime config from environment (or `.env`). See [`configuration.md`](configuration.md) for the full list. Most relevant to CLI behavior:
|
|
789
789
|
|
|
790
790
|
| Variable | Default | Effect |
|
|
791
791
|
|---|---|---|
|
|
@@ -878,7 +878,7 @@ surface).
|
|
|
878
878
|
|
|
879
879
|
## Known issues
|
|
880
880
|
|
|
881
|
-
None outstanding as of v1.0.1. When new bugs surface, they are tracked in the GitHub issues list; check there before relying on a
|
|
881
|
+
None outstanding as of v1.0.1. When new bugs surface, they are tracked in the GitHub issues list; check there before relying on a behavior the docs imply.
|
|
882
882
|
|
|
883
883
|
## Common recipes
|
|
884
884
|
|
|
@@ -145,7 +145,7 @@ This handles intermittent OpenAI API errors (500s) that would otherwise cause se
|
|
|
145
145
|
>
|
|
146
146
|
> Resolution order, highest first: **per-call argument** (`--min-score`, the
|
|
147
147
|
> `min_score` MCP parameter) → **`cerefox_config`** → built-in default. A
|
|
148
|
-
> malformed stored value is ignored in
|
|
148
|
+
> malformed stored value is ignored in favor of the built-in, so a bad setting
|
|
149
149
|
> can never break search. (Before v1.17.0 the web UI and `/api/v1` search sent
|
|
150
150
|
> their own `min_search_score` and `search_alpha`, so they did not obey a stored
|
|
151
151
|
> value; now every path does.)
|
|
@@ -160,7 +160,7 @@ This handles intermittent OpenAI API errors (500s) that would otherwise cause se
|
|
|
160
160
|
> | `search_alpha` | **0.7** | Weight of meaning against keywords. Both sides are on a 0 to 1 scale since v1.17.0, so the weight now means what it says. |
|
|
161
161
|
>
|
|
162
162
|
> Tune only with evidence: `bun scripts/search_benchmark.ts` measures any
|
|
163
|
-
> setting against a
|
|
163
|
+
> setting against a labeled query vocabulary on a staging store
|
|
164
164
|
> ([ops-scripts.md](ops-scripts.md#search_benchmarkts--search-calibration-benchmark)).
|
|
165
165
|
> A setting that suits one store's content can cost another kind of query
|
|
166
166
|
> without anything looking wrong.
|
|
@@ -243,7 +243,7 @@ longer applies.)
|
|
|
243
243
|
- MCP tool: `{"query": "...", "max_bytes": 50000}`
|
|
244
244
|
- Edge Function body: `{"query": "...", "max_bytes": 50000}`
|
|
245
245
|
|
|
246
|
-
See `docs/guides/response-limits.md` for the full guide including
|
|
246
|
+
See `docs/guides/response-limits.md` for the full guide including behavior details and examples.
|
|
247
247
|
|
|
248
248
|
### RPC-level retrieval parameters
|
|
249
249
|
|
|
@@ -339,7 +339,7 @@ there matters.
|
|
|
339
339
|
| Variable | Default | Description |
|
|
340
340
|
|----------|---------|-------------|
|
|
341
341
|
| `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. |
|
|
342
|
-
| `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, and `configure-agent` registers the MCP server as `cerefox-<label>` so a
|
|
342
|
+
| `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, and `configure-agent` registers the MCP server as `cerefox-<label>` so a labeled environment sits alongside production instead of overwriting it (v1.4.0, #168). See [`staging-env.md`](staging-env.md). |
|
|
343
343
|
| `CEREFOX_VERSION_RETENTION_HOURS` | **Retired in v1.1.0 — no longer read.** | Version retention is now a property of the store: `cerefox config set version_retention_hours <hours>`, or the **Settings** page. It moved because it used to be passed per-call from each client's environment, so the surviving history depended on which client wrote last. `cerefox doctor` reports the variable if it is still set. |
|
|
344
344
|
|
|
345
345
|
---
|
|
@@ -53,7 +53,7 @@ Three top-level paths plus a few special cases:
|
|
|
53
53
|
> ChatGPT becomes possible on the same server but is not yet documented.)
|
|
54
54
|
|
|
55
55
|
> **Perplexity** supports stdio-only MCP on macOS Desktop (via Helper App). Remote MCP is
|
|
56
|
-
> "coming soon." Perplexity's CTO has
|
|
56
|
+
> "coming soon." Perplexity's CTO has signaled a strategic shift away from MCP (March 2026),
|
|
57
57
|
> so API-based integration may be the long-term path. Not a priority.
|
|
58
58
|
>
|
|
59
59
|
> **Gemini web** (gemini.google.com) does not support custom MCP servers. No integration path.
|
|
@@ -174,7 +174,7 @@ set — as it is for a [staging environment](staging-env.md) — the name become
|
|
|
174
174
|
entry rather than replacing it, and an agent can hold both at once (v1.4.0, #168).
|
|
175
175
|
The command prints the name it used.
|
|
176
176
|
|
|
177
|
-
A
|
|
177
|
+
A labeled entry additionally carries `CEREFOX_CONFIG_DIR` and `CEREFOX_ENV_LABEL`
|
|
178
178
|
in its `env` block. That is what makes it actually reach the environment it is named
|
|
179
179
|
after: MCP clients spawn a stdio server with the **client's** environment, not the
|
|
180
180
|
shell you ran `configure-agent` in, and a desktop client launched from the dock has
|
|
@@ -344,7 +344,7 @@ https://<your-project-ref>.supabase.co/functions/v1/cerefox-mcp
|
|
|
344
344
|
|----------|--------|
|
|
345
345
|
| Default local agent (Claude Code, Cursor, Codex, Gemini, Claude Desktop) | Path A-Local -- preferred; `cerefox configure-agent`, zero Edge Function cost, no token to distribute |
|
|
346
346
|
| Multiple machines / cloud dev environments | Path A-Remote -- one hosted URL works everywhere (needs a Cerefox token) |
|
|
347
|
-
|
|
|
347
|
+
| Minimize Supabase Edge Function usage (free tier limits) | Path A-Local -- zero Edge Function invocations |
|
|
348
348
|
| Offline use or development on the cerefox codebase | Path A-Local -- no network dependency |
|
|
349
349
|
| Lowest latency (same machine, no HTTPS round-trip) | Path A-Local -- slightly faster |
|
|
350
350
|
|
|
@@ -277,7 +277,7 @@ it is off the field is absent from every other surface, and an export is not the
|
|
|
277
277
|
place to resurrect it.
|
|
278
278
|
|
|
279
279
|
Markdown rather than JSON so the sidecars are readable in the same editor as the
|
|
280
|
-
documents. Pass `--no-metadata` for the older content-only
|
|
280
|
+
documents. Pass `--no-metadata` for the older content-only behavior.
|
|
281
281
|
|
|
282
282
|
Two naming details worth knowing:
|
|
283
283
|
|
|
@@ -320,7 +320,7 @@ Two things worth knowing about what it does *not* claim:
|
|
|
320
320
|
|
|
321
321
|
- **Coverage is stated, not implied.** `x-cerefox-coverage` in the document lists
|
|
322
322
|
how many routes carry a response schema, which routes legitimately return
|
|
323
|
-
something other than JSON, and which are
|
|
323
|
+
something other than JSON, and which are unmodeled. An unmodeled route gets
|
|
324
324
|
**no** schema rather than a guessed one.
|
|
325
325
|
- **`info.version` is the API's, not the release's.** It moves when `/api/v1`
|
|
326
326
|
changes. Binding it to the package version made the committed artifact stale at
|
|
@@ -329,7 +329,7 @@ Two things worth knowing about what it does *not* claim:
|
|
|
329
329
|
|
|
330
330
|
## search_benchmark.ts — Search-calibration benchmark
|
|
331
331
|
|
|
332
|
-
Measures how search ranks against a
|
|
332
|
+
Measures how search ranks against a labeled query vocabulary
|
|
333
333
|
(`_shared/search-benchmark/vocabulary/`: a synthetic corpus, about 150 queries
|
|
334
334
|
in 13 categories, variant groups, no-answer queries) and compares alternative
|
|
335
335
|
scoring formulas and settings. It is how the v1.17.0 defaults were chosen. Design
|
|
@@ -19,13 +19,13 @@ raises or lowers it per call with `--max-bytes`.
|
|
|
19
19
|
> **Changed in v0.10.2.** The CLI originally returned everything, like the web UI. It now
|
|
20
20
|
> honours `CEREFOX_MAX_RESPONSE_BYTES` (200 000 default) and prints
|
|
21
21
|
> `(results truncated at N bytes; use --max-bytes to raise)` when results are dropped.
|
|
22
|
-
> This guide described the pre-v0.10.2
|
|
22
|
+
> This guide described the pre-v0.10.2 behavior until v1.14.4.
|
|
23
23
|
|
|
24
24
|
---
|
|
25
25
|
|
|
26
26
|
## How each access path handles response size
|
|
27
27
|
|
|
28
|
-
| Path | Limit
|
|
28
|
+
| Path | Limit behavior |
|
|
29
29
|
|------|----------------|
|
|
30
30
|
| Web UI (`/search`) | **No limit** — all results returned |
|
|
31
31
|
| CLI (`cerefox search`) | Defaults to `CEREFOX_MAX_RESPONSE_BYTES` (200 000); raise or lower per call with `--max-bytes`. Announces truncation. |
|
|
@@ -203,4 +203,4 @@ threshold (it is a SQL DEFAULT in `rpcs.sql`, changed via `cerefox server deploy
|
|
|
203
203
|
| Can an agent exceed the server ceiling? | No — always capped |
|
|
204
204
|
| Where is the ceiling configured? | `.env` for local MCP; TypeScript constant in Edge Functions |
|
|
205
205
|
| How are limits applied? | Whole-document drop; never mid-content truncation |
|
|
206
|
-
| Is truncation
|
|
206
|
+
| Is truncation signaled? | Yes — `truncated: true` in responses |
|
|
@@ -30,7 +30,7 @@ Three things, and the third turned out to matter most:
|
|
|
30
30
|
every legacy document (real spend, rewrites chunks). Proving it on a copy
|
|
31
31
|
first turns an act of faith into a measurement.
|
|
32
32
|
- **Development.** A store you are willing to break is the cheapest way to turn
|
|
33
|
-
a claim about runtime
|
|
33
|
+
a claim about runtime behavior into a measurement. During the 1.1.0 cycle this
|
|
34
34
|
found a retry loop executing 68,825 times per request, a `migrate-format` that
|
|
35
35
|
reported success while converting nothing, and a `backup create` that failed
|
|
36
36
|
against older servers — none visible from the code or the test suite. Keep it
|
|
@@ -276,7 +276,7 @@ read but nothing acts on it.
|
|
|
276
276
|
|
|
277
277
|
| Area | Problem |
|
|
278
278
|
|---|---|
|
|
279
|
-
| **Agent (MCP) config** | Safe since v1.4.0 ([#168](https://github.com/fstamatelopoulos/cerefox/issues/168)). `configure-agent` names the server after `CEREFOX_ENV_LABEL`, so a staging environment registers as **`cerefox-staging`** alongside your production `cerefox` entry rather than replacing it, and an agent can hold both. The
|
|
279
|
+
| **Agent (MCP) config** | Safe since v1.4.0 ([#168](https://github.com/fstamatelopoulos/cerefox/issues/168)). `configure-agent` names the server after `CEREFOX_ENV_LABEL`, so a staging environment registers as **`cerefox-staging`** alongside your production `cerefox` entry rather than replacing it, and an agent can hold both. The labeled entry also carries its own `CEREFOX_CONFIG_DIR` and `CEREFOX_ENV_LABEL` in the config it writes — without that the client would spawn the server with *its own* environment (a GUI client launched from the dock has none), `CEREFOX_CONFIG_DIR` would be absent, and an entry named `cerefox-staging` would quietly serve production. Before v1.4.0 it registered every environment as `cerefox` and running it from staging silently repointed all your agents. |
|
|
280
280
|
| **`doctor`'s `mcp clients` line** | It inspects your global agent configs, which point at production, and reports them even in staging mode. Informative, but easy to misread as "staging is wired to my agents". |
|
|
281
281
|
| **`doctor` remediation commands** | Environment-aware since v1.8.0: in a staging environment the remediations tell you to prefix `CEREFOX_CONFIG_DIR` (or use the alias). Before that, a staging `doctor` printed bare `cerefox …` remediations that act on production. |
|
|
282
282
|
|