@cerefox/memory 1.13.1 → 1.13.2

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.
@@ -11,7 +11,7 @@
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 9), `last_write_wins`, `update_if_exists`, `project_name` (single, non-destructive add on update), `project_names` (list, destructive replace on update), `metadata` (omit on update to keep existing tags; `{}` clears), `author` |\n| `cerefox_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 CLI, pass `--author`/`--author-type` on writes and `--requestor` on reads, or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE`/`CEREFOX_REQUESTOR_NAME` env vars set in the user's `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **Add metadata** -- at minimum `type` (\"decision-log\", \"research\", \"design-doc\") and `status` (\"active\", \"draft\").\n6. **Write structured Markdown** with H1/H2/H3 headings for good chunking and search.\n7. **Deletes are soft (recoverable); 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.\n8. **Cross-doc links inside content**: **always use `[Text](document-uuid)`.** UUIDs are the only fully reliable link form — stable across title changes, never ambiguous, no encoding gotchas. Every `cerefox_search` result shows `[id: <uuid>]` after the title; grab it and use it. Title-based linking (`[Text](<Title With Spaces>)`) is fragile (breaks on colons, parens, ampersands, brackets — silently navigates to wrong page) — **don't write title-based links**; do an extra search to get the UUID instead. Repo-path forms (`[Text](docs/path.md)`) exist for repo-ingested files; don't construct manually. **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.\n9. **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.\n10. **Search: prefer a few distinctive terms; heed `below confidence`.** When nothing clears the relevance threshold, `cerefox_search` returns the closest candidates prefixed with a `below confidence` warning instead of an empty set — that flag means **weak signal, not absent knowledge**: check the candidates' scores and titles before concluding the KB lacks the content. A truly empty response means nothing even weakly related exists.\n11. **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.\n12. **Project memberships — non-destructive by default**: on `cerefox_ingest` updates, **`project_name` (singular) is a non-destructive add** (ensures membership, preserves others). Use **`project_names` (list)** when you want to set the doc's full project set in one call (destructive replace). For metadata-only project changes without writing content, use **`cerefox_set_document_projects(document_id, project_names)`** — that tool is the destructive-replace contract made explicit. Never call `cerefox_set_document_projects` with a single name when you mean \"add\" — that would REMOVE the doc from all other projects. When in doubt, use `cerefox_ingest` with singular `project_name`.\n\n## Update Workflow (ID-based -- preferred)\n\n```\nsearch(\"topic\") -> find doc [id: abc123] -> get_document(abc123) -> note its content_hash -> modify ->\ningest(title=\"Same Title\", content=\"...\", document_id=\"abc123\",\n expected_content_hash=\"<the hash you read>\", author=\"my-agent\")\n```\n\nOn a **conflict** error: get_document again (fresh content + fresh hash) -> merge your changes -> retry with the new hash.\n\n## Update Workflow (title-based -- fallback)\n\n```\nsearch(\"topic\") -> find doc (note its hash) -> modify ->\ningest(title=\"Same Title\", content=\"...\", update_if_exists=true,\n expected_content_hash=\"<the hash you read>\", author=\"my-agent\")\n```\n\n## Catch-Up Workflow\n\n```\nmetadata_search(metadata_filter={\"type\": \"decision-log\"}, updated_since=\"2026-03-28T00:00:00Z\")\n```\n\n## CLI fallback (when MCP is unavailable)\n\nIf `cerefox_search` is not in your tool list, your user has likely installed the Cerefox CLI. The canonical invocation is plain **`cerefox <subcommand>`** (the TypeScript CLI, installed via `npm install -g @cerefox/memory`). It uses a resource-verb shape (`cerefox document get`, `cerefox project list`, …).\n\nSame operations, same conventions. Full reference: [`docs/guides/cli.md`](docs/guides/cli.md). CLI flag names match MCP parameter names exactly (e.g. `metadata_filter` ↔ `--metadata-filter`); common flags also have single-letter short forms (`-f`, `-p`, `-c`, `-m`, `-u`, `-a`, `-r`). Use the canonical long name (what `--help` shows) or its short form — there are no long-form aliases like `--filter` or `--count`.\n\n| MCP tool | CLI |\n|---|---|\n| `cerefox_search` | `cerefox search \"<q>\" --requestor \"<your-name>\"` |\n| `cerefox_ingest` (paste) | `printf '...' \\| cerefox document ingest --paste --title \"<t>\" --author \"<your-name>\" --author-type agent` |\n| `cerefox_ingest` (update by ID) | `printf '...' \\| cerefox document ingest --paste --title \"<t>\" --document-id \"<uuid>\" --expected-content-hash \"<hash>\" --author \"<your-name>\" --author-type agent` |\n| `cerefox_get_document` | `cerefox document get <id> --version-id <vid> --requestor \"<your-name>\"` |\n| `cerefox_list_versions` | `cerefox document version list <id> --requestor \"<your-name>\"` |\n| `cerefox_list_projects` | `cerefox project list --requestor \"<your-name>\"` |\n| `cerefox_list_metadata_keys` | `cerefox metadata keys` |\n| `cerefox_insert` | `cerefox document insert <id> -t \"<text>\" -p <position> -a \"<anchor-heading>\" -e \"<hash>\" --requestor \"<your-name>\" --author-type agent` |\n| `cerefox_edit` | `cerefox document edit-parts <id> --operations '<json>' -e \"<hash>\" --requestor \"<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>' --requestor \"<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 --requestor \"<your-name>\"` (add `--json` for scripted access) |\n| `cerefox_get_help` | `cerefox guides show agent-quick-reference` (or `cerefox guides list` for the full bundled-docs index) |\n\n**Set identity on every call**, exactly as you would on MCP:\n- Writes (`document ingest`, `document ingest-dir`): `--author \"<your-name>\" --author-type agent`\n- Reads: `--requestor \"<your-name>\"`\n\nOr have your user set `CEREFOX_AUTHOR_NAME` / `CEREFOX_AUTHOR_TYPE` / `CEREFOX_REQUESTOR_NAME` in their `.env` to apply defaults once.\n\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 9), `last_write_wins`, `update_if_exists`, `project_name` (single, non-destructive add on update), `project_names` (list, destructive replace on update), `metadata` (omit on update to keep existing tags; `{}` clears), `author` |\n| `cerefox_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 CLI, pass `--author`/`--author-type` on writes and `--requestor` on reads, or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE`/`CEREFOX_REQUESTOR_NAME` env vars set in the user's `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **Add metadata** -- at minimum `type` (\"decision-log\", \"research\", \"design-doc\") and `status` (\"active\", \"draft\").\n6. **Write structured Markdown** with H1/H2/H3 headings for good chunking and search.\n7. **Deletes are soft (recoverable); 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.\n8. **Cross-doc links inside content**: **always use `[Text](document-uuid)`.** UUIDs are the only fully reliable link form — stable across title changes, never ambiguous, no encoding gotchas. Every `cerefox_search` result shows `[id: <uuid>]` after the title; grab it and use it. Title-based linking (`[Text](<Title With Spaces>)`) is fragile (breaks on colons, parens, ampersands, brackets — silently navigates to wrong page) — **don't write title-based links**; do an extra search to get the UUID instead. Repo-path forms (`[Text](docs/path.md)`) exist for repo-ingested files; don't construct manually. **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.\n9. **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.\n10. **Search: prefer a few distinctive terms; heed `below confidence`.** When nothing clears the relevance threshold, `cerefox_search` returns the closest candidates prefixed with a `below confidence` warning instead of an empty set — that flag means **weak signal, not absent knowledge**: check the candidates' scores and titles before concluding the KB lacks the content. A truly empty response means nothing even weakly related exists.\n11. **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.\n12. **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> = {
@@ -21,7 +21,7 @@ export const HELP_SECTIONS: Record<string, string> = {
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```",
24
- "CLI fallback (when MCP is unavailable)": "## CLI fallback (when MCP is unavailable)\n\nIf `cerefox_search` is not in your tool list, your user has likely installed the Cerefox CLI. The canonical invocation is plain **`cerefox <subcommand>`** (the TypeScript CLI, installed via `npm install -g @cerefox/memory`). It uses a resource-verb shape (`cerefox document get`, `cerefox project list`, …).\n\nSame operations, same conventions. Full reference: [`docs/guides/cli.md`](docs/guides/cli.md). CLI flag names match MCP parameter names exactly (e.g. `metadata_filter` ↔ `--metadata-filter`); common flags also have single-letter short forms (`-f`, `-p`, `-c`, `-m`, `-u`, `-a`, `-r`). Use the canonical long name (what `--help` shows) or its short form — there are no long-form aliases like `--filter` or `--count`.\n\n| MCP tool | CLI |\n|---|---|\n| `cerefox_search` | `cerefox search \"<q>\" --requestor \"<your-name>\"` |\n| `cerefox_ingest` (paste) | `printf '...' \\| cerefox document ingest --paste --title \"<t>\" --author \"<your-name>\" --author-type agent` |\n| `cerefox_ingest` (update by ID) | `printf '...' \\| cerefox document ingest --paste --title \"<t>\" --document-id \"<uuid>\" --expected-content-hash \"<hash>\" --author \"<your-name>\" --author-type agent` |\n| `cerefox_get_document` | `cerefox document get <id> --version-id <vid> --requestor \"<your-name>\"` |\n| `cerefox_list_versions` | `cerefox document version list <id> --requestor \"<your-name>\"` |\n| `cerefox_list_projects` | `cerefox project list --requestor \"<your-name>\"` |\n| `cerefox_list_metadata_keys` | `cerefox metadata keys` |\n| `cerefox_insert` | `cerefox document insert <id> -t \"<text>\" -p <position> -a \"<anchor-heading>\" -e \"<hash>\" --requestor \"<your-name>\" --author-type agent` |\n| `cerefox_edit` | `cerefox document edit-parts <id> --operations '<json>' -e \"<hash>\" --requestor \"<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>' --requestor \"<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 --requestor \"<your-name>\"` (add `--json` for scripted access) |\n| `cerefox_get_help` | `cerefox guides show agent-quick-reference` (or `cerefox guides list` for the full bundled-docs index) |\n\n**Set identity on every call**, exactly as you would on MCP:\n- Writes (`document ingest`, `document ingest-dir`): `--author \"<your-name>\" --author-type agent`\n- Reads: `--requestor \"<your-name>\"`\n\nOr have your user set `CEREFOX_AUTHOR_NAME` / `CEREFOX_AUTHOR_TYPE` / `CEREFOX_REQUESTOR_NAME` in their `.env` to apply defaults once.",
24
+ "CLI fallback (when MCP is unavailable)": "## 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.",
25
25
  "Timestamps are UTC": "## 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.",
26
26
  "Mistakes that have actually happened": "## 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.",
27
27
  };
@@ -2,6 +2,7 @@ import "jsr:@supabase/functions-js/edge-runtime.d.ts";
2
2
  import { createClient } from "jsr:@supabase/supabase-js@2";
3
3
  import { isVersionRequest, versionResponse } from "../../../_shared/ef-meta/index.ts";
4
4
  import { efAuthGate } from "../../../_shared/ef-auth/index.ts";
5
+ import { callerIdentity } from "../../../_shared/mcp-tools/identity.ts";
5
6
 
6
7
  /**
7
8
  * cerefox-get-audit-log -- Supabase Edge Function
@@ -61,9 +62,9 @@ Deno.serve(async (req: Request): Promise<Response> => {
61
62
 
62
63
  const body = await req.json().catch(() => ({}));
63
64
 
64
- // Configurable requestor enforcement
65
- const identityField = "requestor";
66
- const identityValue = body[identityField];
65
+ // Configurable caller-identity enforcement: `author`, or `requestor` as the pre-1.13.2 alias (#244)
66
+ const identityField = "author";
67
+ const identityValue = callerIdentity(body as Record<string, unknown>);
67
68
  const { data: reqConfig } = await supabase.rpc("cerefox_get_config", { p_key: "require_requestor_identity" });
68
69
  if (reqConfig === "true") {
69
70
  if (!identityValue || (typeof identityValue === "string" && identityValue.trim() === "")) {
@@ -85,7 +86,8 @@ Deno.serve(async (req: Request): Promise<Response> => {
85
86
 
86
87
  const params: Record<string, unknown> = {};
87
88
  if (body.document_id) params.p_document_id = body.document_id;
88
- if (body.author) params.p_author = body.author;
89
+ // v1.13.2: the entries filter is `by_author`; `author` is the caller's identity (#244).
90
+ if (body.by_author) params.p_author = body.by_author;
89
91
  if (body.operation) params.p_operation = body.operation;
90
92
  if (body.since) params.p_since = body.since;
91
93
  if (body.until) params.p_until = body.until;
@@ -107,7 +109,7 @@ Deno.serve(async (req: Request): Promise<Response> => {
107
109
  Promise.resolve(supabase.rpc("cerefox_log_usage", {
108
110
  p_operation: "get_audit_log",
109
111
  p_access_path: "edge-function",
110
- p_requestor: body.requestor ?? null,
112
+ p_requestor: identityValue ?? null,
111
113
  p_result_count: (data ?? []).length,
112
114
  })).catch(() => {});
113
115
 
@@ -2,6 +2,7 @@ import "jsr:@supabase/functions-js/edge-runtime.d.ts";
2
2
  import { createClient } from "jsr:@supabase/supabase-js@2";
3
3
  import { isVersionRequest, versionResponse } from "../../../_shared/ef-meta/index.ts";
4
4
  import { efAuthGate } from "../../../_shared/ef-auth/index.ts";
5
+ import { callerIdentity } from "../../../_shared/mcp-tools/identity.ts";
5
6
 
6
7
  /**
7
8
  * cerefox-get-document — Supabase Edge Function
@@ -67,9 +68,9 @@ Deno.serve(async (req: Request): Promise<Response> => {
67
68
  const supabaseKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
68
69
  const supabase = createClient(supabaseUrl, supabaseKey);
69
70
 
70
- // Configurable requestor enforcement
71
- const identityField = "requestor";
72
- const identityValue = body[identityField];
71
+ // Configurable caller-identity enforcement: `author`, or `requestor` as the pre-1.13.2 alias (#244)
72
+ const identityField = "author";
73
+ const identityValue = callerIdentity(body as Record<string, unknown>);
73
74
  const { data: reqConfig } = await supabase.rpc("cerefox_get_config", { p_key: "require_requestor_identity" });
74
75
  if (reqConfig === "true") {
75
76
  if (!identityValue || (typeof identityValue === "string" && identityValue.trim() === "")) {
@@ -120,7 +121,7 @@ Deno.serve(async (req: Request): Promise<Response> => {
120
121
  Promise.resolve(supabase.rpc("cerefox_log_usage", {
121
122
  p_operation: "get_document",
122
123
  p_access_path: "edge-function",
123
- p_requestor: body.requestor ?? null,
124
+ p_requestor: identityValue ?? null,
124
125
  p_document_id: document_id,
125
126
  p_result_count: 1,
126
127
  })).catch(() => {});
@@ -2,6 +2,7 @@ import "jsr:@supabase/functions-js/edge-runtime.d.ts";
2
2
  import { createClient } from "jsr:@supabase/supabase-js@2";
3
3
  import { isVersionRequest, versionResponse } from "../../../_shared/ef-meta/index.ts";
4
4
  import { efAuthGate } from "../../../_shared/ef-auth/index.ts";
5
+ import { callerIdentity } from "../../../_shared/mcp-tools/identity.ts";
5
6
  import { capEmbeddingInput } from "../../../_shared/embeddings/index.ts";
6
7
  import {
7
8
  ensureDocumentInProject,
@@ -52,6 +53,8 @@ interface IngestRequest {
52
53
  metadata?: Record<string, unknown>;
53
54
  update_if_exists?: boolean;
54
55
  author?: string;
56
+ /** Alias of `author` (#244). */
57
+ requestor?: string;
55
58
  author_type?: string; // 'user' | 'agent'
56
59
  // Optimistic concurrency (iter-32): REQUIRED on content updates — the
57
60
  // content_hash of the version this edit was based on. Conflict → HTTP 409.
@@ -275,7 +278,8 @@ Deno.serve(async (req: Request) => {
275
278
  // metadata: null = "not provided" — the RPC keeps existing metadata on
276
279
  // update and uses {} on create (v0.11.1; a `= {}` default here used to wipe
277
280
  // a document's tags on every content update that didn't re-pass them).
278
- const { title, content, document_id = null, project_name, source = "agent", metadata = null, update_if_exists = false, author = "agent", author_type = "agent", expected_content_hash = null, last_write_wins = false } = body;
281
+ const { title, content, document_id = null, project_name, source = "agent", metadata = null, update_if_exists = false, author_type = "agent", expected_content_hash = null, last_write_wins = false } = body;
282
+ const author = callerIdentity(body as unknown as Record<string, unknown>) ?? "agent";
279
283
 
280
284
  // metadata must be a plain JSON object (or absent). A scalar/array stored in
281
285
  // the JSONB column poisons cerefox_list_metadata_keys for the whole dataset
@@ -303,10 +307,10 @@ Deno.serve(async (req: Request) => {
303
307
  const supabaseKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
304
308
  const supabase = createClient(supabaseUrl, supabaseKey);
305
309
 
306
- // Configurable requestor enforcement
310
+ // Configurable caller-identity enforcement: `author`, or `requestor` as the alias (#244)
307
311
  {
308
312
  const identityField = "author";
309
- const identityValue = body[identityField as keyof IngestRequest] as string | undefined;
313
+ const identityValue = callerIdentity(body as unknown as Record<string, unknown>);
310
314
  const { data: reqConfig } = await supabase.rpc("cerefox_get_config", { p_key: "require_requestor_identity" });
311
315
  if (reqConfig === "true") {
312
316
  if (!identityValue || (typeof identityValue === "string" && identityValue.trim() === "")) {
@@ -2,6 +2,7 @@ import "jsr:@supabase/functions-js/edge-runtime.d.ts";
2
2
  import { createClient } from "jsr:@supabase/supabase-js@2";
3
3
  import { isVersionRequest, versionResponse } from "../../../_shared/ef-meta/index.ts";
4
4
  import { efAuthGate } from "../../../_shared/ef-auth/index.ts";
5
+ import { callerIdentity } from "../../../_shared/mcp-tools/identity.ts";
5
6
 
6
7
  /**
7
8
  * cerefox-list-projects -- Supabase Edge Function
@@ -15,7 +16,7 @@ import { efAuthGate } from "../../../_shared/ef-auth/index.ts";
15
16
  *
16
17
  * Note: cerefox-mcp calls the RPC directly (not this Edge Function).
17
18
  *
18
- * Request body (JSON): {} or { requestor?: string }
19
+ * Request body (JSON): {} or { author?: string }
19
20
  * Response (200): Array of { id, name, description }
20
21
  */
21
22
 
@@ -51,9 +52,9 @@ Deno.serve(async (req: Request): Promise<Response> => {
51
52
  const supabaseKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
52
53
  const supabase = createClient(supabaseUrl, supabaseKey);
53
54
 
54
- // Configurable requestor enforcement
55
- const identityField = "requestor";
56
- const identityValue = body[identityField];
55
+ // Configurable caller-identity enforcement: `author`, or `requestor` as the pre-1.13.2 alias (#244)
56
+ const identityField = "author";
57
+ const identityValue = callerIdentity(body as Record<string, unknown>);
57
58
  const { data: reqConfig } = await supabase.rpc("cerefox_get_config", { p_key: "require_requestor_identity" });
58
59
  if (reqConfig === "true") {
59
60
  if (!identityValue || (typeof identityValue === "string" && identityValue.trim() === "")) {
@@ -86,7 +87,7 @@ Deno.serve(async (req: Request): Promise<Response> => {
86
87
  Promise.resolve(supabase.rpc("cerefox_log_usage", {
87
88
  p_operation: "list_projects",
88
89
  p_access_path: "edge-function",
89
- p_requestor: body.requestor ?? null,
90
+ p_requestor: identityValue ?? null,
90
91
  p_result_count: (data ?? []).length,
91
92
  })).catch(() => {});
92
93
 
@@ -2,6 +2,7 @@ import "jsr:@supabase/functions-js/edge-runtime.d.ts";
2
2
  import { createClient } from "jsr:@supabase/supabase-js@2";
3
3
  import { isVersionRequest, versionResponse } from "../../../_shared/ef-meta/index.ts";
4
4
  import { efAuthGate } from "../../../_shared/ef-auth/index.ts";
5
+ import { callerIdentity } from "../../../_shared/mcp-tools/identity.ts";
5
6
 
6
7
  /**
7
8
  * cerefox-list-versions — Supabase Edge Function
@@ -65,9 +66,9 @@ Deno.serve(async (req: Request): Promise<Response> => {
65
66
  const supabaseKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
66
67
  const supabase = createClient(supabaseUrl, supabaseKey);
67
68
 
68
- // Configurable requestor enforcement
69
- const identityField = "requestor";
70
- const identityValue = body[identityField];
69
+ // Configurable caller-identity enforcement: `author`, or `requestor` as the pre-1.13.2 alias (#244)
70
+ const identityField = "author";
71
+ const identityValue = callerIdentity(body as Record<string, unknown>);
71
72
  const { data: reqConfig } = await supabase.rpc("cerefox_get_config", { p_key: "require_requestor_identity" });
72
73
  if (reqConfig === "true") {
73
74
  if (!identityValue || (typeof identityValue === "string" && identityValue.trim() === "")) {
@@ -102,7 +103,7 @@ Deno.serve(async (req: Request): Promise<Response> => {
102
103
  Promise.resolve(supabase.rpc("cerefox_log_usage", {
103
104
  p_operation: "list_versions",
104
105
  p_access_path: "edge-function",
105
- p_requestor: body.requestor ?? null,
106
+ p_requestor: identityValue ?? null,
106
107
  p_document_id: document_id,
107
108
  p_result_count: (data ?? []).length,
108
109
  })).catch(() => {});
@@ -2,6 +2,7 @@ import "jsr:@supabase/functions-js/edge-runtime.d.ts";
2
2
  import { createClient } from "jsr:@supabase/supabase-js@2";
3
3
  import { isVersionRequest, versionResponse } from "../../../_shared/ef-meta/index.ts";
4
4
  import { efAuthGate } from "../../../_shared/ef-auth/index.ts";
5
+ import { callerIdentity } from "../../../_shared/mcp-tools/identity.ts";
5
6
 
6
7
  /**
7
8
  * cerefox-metadata — Supabase Edge Function
@@ -54,9 +55,9 @@ Deno.serve(async (req: Request): Promise<Response> => {
54
55
  const supabaseKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
55
56
  const supabase = createClient(supabaseUrl, supabaseKey);
56
57
 
57
- // Configurable requestor enforcement
58
- const identityField = "requestor";
59
- const identityValue = body[identityField];
58
+ // Configurable caller-identity enforcement: `author`, or `requestor` as the pre-1.13.2 alias (#244)
59
+ const identityField = "author";
60
+ const identityValue = callerIdentity(body as Record<string, unknown>);
60
61
  const { data: reqConfig } = await supabase.rpc("cerefox_get_config", { p_key: "require_requestor_identity" });
61
62
  if (reqConfig === "true") {
62
63
  if (!identityValue || (typeof identityValue === "string" && identityValue.trim() === "")) {
@@ -89,7 +90,7 @@ Deno.serve(async (req: Request): Promise<Response> => {
89
90
  Promise.resolve(supabase.rpc("cerefox_log_usage", {
90
91
  p_operation: "list_metadata_keys",
91
92
  p_access_path: "edge-function",
92
- p_requestor: body.requestor ?? null,
93
+ p_requestor: identityValue ?? null,
93
94
  p_result_count: (data ?? []).length,
94
95
  })).catch(() => {});
95
96
 
@@ -2,6 +2,7 @@ import "jsr:@supabase/functions-js/edge-runtime.d.ts";
2
2
  import { createClient } from "jsr:@supabase/supabase-js@2";
3
3
  import { isVersionRequest, versionResponse } from "../../../_shared/ef-meta/index.ts";
4
4
  import { efAuthGate } from "../../../_shared/ef-auth/index.ts";
5
+ import { callerIdentity } from "../../../_shared/mcp-tools/identity.ts";
5
6
  import { reviewWorkflowEnabled } from "../../../_shared/mcp-tools/feature-flags.ts";
6
7
 
7
8
  /**
@@ -110,9 +111,9 @@ Deno.serve(async (req: Request): Promise<Response> => {
110
111
  const supabaseKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
111
112
  const supabase = createClient(supabaseUrl, supabaseKey);
112
113
 
113
- // Configurable requestor enforcement
114
- const identityField = "requestor";
115
- const identityValue = body[identityField];
114
+ // Configurable caller-identity enforcement: `author`, or `requestor` as the pre-1.13.2 alias (#244)
115
+ const identityField = "author";
116
+ const identityValue = callerIdentity(body as Record<string, unknown>);
116
117
  const { data: reqConfig } = await supabase.rpc("cerefox_get_config", { p_key: "require_requestor_identity" });
117
118
  if (reqConfig === "true") {
118
119
  if (!identityValue || (typeof identityValue === "string" && identityValue.trim() === "")) {
@@ -157,7 +158,7 @@ Deno.serve(async (req: Request): Promise<Response> => {
157
158
  Promise.resolve(supabase.rpc("cerefox_log_usage", {
158
159
  p_operation: "metadata_search",
159
160
  p_access_path: "edge-function",
160
- p_requestor: body.requestor ?? null,
161
+ p_requestor: identityValue ?? null,
161
162
  p_query_text: JSON.stringify(metadata_filter),
162
163
  p_result_count: (data ?? []).length,
163
164
  p_project_id: project_id,
@@ -2,6 +2,7 @@ import "jsr:@supabase/functions-js/edge-runtime.d.ts";
2
2
  import { createClient } from "jsr:@supabase/supabase-js@2";
3
3
  import { isVersionRequest, versionResponse } from "../../../_shared/ef-meta/index.ts";
4
4
  import { efAuthGate } from "../../../_shared/ef-auth/index.ts";
5
+ import { callerIdentity } from "../../../_shared/mcp-tools/identity.ts";
5
6
  import { capEmbeddingInput } from "../../../_shared/embeddings/index.ts";
6
7
 
7
8
  /**
@@ -67,6 +68,8 @@ interface SearchRequest {
67
68
  min_score?: number;
68
69
  metadata_filter?: Record<string, string> | null;
69
70
  max_bytes?: number;
71
+ author?: string;
72
+ /** Pre-1.13.2 spelling of `author`; still accepted. */
70
73
  requestor?: string;
71
74
  }
72
75
 
@@ -266,9 +269,9 @@ Deno.serve(async (req: Request) => {
266
269
  const supabaseKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
267
270
  const supabase = createClient(supabaseUrl, supabaseKey);
268
271
 
269
- // Configurable requestor enforcement
270
- const identityField = "requestor";
271
- const identityValue = body[identityField as keyof SearchRequest] as string | undefined;
272
+ // Configurable caller-identity enforcement: `author`, or `requestor` as the pre-1.13.2 alias (#244)
273
+ const identityField = "author";
274
+ const identityValue = callerIdentity(body as unknown as Record<string, unknown>);
272
275
  const { data: reqConfig } = await supabase.rpc("cerefox_get_config", { p_key: "require_requestor_identity" });
273
276
  if (reqConfig === "true") {
274
277
  if (!identityValue || (typeof identityValue === "string" && identityValue.trim() === "")) {
@@ -377,7 +380,7 @@ Deno.serve(async (req: Request) => {
377
380
  Promise.resolve(supabase.rpc("cerefox_log_usage", {
378
381
  p_operation: "search",
379
382
  p_access_path: "edge-function",
380
- p_requestor: body.requestor ?? null,
383
+ p_requestor: identityValue ?? null,
381
384
  p_query_text: query,
382
385
  p_result_count: accepted.length,
383
386
  p_project_id: projectId,
@@ -149,14 +149,14 @@ cerefox search [OPTIONS] QUERY
149
149
  | `--metadata-filter <json>` (`-f`) | JSON | _none_ | JSONB metadata containment filter, e.g. `'{"type":"decision"}'`. |
150
150
  | `--max-bytes <n>` | int | `200000` | Response size budget in bytes. |
151
151
  | `--only-metadata` | flag | off | List matching docs (id, score, chunks, chars) without content — a compact listing. |
152
- | `--requestor <name>` (`-r`) | str | `CEREFOX_REQUESTOR_NAME` or `user` | Identity recorded in the usage log. |
152
+ | `--author <name>` (`-a`) | str | `CEREFOX_REQUESTOR_NAME`, then `CEREFOX_AUTHOR_NAME`, else `unknown` | Your name (agent or user), recorded in the usage log. `--requestor` (`-r`) still works as a hidden alias (v1.13.2). |
153
153
  | `--json` | flag | off | Machine-readable JSON output. |
154
154
 
155
155
  **Examples**:
156
156
  ```bash
157
157
  cerefox search "OAuth design"
158
158
  cerefox search "decisions" --metadata-filter '{"type":"decision-log"}' --match-count 5
159
- cerefox search "what we tried" --mode hybrid --requestor "claude-code"
159
+ cerefox search "what we tried" --mode hybrid --author "claude-code"
160
160
  cerefox search "design docs" --only-metadata
161
161
  ```
162
162
 
@@ -182,7 +182,7 @@ cerefox document get [OPTIONS] DOCUMENT_ID
182
182
  | Flag | Type | Default | Description |
183
183
  |---|---|---|---|
184
184
  | `--version-id <uuid>` | UUID | _none_ (current) | Archived version UUID — get from `cerefox document version list`. |
185
- | `--requestor <name>` (`-r`) | str | `CEREFOX_REQUESTOR_NAME` or `user` | Identity recorded in the usage log. |
185
+ | `--author <name>` (`-a`) | str | `CEREFOX_REQUESTOR_NAME`, then `CEREFOX_AUTHOR_NAME`, else `unknown` | Your name (agent or user), recorded in the usage log. `--requestor` (`-r`) still works as a hidden alias (v1.13.2). |
186
186
  | `--json` | flag | off | Machine-readable JSON output. |
187
187
 
188
188
  **Examples**:
@@ -394,7 +394,7 @@ cerefox document version list [OPTIONS] DOCUMENT_ID
394
394
 
395
395
  | Flag | Type | Default | Description |
396
396
  |---|---|---|---|
397
- | `--requestor TEXT` | str | `CEREFOX_REQUESTOR_NAME` or `user` | Identity recorded in the usage log. |
397
+ | `--author TEXT` (`-a`) | str | `CEREFOX_REQUESTOR_NAME`, then `CEREFOX_AUTHOR_NAME`, else `unknown` | Your name (agent or user), recorded in the usage log. `--requestor` (`-r`) still works as a hidden alias (v1.13.2). |
398
398
 
399
399
  **Output**: table with version number, created timestamp, source, chunk/char counts, and version UUID. Pass the UUID to `cerefox document get --version-id <uuid>` to retrieve the archived content.
400
400
 
@@ -429,7 +429,7 @@ cerefox project list [OPTIONS]
429
429
 
430
430
  | Flag | Type | Default | Description |
431
431
  |---|---|---|---|
432
- | `--requestor TEXT` | str | `CEREFOX_REQUESTOR_NAME` or `user` | Identity recorded in the usage log. |
432
+ | `--author TEXT` (`-a`) | str | `CEREFOX_REQUESTOR_NAME`, then `CEREFOX_AUTHOR_NAME`, else `unknown` | Your name (agent or user), recorded in the usage log. `--requestor` (`-r`) still works as a hidden alias (v1.13.2). |
433
433
 
434
434
  **MCP equivalent**: [`cerefox_list_projects`](../../AGENT_GUIDE.md).
435
435
 
@@ -498,7 +498,7 @@ cerefox metadata search --metadata-filter '<json>' [OPTIONS]
498
498
  | `--created-since TEXT` | ISO-8601 | _none_ | Documents created after this timestamp. |
499
499
  | `--limit INTEGER` | int | `10` | Max results. |
500
500
  | `--include-content` | flag | off | Include full document content (slower; subject to byte budget). |
501
- | `--requestor TEXT` | str | `CEREFOX_REQUESTOR_NAME` or `user` | Identity recorded in the usage log. |
501
+ | `--author TEXT` (`-a`) | str | `CEREFOX_REQUESTOR_NAME`, then `CEREFOX_AUTHOR_NAME`, else `unknown` | Your name (agent or user), recorded in the usage log. `--requestor` (`-r`) still works as a hidden alias (v1.13.2). |
502
502
 
503
503
  **Examples**:
504
504
  ```bash
@@ -526,13 +526,13 @@ cerefox audit list [OPTIONS]
526
526
  | Flag | Type | Default | Description |
527
527
  |---|---|---|---|
528
528
  | `--document-id TEXT` | UUID | _none_ | Filter to a single document. |
529
- | `--author TEXT` | str | _none_ | Filter by author name (exact match). |
529
+ | `--by-author TEXT` | str | _none_ | Filter: only entries written by this author name (exact match). Until v1.13.2 this filter was `--author`; `--author` is now the caller identity here, as on every other command. |
530
530
  | `--operation TEXT` | choice | _none_ | Filter by operation type: `create`, `update-content`, `update-metadata`, `insert`, `replace-section`, `delete-section`, `rename-section`, `delete`, `restore`, `status-change`, `archive`, `unarchive`, `config-change`, `project-create`, `project-edit`, `project-delete`. |
531
531
  | `--since TEXT` | ISO-8601 | _none_ | Lower bound on `created_at`. |
532
532
  | `--until TEXT` | ISO-8601 | _none_ | Upper bound on `created_at`. |
533
533
  | `--limit INTEGER` | int | `50` | Max rows. |
534
534
  | `--json` | flag | off | Emit one JSON object per line (for piping to `jq` / scripts). |
535
- | `--requestor TEXT` | str | `CEREFOX_REQUESTOR_NAME` or `user` | Identity recorded in the usage log. |
535
+ | `--author TEXT` (`-a`) | str | `CEREFOX_REQUESTOR_NAME`, then `CEREFOX_AUTHOR_NAME`, else `unknown` | Your name (agent or user), recorded in the usage log. `--requestor` (`-r`) still works as a hidden alias (v1.13.2). |
536
536
 
537
537
  **Examples**:
538
538
  ```bash
@@ -791,7 +791,7 @@ The CLI reads its own runtime config from environment (or `.env`). See [`configu
791
791
  |---|---|---|
792
792
  | `CEREFOX_AUTHOR_NAME` | `unknown` | Default for `--author` on `ingest` / `ingest-dir`. |
793
793
  | `CEREFOX_AUTHOR_TYPE` | `user` | Default for `--author-type`. |
794
- | `CEREFOX_REQUESTOR_NAME` | `user` | Default for `--requestor` on read commands. |
794
+ | `CEREFOX_REQUESTOR_NAME` | `user` | Default for `--author` on read commands (falls back to `CEREFOX_AUTHOR_NAME`). |
795
795
 
796
796
  Precedence: **CLI flag > env var > built-in default**.
797
797
 
@@ -811,18 +811,18 @@ Every MCP parameter has an exact-name CLI flag (kebab-cased). Short forms exist
811
811
 
812
812
  | MCP tool | CLI command |
813
813
  |---|---|
814
- | `cerefox_search(query, match_count, project_name, metadata_filter, author)` | `cerefox search "<q>" --match-count N --project-name <name> --metadata-filter '<json>' --requestor <name>` |
814
+ | `cerefox_search(query, match_count, project_name, metadata_filter, author)` | `cerefox search "<q>" --match-count N --project-name <name> --metadata-filter '<json>' --author <name>` |
815
815
  | `cerefox_ingest(title, content, project_name, metadata, update_if_exists, document_id, expected_content_hash, last_write_wins, source, author, author_type)` (file) | `cerefox document ingest <path> --title <t> --project-name <n> --metadata '<json>' --update-if-exists\|--document-id <uuid> --expected-content-hash <hash>\|--last-write-wins --source <s> --author <a> --author-type <t>` |
816
816
  | `cerefox_ingest(...)` (paste) | `printf '...' \| cerefox document ingest --paste --title "<t>"` (same flags) |
817
- | `cerefox_get_document(document_id, version_id, outline, author)` | `cerefox document get <id> --version-id <vid> --outline --requestor <name>` |
818
- | `cerefox_insert(document_id, text, position, anchor_heading, section_part, expected_content_hash, author)` | `cerefox document insert <id> -t <text\|-\|@file> -p <position> -a <anchor> --section-part <part> --expected-hash <hash> --requestor <name>` |
819
- | `cerefox_edit(document_id, operations, expected_content_hash, author)` | `cerefox document edit-parts <id> -o <json\|-\|@file> --expected-hash <hash> --requestor <name>` |
820
- | `cerefox_list_versions(document_id, author)` | `cerefox document version list <id> --requestor <name>` |
821
- | `cerefox_list_projects(author)` | `cerefox project list --requestor <name>` |
817
+ | `cerefox_get_document(document_id, version_id, outline, author)` | `cerefox document get <id> --version-id <vid> --outline --author <name>` |
818
+ | `cerefox_insert(document_id, text, position, anchor_heading, section_part, expected_content_hash, author)` | `cerefox document insert <id> -t <text\|-\|@file> -p <position> -a <anchor> --section-part <part> --expected-hash <hash> --author <name>` |
819
+ | `cerefox_edit(document_id, operations, expected_content_hash, author)` | `cerefox document edit-parts <id> -o <json\|-\|@file> --expected-hash <hash> --author <name>` |
820
+ | `cerefox_list_versions(document_id, author)` | `cerefox document version list <id> --author <name>` |
821
+ | `cerefox_list_projects(author)` | `cerefox project list --author <name>` |
822
822
  | `cerefox_set_document_projects(document_id, project_names, author)` | `cerefox document set-projects <id> <name...> --author <a> --author-type <t>` (or `--clear` to remove all) |
823
823
  | `cerefox_list_metadata_keys()` | `cerefox metadata keys` |
824
- | `cerefox_metadata_search(metadata_filter, project_name, updated_since, created_since, limit, include_content, author)` | `cerefox metadata search --metadata-filter '<json>' --project-name <n> --updated-since <iso> --created-since <iso> --limit N --include-content --requestor <name>` |
825
- | `cerefox_get_audit_log(document_id, by_author, operation, since, until, limit, author)` | `cerefox audit list --document-id <id> --author <a> --operation <op> --since <iso> --until <iso> --limit N --requestor <name>` |
824
+ | `cerefox_metadata_search(metadata_filter, project_name, updated_since, created_since, limit, include_content, author)` | `cerefox metadata search --metadata-filter '<json>' --project-name <n> --updated-since <iso> --created-since <iso> --limit N --include-content --author <name>` |
825
+ | `cerefox_get_audit_log(document_id, by_author, operation, since, until, limit, author)` | `cerefox audit list --document-id <id> --by-author <a> --operation <op> --since <iso> --until <iso> --limit N --author <name>` |
826
826
  | `cerefox_set_document_metadata(document_id, metadata, replace, author)` | `cerefox document set-metadata <id> --set key=value` (also `--remove key`, `--json '<json>'`, `--replace`) |
827
827
  | `cerefox_delete_document(document_id, expected_content_hash, reason, author)` | `cerefox document delete <id> --reason <text> --author <a> --author-type <t> --yes` (confirms interactively instead of requiring the hash) |
828
828
  | `cerefox_restore_document(document_id, reason, author)` | `cerefox document restore <id> --reason <text> --author <a> --author-type <t>` |
@@ -488,8 +488,11 @@ multi-agent setups.
488
488
  Since v1.13.1 every MCP tool takes the caller's identity as **`author`**, reads and
489
489
  writes alike; `requestor` (the pre-1.13.1 name on most tools) is still accepted as
490
490
  a silent alias. (On `cerefox_get_audit_log` the entries filter, formerly `author`,
491
- is now `by_author`.) The primitive Edge Functions used by GPT Actions keep their
492
- original body fields (`requestor` on reads, `author` on ingest).
491
+ is now `by_author`.) Since v1.13.2 the CLI (`--author` on every command,
492
+ `--requestor` a hidden alias, `--by-author` the audit-list filter) and the
493
+ primitive Edge Functions used by GPT Actions (`author` in every request body,
494
+ `requestor` an alias, `by_author` on `cerefox-get-audit-log`) follow the same
495
+ rule, so there is one name on every surface.
493
496
 
494
497
  You can optionally enforce caller identification so that tool calls must include
495
498
  an identity. Calls without one receive a JSON-RPC `-32602` error with a helpful