@inkeep/open-knowledge 0.10.0-beta.0 → 0.10.0-beta.10

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.
Files changed (80) hide show
  1. package/dist/assets/skills/discovery/SKILL.md +1 -1
  2. package/dist/assets/skills/packs/entity-vault/SKILL.md +1 -1
  3. package/dist/assets/skills/packs/knowledge-base/SKILL.md +4 -4
  4. package/dist/assets/skills/packs/software-lifecycle/SKILL.md +1 -1
  5. package/dist/assets/skills/packs/worldbuilding/SKILL.md +1 -1
  6. package/dist/assets/skills/packs/writing-pipeline/SKILL.md +1 -1
  7. package/dist/assets/skills/project/SKILL.md +98 -84
  8. package/dist/build-CcnIXJkq.mjs +1 -0
  9. package/dist/{build-CHU3LuiR.mjs → build-Dt6UA4sF.mjs} +2 -2
  10. package/dist/{chokidar-CblsKfnx.mjs → chokidar-BDjANGpQ.mjs} +3 -3
  11. package/dist/{chunk-BRGMQKJV-CUNiOrRZ.mjs → chunk-BRGMQKJV-CD9YvPOS.mjs} +2 -2
  12. package/dist/{chunk-HWBSOZZR-LxiTl7Bc.mjs → chunk-HWBSOZZR-Bw5kf-cE.mjs} +3 -3
  13. package/dist/{chunk-XHM67O4N-akBjDPaR.mjs → chunk-XHM67O4N-53eJIPK0.mjs} +2 -2
  14. package/dist/cli.mjs +50 -44
  15. package/dist/config-schema.json +0 -21
  16. package/dist/config.project.schema.json +0 -21
  17. package/dist/constants-BvbCi0i6.mjs +2 -0
  18. package/dist/{dist-CILctbXr.mjs → dist-2T4GwdDL.mjs} +160 -168
  19. package/dist/dist-Dvjp8LvP.mjs +1 -0
  20. package/dist/{flag-coverage-QT4N2Z44-BWjM15GF.mjs → flag-coverage-QT4N2Z44-C7XMLkDY.mjs} +2 -2
  21. package/dist/{gh-detect-DCIXLWuz.mjs → gh-detect-D4VxJyjK.mjs} +3 -3
  22. package/dist/{git-handle-BSAUg1L_-CUJRlGhd.mjs → git-handle-BSAUg1L_-Bn2h-fYS.mjs} +4 -4
  23. package/dist/git-handle-CvT4CaVU-HAzuhDq0.mjs +1 -0
  24. package/dist/index.mjs +1 -1
  25. package/dist/init-CVlHYzW5.mjs +1 -0
  26. package/dist/{init-CsAeoxGW.mjs → init-DlaoEXBr.mjs} +25 -25
  27. package/dist/{loader-DthGvoM_.mjs → loader-C4VPi9d_.mjs} +3 -3
  28. package/dist/loader-DbmtQhQv.mjs +1 -0
  29. package/dist/{lzma-BSHkPrAt.mjs → lzma-DMoteSiK.mjs} +2 -2
  30. package/dist/preview-Dt8CrVXL.mjs +1 -0
  31. package/dist/preview-zRqBPqrO.mjs +3 -0
  32. package/dist/public/assets/{ActivityModeContent-CZ3f6pXo.js → ActivityModeContent-BTxwOaje.js} +1 -1
  33. package/dist/public/assets/DocumentContext-C1-gHdel.js +61 -0
  34. package/dist/public/assets/{GraphPanel-OqhIPCR-.js → GraphPanel-Dp3M-faj.js} +9 -9
  35. package/dist/public/assets/SettingsDialogBody-DupssDRA.js +7 -0
  36. package/dist/public/assets/SourceEditor-CFGRquAW.js +2 -0
  37. package/dist/public/assets/config-validation-events-BehAiIxH.js +12 -0
  38. package/dist/public/assets/index-B3eQbsM3.css +1 -0
  39. package/dist/public/assets/index-BJbD0NuW.js +1915 -0
  40. package/dist/public/assets/keyboard-shortcuts-CWZRWNEB.js +1 -0
  41. package/dist/public/assets/prop-types-9nwL-Fhi.js +500 -0
  42. package/dist/public/assets/{target-navigation-intent-DQhKf-lZ.js → target-navigation-intent-BZC6opgP.js} +1 -1
  43. package/dist/public/assets/{typing-burst-detector-Bnt3kvRi.js → typing-burst-detector-DQhYN8C1.js} +3 -3
  44. package/dist/public/index.html +8 -8
  45. package/dist/{repair-launch-json-NFu_e3_F.mjs → repair-launch-json-CDDb3KUo.mjs} +2 -2
  46. package/dist/{repair-mcp-configs-Zi_XxyGY.mjs → repair-mcp-configs-BRGcJS3C.mjs} +2 -2
  47. package/dist/{repair-skills-8cPy8M95.mjs → repair-skills-D6H2pCdZ.mjs} +3 -3
  48. package/dist/repair-skills-DFYi31tf.mjs +1 -0
  49. package/dist/schemas/v0/config.project.schema.json +0 -21
  50. package/dist/{server-lock-BpjJj3OD-DBIqFE7p.mjs → server-lock-BpjJj3OD-ATm9AAM0.mjs} +89 -89
  51. package/dist/server-lock-CyhBidkz-CPWADvvb.mjs +1 -0
  52. package/dist/{sqlite3-56UMWEY3-D2ymLPIL.mjs → sqlite3-56UMWEY3-URg2-QXI.mjs} +1 -1
  53. package/dist/src-Daa-0xy9.mjs +7 -0
  54. package/dist/start-B_7A6Tzo.mjs +3 -0
  55. package/dist/start-DBND1J40.mjs +1 -0
  56. package/dist/{tar-ANUBEFJY-BajYpRKO.mjs → tar-ANUBEFJY-BBFrIW8E.mjs} +1 -1
  57. package/dist/write-project-skill-ClsrSTUT.mjs +2 -0
  58. package/package.json +1 -1
  59. package/dist/build-DxnNKCqf.mjs +0 -1
  60. package/dist/constants-DJXMedPd.mjs +0 -2
  61. package/dist/dist-jwEG_6T7.mjs +0 -1
  62. package/dist/git-handle-CvT4CaVU-BJSxTfe_.mjs +0 -1
  63. package/dist/init-jyYK5JFI.mjs +0 -1
  64. package/dist/loader-Hhqjtdao.mjs +0 -1
  65. package/dist/preview-BGlZsxSQ.mjs +0 -3
  66. package/dist/preview-CiM6px2S.mjs +0 -1
  67. package/dist/public/assets/DocumentContext-lPnCPfnx.js +0 -61
  68. package/dist/public/assets/SettingsDialogBody-lpcuh5h-.js +0 -7
  69. package/dist/public/assets/SourceEditor-CA6VzD8t.js +0 -2
  70. package/dist/public/assets/config-validation-events-CVUdGMqF.js +0 -12
  71. package/dist/public/assets/index-CYUEwyxk.css +0 -1
  72. package/dist/public/assets/index-D0Rrcm7t.js +0 -1915
  73. package/dist/public/assets/keyboard-shortcuts-AT0jBk2u.js +0 -1
  74. package/dist/public/assets/prop-types-N7Ts5qWw.js +0 -500
  75. package/dist/repair-skills-CTGGxNTm.mjs +0 -1
  76. package/dist/server-lock-CyhBidkz-DbELIlyl.mjs +0 -1
  77. package/dist/src-Djxmxob_.mjs +0 -7
  78. package/dist/start-BXQ2VEmg.mjs +0 -1
  79. package/dist/start-OWDBeL4F.mjs +0 -3
  80. package/dist/write-project-skill-Da4v8WUu.mjs +0 -2
@@ -3,7 +3,7 @@ name: open-knowledge-discovery
3
3
  description: "Read when the user asks what Open Knowledge is, wants to install it on a repository, wants to share an Open Knowledge project with collaborators, or asks how `ok init` / `ok install-skill` / OK Desktop set up a project. Do NOT load to perform Open Knowledge reads/writes — the runtime guidance for editing markdown inside an initialized OK project ships as a separate project-local skill at `.claude/skills/open-knowledge/` whenever `ok init` runs. If the user appears to be editing markdown inside a `.ok/` project and this is the only OK skill loaded, advise them to re-run `ok init` to install the project-local skill."
4
4
  compatibility: "Any agent host — no MCP server required. Pure discovery + install guidance."
5
5
  metadata:
6
- version: "0.10.0-beta.0"
6
+ version: "0.10.0-beta.10"
7
7
  author: "Inkeep"
8
8
  repository: "https://github.com/inkeep/open-knowledge"
9
9
  ---
@@ -45,4 +45,4 @@ This pack ships the Markdown half (folders + templates + this skill); OK is the
45
45
 
46
46
  ## Templates
47
47
 
48
- Create with `write_document({ template: "<name>", })`. Templates carry the structure (including the compiled-truth / `--- timeline ---` separator) plus short inline reminders at the point of use; this skill holds the full convention, so prefer it as the canonical reference if the two ever disagree.
48
+ Create with `write({ document: { path: "<path>", template: "<name>" } })`. Templates carry the structure (including the compiled-truth / `--- timeline ---` separator) plus short inline reminders at the point of use; this skill holds the full convention, so prefer it as the canonical reference if the two ever disagree.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: open-knowledge-pack-knowledge-base
3
- description: "How to work in a Knowledge Base project (the `knowledge-base` starter pack). Read when the project has the three-layer source-grounded layout — `external-sources/` → `research/` → `articles/` — wired to the ingest / research / consolidate MCP tools. Carries the pack's workflow, per-folder rules, status flows, and log discipline so this guidance does NOT live inside template bodies or log.md. Complements the platform `open-knowledge` skill; does not replace it."
3
+ description: "How to work in a Knowledge Base project (the `knowledge-base` starter pack). Read when the project has the three-layer source-grounded layout — `external-sources/` → `research/` → `articles/` — wired to the `workflow` MCP tool's ingest / research / consolidate kinds. Carries the pack's workflow, per-folder rules, status flows, and log discipline so this guidance does NOT live inside template bodies or log.md. Complements the platform `open-knowledge` skill; does not replace it."
4
4
  compatibility: "Claude Code, Claude Desktop, Claude Cowork, Claude.ai web. Requires Open Knowledge MCP server. Installed project-local by `ok seed --pack knowledge-base`."
5
5
  metadata:
6
6
  pack: "knowledge-base"
@@ -23,7 +23,7 @@ research/ provisional analysis (produced by `research`)
23
23
  articles/ canonical, decided knowledge (produced by `consolidate`)
24
24
  ```
25
25
 
26
- The loop is **ingest → research → consolidate**. Every downstream claim traces upstream to a preserved source. Cite local paths in `external-sources/`, never bare web URLs — the KB must survive link rot.
26
+ The loop is **ingest → research → consolidate** — each a `workflow({ kind })` guide you invoke (`workflow({ kind: 'ingest' })`, etc.). Every downstream claim traces upstream to a preserved source. Cite local paths in `external-sources/`, never bare web URLs — the KB must survive link rot.
27
27
 
28
28
  ## Per-folder rules
29
29
 
@@ -46,7 +46,7 @@ When a new article supersedes an older one, add the older article's path to the
46
46
 
47
47
  There is a `log.md` at the project root. **Append one dated entry after any turn that creates, edits, or restructures content** — one entry per turn, not per file. Silent edits break the audit trail.
48
48
 
49
- Log: `ingest` runs (new sources), `research` / `consolidate` runs (provisional or canonical articles), direct `write_document` / `edit_document` / rename / delete outside the three loop tools, `discover` runs, folder restructures, and `.ok/config.yml` changes.
49
+ Log: `ingest` runs (new sources), `research` / `consolidate` runs (provisional or canonical articles), direct `write` / `edit` / `move` / `delete` outside the three loop tools, `discover` runs, folder restructures, and `.ok/config.yml` changes.
50
50
 
51
51
  **Reference docs as markdown links, not bare paths** — `[path/to/doc](./path/to/doc.md)`, so the entry shows up in `links({ kind: "backlinks" })` for those docs. A bare path string does not register in the graph.
52
52
 
@@ -63,4 +63,4 @@ Entry shape:
63
63
 
64
64
  ## Templates
65
65
 
66
- Each folder has a starter template (`clip`, `research-log`, `article`). Create with `write_document({ template: "<name>", docName, })`. Templates carry only structure (headings + frontmatter scaffold) — the meaning of each field and section is described above, not repeated inside the document body.
66
+ Each folder has a starter template (`clip`, `research-log`, `article`). Create with `write({ document: { path, template: "<name>" } })`. Templates carry only structure (headings + frontmatter scaffold) — the meaning of each field and section is described above, not repeated inside the document body.
@@ -40,4 +40,4 @@ guides/ how-to / onboarding / runbooks (referenced throughout)
40
40
 
41
41
  ## Templates
42
42
 
43
- Create docs with `write_document({ template: "<name>", })`. Templates carry only structure (headings + frontmatter scaffold); what each section is for is described above, not repeated in the document body.
43
+ Create docs with `write({ document: { path, template: "<name>" } })`. Templates carry only structure (headings + frontmatter scaffold); what each section is for is described above, not repeated in the document body.
@@ -30,4 +30,4 @@ This project is a fiction encyclopedia. The graph is the product: characters, se
30
30
 
31
31
  ## Templates
32
32
 
33
- Create with `write_document({ template: "<name>", })`. Templates carry only structure; section meaning is described here, not inside the document body.
33
+ Create with `write({ document: { path, template: "<name>" } })`. Templates carry only structure; section meaning is described here, not inside the document body.
@@ -27,4 +27,4 @@ published/ shipped work; treat as immutable
27
27
 
28
28
  ## Templates
29
29
 
30
- Create with `write_document({ template: "<name>", })`. Templates carry only structure; section meaning lives here, not in the document body.
30
+ Create with `write({ document: { path, template: "<name>" } })`. Templates carry only structure; section meaning lives here, not in the document body.
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: open-knowledge
3
- description: "MUST invoke before reading or editing any `.md` / `.mdx` file, and before any `mcp__open-knowledge__*` tool call (`exec`, `search`, `write_document`, `edit_document`, and the rest). This skill is installed into the repository by `ok init`, so its presence alone means this is an Open Knowledge project — its runtime contract governs every markdown file here, with no need to probe for a `.ok/` directory. Authoritative agent-runtime contract; supersedes the overlapping MCP server `instructions` echo."
3
+ description: "MUST invoke before reading or editing any `.md` / `.mdx` file, and before any `mcp__open-knowledge__*` tool call (`exec`, `search`, `write`, `edit`, and the rest). This skill is installed into the repository by `ok init`, so its presence alone means this is an Open Knowledge project — its runtime contract governs every markdown file here, with no need to probe for a `.ok/` directory. Authoritative agent-runtime contract; supersedes the overlapping MCP server `instructions` echo."
4
4
  compatibility: "Claude Code, Claude Desktop, Claude Cowork, Claude.ai web. Requires Open Knowledge MCP server + code execution."
5
5
  metadata:
6
- version: "0.10.0-beta.0"
6
+ version: "0.10.0-beta.10"
7
7
  author: "Inkeep"
8
8
  repository: "https://github.com/inkeep/open-knowledge"
9
9
  ---
@@ -18,20 +18,27 @@ Open Knowledge (OK) is a markdown-CRDT collaboration platform exposed via MCP. T
18
18
  ## TL;DR — the 90% case
19
19
 
20
20
  1. **Reads:** `exec("cat …")` for a single doc, `exec("ls -A …")` for a directory (with folder defaults + template menu), `exec("grep …")` for literal, `search` for ranked retrieval. Native `Read` / `Grep` only on source code (`.ts` / `.py` / …), never on in-scope `.md` / `.mdx`.
21
- 2. **Writes:** `write_document` for new or full-replace, `edit_document` for body-only find/replace, `edit_frontmatter` for 1-2 frontmatter keys (JSON Merge Patch — preferred). Full frontmatter rewrites use `write_document({ position: "replace" })`. `edit_document` rejects frontmatter (HTTP 400). Pass a one-line `summary` (≤80 chars, user-facing outcome) on every content write — it's the timeline change-note (see §Writing).
22
- 3. **Preview:** every OK read/write response carries a route-only `previewUrl` (`/#/<doc>`, no host:port). If you have a `preview_*` tool, call `preview_start("open-knowledge-ui")`; if you have an in-app browser, call `get_preview_url` once for the full browser URL and navigate to it; on the Claude Code CLI (no browser tool), run `ok open <doc>` to open it in the OK Desktop app. Surface to the user on a `start-ui` warning (no UI running). Don't `preview_screenshot` to confirm edits — the CRDT tool response is the confirmation.
23
- 4. **Workflow tools** (`ingest` / `research` / `consolidate` / `discover`) return procedural guides, not data. Use them when the work fits the layer; follow their numbered steps.
21
+ 2. **Writes:** `write({ document: { path, content } })` for a new or full-replace doc; `edit({ document: { path, find, replace } })` for a body find/replace; `edit({ document: { path, frontmatter } })` for a frontmatter merge-patch (`null` deletes a key). `delete({ document })` removes, `move({ from, to })` moves/renames. Body find/replace is body-only — frontmatter goes through the `frontmatter` patch. Pass a one-line `summary` (≤80 chars, user-facing outcome) on every content write — it's the timeline change-note (see §Writing).
22
+ 3. **Preview:** every OK read/write response carries a route-only `previewUrl` (`/#/<doc>`, no host:port). If you have a `preview_*` tool, call `preview_start("open-knowledge-ui")`; if you have an in-app browser, call `preview_url` once for the full browser URL and navigate to it; on the Claude Code CLI (no browser tool), run `ok open <doc>` to open it in the OK Desktop app. Surface to the user on a `start-ui` warning (no UI running). Don't `preview_screenshot` to confirm edits — the CRDT tool response is the confirmation.
23
+ 4. **Workflow guides** — `workflow({ kind: 'ingest' | 'research' | 'consolidate' | 'discover' })` returns a procedural guide, not data. Use it when the work fits the layer; follow the numbered steps.
24
24
 
25
25
  Everything below is depth. Read on demand.
26
26
 
27
- ## Tool index — 23 tools
27
+ ## Tool index — 17 tools
28
28
 
29
29
  The full MCP surface, grouped by risk-level. Every tool's `kind` / `action` set is single-risk-level (never a read and a write behind one discriminator).
30
30
 
31
- - **Reads** — `exec` (primary; shell-style `cat`/`ls`/`grep`/`find` with frontmatter + backlink + history enrichment), `search` (ranked, BM25 + recency), `get_history` (versions for a doc), `links` (`kind: 'backlinks'|'forward'|'dead'|'orphans'|'hubs'|'suggest'`), `get_config` (resolved config), `get_components` (canonical component JSX schemas), `get_authoring_palette` (markdown-native authoring forms + themed `html preview` embed starters + theme tokens), `get_preview_url` (browser-reachable preview URL on demand), `share_link` (GitHub-substrate share URL for a doc or folder; read-only against `.git/`, no commits/pushes — returns a clear error when the project has no GitHub remote, since agents do not publish projects).
32
- - **Writes** — `write_document` (new or full-replace; supports `template:` instantiation), `edit_document` (body-only find/replace), `edit_frontmatter` (1-2 keys via RFC 7396 JSON Merge Patch — preferred), `delete_document`, `rename` (probes file vs folder; rewrites referrers), `version` (`action: 'save'|'rollback'`), `folder_config` (`action: 'set-rule'|'write-template'|'delete-template'`). `set-rule` writes a folder's own frontmatter (open-shape, like a doc's); `write-template`/`delete-template` manage the folder's templates (what new docs start with).
33
- - **GitHub-sync conflicts** `list_conflicts` (enumerate), `get_conflict_content` (base/ours/theirs stages + lifecycle), `resolve_conflict` (write a chosen resolution + commit; destructive). Mutating writes against a doc in conflict return RFC 9457 `urn:ok:error:doc-in-conflict` (409); `exec("cat …")` returns `lifecycle: {status, reason} | null` so you can detect the state proactively. See *Conflict-aware writes*.
34
- - **Workflow**`ingest`, `research`, `consolidate`, `discover` (return procedural guides, not data).
31
+ - **Reads** — `exec` (primary; shell-style `cat`/`ls`/`grep`/`find` with frontmatter + backlink + history enrichment), `search` (ranked, BM25 + recency), `history` (versions for a doc), `links` (`kind: 'backlinks'|'forward'|'dead'|'orphans'|'hubs'|'suggest'`, or an array of those for a one-call audit, e.g. `links({ kind: ["dead", "orphans", "hubs"] })`), `config` (resolved config), `palette` (markdown-native authoring forms + themed `html preview` embed starters + theme tokens; pass `components` for the canonical component JSX schemas), `preview_url` (browser-reachable preview URL on demand), `share_link` (GitHub-substrate share URL for a doc or folder; read-only against `.git/`, no commits/pushes — returns a clear error when the project has no GitHub remote, since agents do not publish projects).
32
+ - **Writes** — four native CRUD verbs, polymorphic over `document` / `folder` / `template` / `asset` (Pattern B: per-target fields nested inside the address key; pass EXACTLY ONE target):
33
+ - `write`create/overwrite a `document` (supports `document.template` instantiation), create a `folder` (with open-shape frontmatter), create a `template`, or upload an `asset`.
34
+ - `edit`modify a `document` (body find/replace OR frontmatter merge-patch), a `folder` (frontmatter merge-patch), or a `template`. (No asset — binary has no text body.)
35
+ - `delete` — remove a `document` (name or array), `folder`, `template`, or `asset`.
36
+ - `move` — move/rename a `document`, `folder`, or `asset`; rewrites referrers.
37
+ The OUTPUT mirrors the input: `write`/`edit`/`delete` nest their result under the same target key you passed (`{ document: {…} }` / `{ folder: {…} }` / `{ template: {…} }` / `{ asset: {…} }`, or `{ documents: [...] }` for a batch). The preview envelope (`previewUrl`, `warning`, `previousPreviewUrl`) stays at the top level — same for every tool.
38
+ Plus `checkpoint` (save a named version) and `restore_version` (roll a doc back to a prior version). A folder's own frontmatter is open-shape exactly like a doc's (self-only, does NOT cascade into child docs); templates are what new docs in a folder start with.
39
+ **Self-correcting on misuse:** the few constraints JSON Schema can't express — "pass exactly one target", "`find` needs a `replace`", body-XOR-frontmatter — are enforced by a *teaching error*: a wrong call returns `isError: true` with a one-line message naming the exact corrective shape. Read it and retry with that shape; don't guess.
40
+ - **GitHub-sync conflicts** — `conflicts` (`kind: 'list'` to enumerate, `kind: 'content'` for base/ours/theirs stages + lifecycle), `resolve_conflict` (write a chosen resolution + commit; destructive). Mutating writes against a doc in conflict return RFC 9457 `urn:ok:error:doc-in-conflict` (409); `exec("cat …")` returns `lifecycle: {status, reason} | null` so you can detect the state proactively. See *Conflict-aware writes*.
41
+ - **Workflow** — `workflow` (`kind: 'ingest' | 'research' | 'consolidate' | 'discover'`; returns procedural guides, not data).
35
42
 
36
43
  Tools NOT in OK MCP (they belong to your agent host): `preview_start`, `preview_screenshot`, `WebFetch`, `WebSearch`, native `Read` / `Grep` / `Glob` / `Edit`. The STOP rule below governs which of those you may use on in-scope markdown.
37
44
 
@@ -45,7 +52,7 @@ When this workspace has Open Knowledge MCP configured, do **not** use your host'
45
52
  - **Dispatching the Explore / general-purpose subagent for markdown-heavy exploration** — subagents use native `Read` / `Grep` / `Glob` internally and bypass Open Knowledge entirely. Do markdown exploration yourself via `exec` / `search`. Subagents remain appropriate for **source-code** exploration.
46
53
  - **Native `Read` / `Grep` on any in-scope markdown inside `.ok/`** — the `.ok/` directory is in-scope; if it carries `.md` / `.mdx`, treat those the same as any other knowledge-base file.
47
54
 
48
- Why: native tools skip frontmatter, backlinks, shadow-repo activity, and project git history that OK's tools return for every matched knowledge-base file. `exec` is the primary read surface; it runs read-only bash (`cat`, `ls`, `grep`, `find`, `head`, `tail`, `wc`, `sort`, `uniq`, `cut` — pipes OK) and returns raw stdout plus enriched metadata per file.
55
+ Why: native tools skip frontmatter, backlinks, shadow-repo activity, and project git history that OK's tools return for every matched knowledge-base file. `exec` is the primary read surface; it runs read-only bash (`cat`, `ls`, `grep`, `find`, `head`, `tail`, `wc`, `sort`, `uniq`, `cut`) and returns raw stdout plus enriched metadata per file. One command or a pipe (`|`) per call — it is NOT a shell, so `&&` / `;` / redirects are rejected; list several dirs with `ls -A a b c` or make separate calls.
49
56
 
50
57
  **MCP tool visibility — not seeing `exec` is NOT the escape hatch.** MCP wiring varies by client. Claude Code, Cursor, Codex, Windsurf, VS Code — each surfaces MCP differently. Server labels are user-defined; tools may not appear as top-level symbols named `exec` in your specific UI. If Open Knowledge is registered as an MCP server in this workspace, route markdown reads through its `exec` / `search` via your client's documented MCP invocation (including any generic "call MCP tool" flow). Registration is the test, not top-level-symbol visibility.
51
58
 
@@ -70,10 +77,10 @@ The user watches your edits land in a live browser preview. Open it once at sess
70
77
 
71
78
  **Pick how to open the preview by tool capability — not by host name.** Look at the tools actually available to you this session. If a tool can navigate to a URL, it counts as an in-app browser — match on the capability, not on what your host is called.
72
79
 
73
- - **You have `preview_*` tools** (e.g. `preview_start` + `preview_eval`) → **First open of the session:** to land directly on a doc, arm it first with `get_preview_url({ armPaneTarget: true, docName })` (or `folder`), then `preview_start("open-knowledge-ui")` — `ok ui` redirects the base-open straight to the armed route, so the pane opens on the doc, not root. Plain `preview_start` (no arm) opens at root. **Moving between docs once the pane is open: do it in one `preview_eval` step — set `window.location.hash` to the target's route fragment from the response `previewUrl`, the part from `#` on (e.g. `window.location.hash = '#/specs/foo/SPEC'`).** That drives the SPA router directly. Arm + `preview_start` only redirects a *fresh* open; it can't move an already-open pane (`preview_start` reuses the live process without reloading), so use `preview_eval` there. Don't read or edit `.claude/launch.json` — host-managed; the OK lock-collision proxy handles the UI-already-running case. If `preview_start` fails, report it; don't "fix" `launch.json`.
74
- - **No `preview_*` tool, but you have an in-app / built-in browser tool** — Codex's built-in browser, or any host tool that navigates to a URL (`browser`, `view_url`, `open_url`, `web.browse`, etc.) → call `get_preview_url` once for the **exact** target (`docName` for a doc, `folder` for a folder) and navigate your **in-app browser** straight to the returned `url`. Open that deep URL directly — never the root then navigate. Omit both args only for the root.
75
- - **Truly no browser-capable tool — if you have ANY tool that navigates to a URL, use the in-app branch above** (a pure stdio host with no URL-navigation tool at all, e.g. the Claude Code CLI) → for an "open `<doc>`/`<folder>`" request, run **`ok open <doc>`** (`--folder` for a folder) — opens the doc in OK Desktop via deep link (folders in the browser), with browser fallback; an action, not a URL to print. No `ok` on PATH or no shell → `get_preview_url`, then `open <url>` in the system browser as a last resort, and say so plainly. The system browser is the fallback, never the default.
76
- - **Honor `autoOpen`** (on `get_preview_url`, or on `warning` for write tools). If `false`, do not open or refresh any preview UI; surface the URL only if asked.
80
+ - **You have `preview_*` tools** (e.g. `preview_start` + `preview_eval`) → **First open of the session:** to land directly on a doc, arm it first with `preview_url({ armPaneTarget: true, document })` (or `folder`), then `preview_start("open-knowledge-ui")` — `ok ui` redirects the base-open straight to the armed route, so the pane opens on the doc, not root. Plain `preview_start` (no arm) opens at root. **Moving between docs once the pane is open: do it in one `preview_eval` step — set `window.location.hash` to the target's route fragment from the response `previewUrl`, the part from `#` on (e.g. `window.location.hash = '#/specs/foo/SPEC'`).** That drives the SPA router directly. Arm + `preview_start` only redirects a *fresh* open; it can't move an already-open pane (`preview_start` reuses the live process without reloading), so use `preview_eval` there. Don't read or edit `.claude/launch.json` — host-managed; the OK lock-collision proxy handles the UI-already-running case. If `preview_start` fails, report it; don't "fix" `launch.json`.
81
+ - **No `preview_*` tool, but you have an in-app / built-in browser tool** — Codex's built-in browser, or any host tool that navigates to a URL (`browser`, `view_url`, `open_url`, `web.browse`, etc.) → call `preview_url` once for the **exact** target (`document` for a doc, `folder` for a folder) and navigate your **in-app browser** straight to the returned `url`. Open that deep URL directly — never the root then navigate. Omit both args only for the root.
82
+ - **Truly no browser-capable tool — if you have ANY tool that navigates to a URL, use the in-app branch above** (a pure stdio host with no URL-navigation tool at all, e.g. the Claude Code CLI) → for an "open `<doc>`/`<folder>`" request, run **`ok open <doc>`** (`--folder` for a folder) — opens the doc in OK Desktop via deep link (folders in the browser), with browser fallback; an action, not a URL to print. No `ok` on PATH or no shell → `preview_url`, then `open <url>` in the system browser as a last resort, and say so plainly. The system browser is the fallback, never the default.
83
+ - **Honor `autoOpen`** (on `preview_url`, or on `warning` for write tools). If `false`, do not open or refresh any preview UI; surface the URL only if asked.
77
84
 
78
85
  **Opening or reading a file IS a preview navigation.** On any "open `<file>`" / "read `<file>`" request, navigate the browser to that doc's `previewUrl` route from the tool response — not a separate fetch, not a fresh system-browser launch.
79
86
 
@@ -81,12 +88,12 @@ The user watches your edits land in a live browser preview. Open it once at sess
81
88
 
82
89
  1. You opened/navigated earlier this session → don't reopen.
83
90
  2. Write response has `previewUrl` (non-null route) and NO `warning` → a browser is attached somewhere; do nothing.
84
- 3. `warning: { action: "attach-preview-once", previewUrl, message }` → UI reachable, no browser attached; navigate one-shot (`preview_start`, or `get_preview_url` → in-app browser).
91
+ 3. `warning: { action: "attach-preview-once", previewUrl, message }` → UI reachable, no browser attached; navigate one-shot (`preview_start`, or `preview_url` → in-app browser).
85
92
  4. `warning: { action: "start-ui", previewUrl: null, message }` → no UI running anywhere. Surface the message verbatim — recovery options are in the in-band copy. Don't loop on retries.
86
93
 
87
94
  Warnings fire at most once per session in the fresh-start case.
88
95
 
89
- **`previewUrl: null` only means "no UI reachable" on the three attach-warning tools: `write_document` / `edit_document` / `edit_frontmatter`.** Workflow tools return prose and don't carry `previewUrl`. `delete_document` / `rename` emit `previousPreviewUrl` (different field, for closing stale tabs) and don't fire attach warnings. `get_preview_url` reports `running: false` + `url: null` when no UI is running.
96
+ **`previewUrl: null` only means "no UI reachable" on the two attach-warning tools: `write` / `edit`.** Workflow tools return prose and don't carry `previewUrl`. `delete` / `move` emit `previousPreviewUrl` (different field, for closing stale tabs) and don't fire attach warnings. `preview_url` reports `running: false` + `url: null` when no UI is running.
90
97
 
91
98
  If you see `"Hocuspocus server is not running"`, run `ok start` and retry.
92
99
 
@@ -98,17 +105,19 @@ OK Electron and `ok ui` share `ui.lock`; when a second UI binds a different port
98
105
 
99
106
  ## Writing
100
107
 
101
- Call `write_document` / `edit_document` as soon as you have content. Native `Edit` / `sed` / direct `Write` on in-scope markdown is forbidden — it bypasses the CRDT and loses agent attribution in the shadow repo.
108
+ Call `write` / `edit` as soon as you have content. Native `Edit` / `sed` / direct `Write` on in-scope markdown is forbidden — it bypasses the CRDT and loses agent attribution in the shadow repo.
102
109
 
103
- **Pass a `summary` on every content write (SHOULD).** `write_document`, `edit_document`, and `edit_frontmatter` each take a one-line `summary` (≤80 chars) describing the user-facing outcome of the change — "Add gear list and permit info", not "edited trip doc". It renders as a bullet under your name in the document timeline and is the only human-readable change-note persisted to the shadow-repo history; omit it and the timeline shows *that* you wrote but not *what changed*. Write it from the reader's perspective, keep it specific, and avoid secrets or PII (it lands in git history). Each entry in the batch `docs:` form carries its own `summary`.
110
+ **Pass a `summary` on every content write (SHOULD).** `write`, `edit`, and `move` each take a one-line `summary` (≤80 chars) describing the user-facing outcome of the change — "Add gear list and permit info", not "edited trip doc". It renders as a bullet under your name in the document timeline and is the only human-readable change-note persisted to the shadow-repo history; omit it and the timeline shows *that* you wrote but not *what changed*. Write it from the reader's perspective, keep it specific, and avoid secrets or PII (it lands in git history). Each entry in the batch `documents:` form carries its own `summary`.
104
111
 
105
- **Content-divergence warning (Site A gate).** `write_document` and `edit_document` responses may include a content-divergence warning when the converged Y.Text doesn't match the bytes the payload composed to (concurrent peer left residue, or rare a primitive regression). The write still landed; on this signal, re-read the doc (`exec("cat <path>")`) to see what actually converged before continuing. Single-doc shape: `structuredContent.contentDivergence = { kind: "content-divergence", intendedBytes, actualBytes, byteDelta, hint }`. Batch shape: per-doc `structuredContent.documents[].contentDivergence` with the same fields. Distinct from the preview-attach `warning` field (`action: "attach-preview-once" | "start-ui"`) separate keys, can coexist.
112
+ **Reach for visual structure where it aids comprehension.** Default to the right OK primitive over flat prose: a Callout (`> [!NOTE]`) for a key caveat, a ` ```mermaid ` diagram for a process or relationship, a table for options or comparisons, an `html preview` chart for numbers. **Call the `palette` MCP tool as you draft** (and `palette({ components })` for a canonical's JSX schema) it returns copy-ready markdown-native forms, themed `html preview` embed starters, and the theme tokens, so the visual lands themed and in the content graph instead of hand-rolled. Don't decorate use a visual only when it carries the point better than prose would. Full catalog: §Components.
106
113
 
107
- To author an MDX doc (the KB renders MDX/JSX components), pass a `.mdx` `docName` on the create: `write_document({ docName: "guides/widget.mdx", markdown, position: "replace" })` lands `guides/widget.mdx`. A `.md` or extension-less `docName` lands `.md`. An existing doc keeps its on-disk extension regardless of the suffix you pass changing it in place isn't available via the MCP today.
114
+ **Content-divergence warning (Site A gate).** `write` and `edit` responses may include a content-divergence warning when the converged Y.Text doesn't match the bytes the payload composed to (concurrent peer left residue, or — rare — a primitive regression). The write still landed; on this signal, re-read the doc (`exec("cat <path>")`) to see what actually converged before continuing. Single-doc shape: `structuredContent.document.contentDivergence = { kind: "content-divergence", intendedBytes, actualBytes, byteDelta, hint }` (the result nests under the `document` target key, mirroring the input). Batch shape: per-doc `structuredContent.documents[].contentDivergence` with the same fields. Distinct from the preview-attach `warning` field (`action: "attach-preview-once" | "start-ui"`), which stays at the top level separate keys, can coexist.
108
115
 
109
- To delete a doc, call `delete_document` never `rm` / `unlink` / native `Bash` removal on in-scope markdown. The MCP path closes open agent sessions and unloads the doc from Hocuspocus before unlinking; native `rm` desynchronizes those. Deletion is irreversible — call `version({ action: "save" })` first if you may need to roll back (restore via `version({ action: "rollback" })`; list snapshots via `get_history`), and `links({ kind: "backlinks", docName })` first if you want to fix referrers that will become redlinks. To move or rename a doc instead of delete + rewrite, use `rename({ from, to })` — it auto-detects file vs folder and rewrites incoming references atomically.
116
+ To author an MDX doc (the KB renders MDX/JSX components), pass a `.mdx` path on the create: `write({ document: { path: "guides/widget.mdx", content, position: "replace" } })` lands `guides/widget.mdx`. A `.md` or extension-less path lands `.md`. An existing doc keeps its on-disk extension regardless of the suffix you pass changing it in place isn't available via the MCP today.
110
117
 
111
- **If `edit_document` returns "Text not found" on text you can verify exists on disk** (via `exec("cat …")`), the MCP session is likely stale (e.g., after a folder rename or server restart). Treat this as the escape-hatch trigger from the STOP block: prefix your next user-visible sentence with `Open Knowledge MCP unavailable:` and report the inconsistency. Don't loop on retries the symptom is structural, not transient.
118
+ To delete a doc, call `delete({ document })` never `rm` / `unlink` / native `Bash` removal on in-scope markdown. The MCP path closes open agent sessions and unloads the doc from Hocuspocus before unlinking; native `rm` desynchronizes those. Deletion is irreversible call `checkpoint()` first if you may need to roll back (it snapshots the whole project; afterwards restore the doc via `restore_version({ document, version })`, finding the `version` in `history`), and `links({ kind: "backlinks", document })` first if you want to fix referrers that will become redlinks. To move or rename a doc instead of delete + rewrite, use `move({ from, to })` — it auto-detects document vs folder vs asset and rewrites incoming references atomically.
119
+
120
+ **If `edit` returns "Text not found" on text you can verify exists on disk** (via `exec("cat …")`), the MCP session is likely stale (e.g., after a folder rename or server restart). Treat this as the escape-hatch trigger from the STOP block: prefix your next user-visible sentence with `Open Knowledge MCP unavailable:` and report the inconsistency. Don't loop on retries — the symptom is structural, not transient.
112
121
 
113
122
  ## Conflict-aware writes
114
123
 
@@ -119,23 +128,23 @@ Projects with GitHub sync enabled may carry docs in a merge-conflict state. The
119
128
  "type": "urn:ok:error:doc-in-conflict",
120
129
  "title": "Document is in conflict.",
121
130
  "status": 409,
122
- "detail": "The document is in a merge-conflict state. Call get_conflict_content + resolve_conflict before retrying.",
131
+ "detail": "The document is in a merge-conflict state. Call conflicts({ kind: 'content' }) + resolve_conflict before retrying.",
123
132
  "file": "notes/sso.md",
124
- "resolutionOptions": ["mine", "theirs", "content"]
133
+ "resolutionOptions": ["mine", "theirs", "content", "delete"]
125
134
  }
126
135
  ```
127
136
 
128
- The gate covers `write_document`, `edit_document`, `delete_document`, `rename`, `version` (`action: 'rollback'`), `folder_config` (`action: 'write-template'|'delete-template'`), and agent undo. You cannot route around it by writing content that byte-matches one of the merge stages — the gate refuses on lifecycle state, not on body equality.
137
+ The gate covers `write`, `edit`, `delete`, `move`, `restore_version`, and agent undo (the doc-CRDT write spine; template/folder ops are fs-direct). You cannot route around it by writing content that byte-matches one of the merge stages — the gate refuses on lifecycle state, not on body equality.
129
138
 
130
139
  **Detect proactively.** `exec("cat <path>.md")` always returns `lifecycle: {status, reason} | null` alongside the body. When `status === 'conflict'`, switch to the resolution flow before attempting any mutation.
131
140
 
132
141
  **Resolution flow.** Three tools compose:
133
142
 
134
- 1. `list_conflicts()` → enumerate every doc currently tracked in conflict.
135
- 2. `get_conflict_content({ file })` → fetch `base` / `ours` / `theirs` stages plus the doc's `lifecycleStatus`. `ours` reflects the live Y.Text (what the human user sees in the editor) when the doc is loaded server-side and is marker-free; falls back to `git show :2:<file>` otherwise (e.g. after an editor reopen seeded markers into Y.Text).
136
- 3. `resolve_conflict({ file, strategy, content? })` → write the chosen bytes and commit. Strategies: `mine` writes `git show :2:`, `theirs` writes `git show :3:`, `content` writes the bytes you supply.
143
+ 1. `conflicts({ kind: 'list' })` → enumerate every doc currently tracked in conflict.
144
+ 2. `conflicts({ kind: 'content', file })` → returns `{ content: { base, ours, theirs, shape, lifecycleStatus } }` (the result nests under the `content` kind key). `ours` reflects the live Y.Text (what the human user sees in the editor) when the doc is loaded server-side and is marker-free; falls back to `git show :2:<file>` otherwise (e.g. after an editor reopen seeded markers into Y.Text).
145
+ 3. `resolve_conflict({ file, strategy, content? })` → write the chosen bytes and commit. Strategies: `mine` runs `git checkout --ours` (your committed stage 2), `theirs` runs `git checkout --theirs` (their stage 3), `content` writes the bytes you supply, `delete` runs `git rm` (for delete-modify / modify-delete shapes where a stage is missing).
137
146
 
138
- `file` is a `.md` / `.mdx` path relative to the project dir (extension included) — mirrors the on-disk shape, not the extension-less `docName` used by other tools.
147
+ `file` is a `.md` / `.mdx` path relative to the project dir (extension included) — mirrors the on-disk shape, not the extension-less `document` path used by other tools.
139
148
 
140
149
  The resolve operation is best-effort and NOT atomic: `git checkout --ours/--theirs && git add` may succeed but the subsequent `git commit --no-edit` can fail (pre-commit hook rejection, locked index). On commit failure the staged files are re-`git add`-ed back into the unmerged index and the tracked entry remains in `conflicts.json` — re-call `resolve_conflict` after the user clears the blocker.
141
150
 
@@ -151,17 +160,17 @@ OK auto-promotes markdown-native syntax into themed canonical components at pars
151
160
  | Math | `$x$` inline, `$$…$$` block | KaTeX Math |
152
161
  | Inline a doc or asset | `![[file]]` | wiki embed |
153
162
 
154
- `Tabs` is the lone canonical with **no** markdown-native form — write the JSX directly (`<Tabs><Tab label="…">…</Tab></Tabs>`). For any canonical's full JSX prop schema, call `get_components({ ids })`. If no canonical fits, any `<TagName>…</TagName>` falls through as raw MDX — but prefer a canonical when one matches.
163
+ `Tabs` is the lone canonical with **no** markdown-native form — write the JSX directly (`<Tabs><Tab label="…">…</Tab></Tabs>`). For any canonical's full JSX prop schema, call `palette({ components: [ids] })`. If no canonical fits, any `<TagName>…</TagName>` falls through as raw MDX — but prefer a canonical when one matches.
155
164
 
156
- **Discover the palette in one call.** `get_authoring_palette` returns every markdown-native form (copy-ready `example` + `guidance`), the themed `html preview` embed starters, and the injected theme-token list — the source of truth for component-forward, themed authoring. Canonical names/counts beyond the markdown-native set are project-specific; the inventory in the `write_document` / `edit_document` descriptions and `get_components` are authoritative for those.
165
+ **Discover the palette in one call.** `palette` returns every markdown-native form (copy-ready `example` + `guidance`), the themed `html preview` embed starters, and the injected theme-token list — the source of truth for component-forward, themed authoring. Canonical names/counts beyond the markdown-native set are project-specific; the inventory in the `write` / `edit` descriptions and `palette({ components })` are authoritative for those.
157
166
 
158
- **Show findings, don't just tell them.** When a point is quantitative or comparative — a trend over time, a breakdown, a before/after, a ranking, a distribution — present it visually: a chart or stat-card `html preview` embed, a ` ```mermaid ` diagram, a table, or a Callout for the headline takeaway. Prose-only buries the insight. This matters most where the document's job is to make findings legible — **`research` reports and `consolidate` articles especially**, and any write-up meant to present results. A research article with three dense paragraphs of numbers should have been a chart. Reach for `get_authoring_palette` as you draft, not after.
167
+ **Show findings, don't just tell them.** When a point is quantitative or comparative — a trend over time, a breakdown, a before/after, a ranking, a distribution — present it visually: a chart or stat-card `html preview` embed, a ` ```mermaid ` diagram, a table, or a Callout for the headline takeaway. Prose-only buries the insight. This matters most where the document's job is to make findings legible — **`research` reports and `consolidate` articles especially**, and any write-up meant to present results. A research article with three dense paragraphs of numbers should have been a chart. Reach for `palette` as you draft, not after.
159
168
 
160
169
  ### `html preview` — themed interactive embeds
161
170
 
162
171
  A ` ```html preview ` fence (also `htm` / `xml`) renders a standalone HTML/CSS/JS page as a live sandboxed iframe — the extend-to-anything primitive for charts, stat cards, custom SVG, calculators, demos. The iframe auto-sizes to its content; pass `h=` / `w=` (e.g. ` ```html preview h=400px `) only to pin a fixed size.
163
172
 
164
- **Start from a starter — don't hand-roll.** `get_authoring_palette` returns `embedPatterns` (chart, stat cards, custom SVG, interactive control), each already wired to the theme tokens. Copy one and fill in your data — that is the only path that cannot render unthemed. Hand-author a fence from scratch only when no starter is close.
173
+ **Start from a starter — don't hand-roll.** `palette` returns `embedPatterns` (chart, stat cards, custom SVG, interactive control), each already wired to the theme tokens. Copy one and fill in your data — that is the only path that cannot render unthemed. Hand-author a fence from scratch only when no starter is close.
165
174
 
166
175
  **MUST — never hardcode colors in an `html preview` embed.** OK injects its theme tokens into every preview iframe; an embed that hardcodes hex / `rgb()` renders unthemed — a white box on a dark page, clashing with every component around it. This is the single most common embed mistake. Wire every color to a token: `var(--chart-1..5)` for chart series, `var(--foreground)` / `var(--muted-foreground)` for text, `var(--card)` / `var(--background)` for surfaces, plus `var(--border)`, `var(--primary)`, `var(--radius)`. Don't set a `body` background at all unless you specifically mean to — the iframe already carries a themed one.
167
176
 
@@ -189,6 +198,8 @@ const c1 = getComputedStyle(document.documentElement).getPropertyValue('--chart-
189
198
 
190
199
  **Boundary.** Reach for a canonical (via its markdown-native form) when one matches the semantic need — it is themed and integrated. Reach for ` ```html preview ` for interactive or bespoke content no canonical covers. ` ```<lang> ` fences for other languages are plain syntax-highlighted code, no preview.
191
200
 
201
+ **External resources load directly.** The preview iframe has open network access — an embed can load external stylesheets, `fetch` live data, pull map tiles / remote images, use web fonts, or embed third-party iframes over `https:`. A Leaflet map, a live-`fetch` chart, or a Google-Font embed renders with no extra setup. The iframe is a sandboxed null-origin frame, so an embed can reach the network but can never read the knowledge base, cookies, or auth. (`'unsafe-eval'` is not granted — Chart.js / Leaflet / Plotly don't need it; a library that compiles expression strings at runtime won't run.)
202
+
192
203
  ## Grounding — every factual claim needs a source (MUST)
193
204
 
194
205
  Knowledge-base docs are factual artifacts — whether the project is a wiki, an LLM brain, a spec collection, a research log, or anything else markdown-shaped. Every claim must be traceable, and **the source has to live inside the knowledge base**, not float on the public web.
@@ -213,7 +224,7 @@ Knowledge-base docs are factual artifacts — whether the project is a wiki, an
213
224
  - **Every link must resolve to a doc that exists.** Never link to a doc that isn't written yet. If you want to reference something that should have its own page but doesn't: create that page in the same pass, or record it as a tracked task (`TaskCreate`, or your host's task tool — if the host has none, tell the user) and leave the mention as plain prose. A broken link is debt, not a to-do marker.
214
225
  - **Never wrap a link in backticks.** `` `[text](./foo.md)` `` is a bug — the backticks make it render as literal code rather than a link.
215
226
  - **Never use HTML anchors** (`<a href="...">`). Markdown link syntax only.
216
- - **Verify before walking away.** After writing a doc, call `links({ kind: "dead", sourceDocNames: ["your/doc"] })` to find broken references. Fix or remove every one — a dead link is never acceptable to leave behind. Companion `links` kinds: `backlinks` (incoming), `forward` (outgoing), `orphans` (no incoming), `hubs` (high-incoming), `suggest` (untextualized mentions worth linking).
227
+ - **Verify before walking away.** After writing a doc, call `links({ kind: "dead", sourceDocuments: ["your/doc"] })` to find broken references. Fix or remove every one — a dead link is never acceptable to leave behind. Companion `links` kinds: `backlinks` (incoming), `forward` (outgoing), `orphans` (no incoming), `hubs` (high-incoming), `suggest` (untextualized mentions worth linking).
217
228
  - **The editor's red-underline visual lies.** Its dead-link detection tolerates slug-fallback (e.g., `foo` may appear resolved because `foo.md` exists at root). `links({ kind: "dead" })` is strict-exact — trust the tool, not the visual.
218
229
 
219
230
  **Note on wiki-link syntax (`[[Page]]`):** the parser still handles it for legacy content, but it's NO LONGER the recommended default. Write new content with standard markdown links per above. Seed-pack templates (`ok seed --pack <name>`) may still emit `[[Page]]` placeholders inside template body text — those are legacy. When you instantiate a seed-pack template, replace the legacy placeholders with standard markdown links during the `{shape}`-fill pass.
@@ -263,11 +274,11 @@ A doc's frontmatter is exactly its own on-disk YAML — folder frontmatter never
263
274
 
264
275
  ### Read the folder before writing (MUST)
265
276
 
266
- Before creating or editing docs in a folder, **always** call `exec("ls -A <folder>")` once. The response carries the folder's own `title`/`description`/`tags` + `templates_available` (the template menu for `write_document({ template })`). Skipping this is how agents land docs that violate folder discipline.
277
+ Before creating or editing docs in a folder, **always** call `exec("ls -A <folder>")` once. The response carries the folder's own `title`/`description`/`tags` + `templates_available` (the template menu for `write({ document: { template } })`). Skipping this is how agents land docs that violate folder discipline.
267
278
 
268
279
  Pre-write checklist:
269
280
 
270
- 0. **First-contact check.** If the folder has no frontmatter of its own AND `templates_available` is empty AND `exec("ls -A")` shows substantial content elsewhere, the project hasn't been onboarded — STOP and invoke `discover` (Workflow tools below). Skip on subsequent writes once confirmed.
281
+ 0. **First-contact check.** If the folder has no frontmatter of its own AND `templates_available` is empty AND `exec("ls -A")` shows substantial content elsewhere, the project hasn't been onboarded — STOP and invoke `workflow({ kind: 'discover' })` (Workflow guides below). Skip on subsequent writes once confirmed.
271
282
  1. **Read the folder's description** — its `title`/`description`/`tags` tell you what the folder is for. (These describe the folder; they are NOT defaults the doc inherits.)
272
283
  2. **Read `templates_available`** — each entry has `name`, `title`, `description`, `scope` (`local` / `inherited`). If one matches, **prefer it** over free-form markdown (it's the folder's contract — templates carry frontmatter + body structure hand-authored docs routinely miss).
273
284
  3. **Read recent siblings** — new docs should match the shape of existing ones (filename, frontmatter, body structure).
@@ -277,53 +288,55 @@ Pre-write checklist:
277
288
 
278
289
  ### When to use a template (MUST when one fits)
279
290
 
280
- Instantiate via `write_document({ template, docName, position: "replace" })`. Inherited templates (`scope: "inherited"`) are equally valid. Skip only when (a) `templates_available` is empty, (b) no entry matches, OR (c) the user asked for free-form. If you skip, briefly note why in chat.
291
+ Instantiate via `write({ document: { path, template } })`. Inherited templates (`scope: "inherited"`) are equally valid. Skip only when (a) `templates_available` is empty, (b) no entry matches, OR (c) the user asked for free-form. If you skip, briefly note why in chat.
281
292
 
282
293
  ### When to create a template
283
294
 
284
295
  Templates make folder structure durable. Create them proactively:
285
296
 
286
- - 2+ sibling docs share a skeleton in a folder with no template → extract via `folder_config({ action: "write-template" })`.
297
+ - 2+ sibling docs share a skeleton in a folder with no template → extract via `write({ template })`.
287
298
  - About to write a doc in a folder where no template fits, AND the shape is reusable → save as template the same turn.
288
- - Scaffolding a new folder for a doc category → pair the rule (`folder_config({ action: "set-rule" })`) with a template in the same turn.
299
+ - Scaffolding a new folder for a doc category → pair `write({ folder })` (or `edit({ folder })`) with `write({ template })` in the same turn.
289
300
  - The user describes a recurring doc shape ("we always log meetings with attendees, agenda, action items") → author the template once.
290
301
 
291
302
  Note new templates in chat ("saved as a template at `meetings/.ok/templates/prep-notes.md` for next time") so the user sees the discipline grew.
292
303
 
304
+ **Keep starter content clean (MUST).** A template body is a reusable skeleton, not a meta-prompt: section headings, real frontmatter, and SHORT `{Stub}` placeholders (e.g. `# {Meeting Title}`). Do NOT bake a workflow's verbose `{...}` prompt-paragraphs (the `research` / `consolidate` shape guidance is for filling ONE doc, not for persisting into every new one), do NOT duplicate sections, and do NOT save a half-filled or in-progress doc as a template. Long "how to fill this" guidance belongs in the folder description, not in the body each new doc inherits. After saving, `exec("cat <folder>/.ok/templates/<name>.md")` and eyeball it — a template propagates to every doc made from it, so a garbled one is a recurring defect, not a one-off.
305
+
293
306
  ### When recurring per-doc properties emerge (MUST when a pattern emerges)
294
307
 
295
- If you're writing the same frontmatter (tags, status, a title prefix) on multiple siblings, bake those starting values into a **template** (`folder_config({ action: "write-template" })`) — that's the single mechanism for new-doc starting properties. Folder frontmatter does not cascade values into docs.
308
+ If you're writing the same frontmatter (tags, status, a title prefix) on multiple siblings, bake those starting values into a **template** (`write({ template })`) — that's the single mechanism for new-doc starting properties. Folder frontmatter does not cascade values into docs.
296
309
 
297
310
  ### Editing a folder's own description
298
311
 
299
312
  ```ts
300
- folder_config({
301
- action: "set-rule",
302
- rules: [
303
- { match: "meetings/**", frontmatter: { title: "Meetings", description: "Meeting notes", tags: ["meeting"] } },
304
- ],
313
+ edit({
314
+ folder: {
315
+ path: "meetings",
316
+ frontmatter: { title: "Meetings", description: "Meeting notes", tags: ["meeting"] },
317
+ },
305
318
  })
306
319
  ```
307
320
 
308
- `frontmatter` is open-shape — any key about the folder itself, exactly like a doc's frontmatter (`title` / `description` / `tags` are conventional keys the UI surfaces). It's self-only: it describes the folder and does NOT flow into child docs — put per-doc starting values in a template instead. Each `match` resolves to a SINGLE target folder. Multi-folder globs (`specs/*/evidence/**`) are rejected with `MULTI_FOLDER_GLOB` split per folder. Remove a rule by passing empty `frontmatter: {}` — file deletes and `.ok/` auto-cleans if no other tenant remains.
321
+ `frontmatter` is open-shape — any key about the folder itself, exactly like a doc's frontmatter (`title` / `description` / `tags` are conventional keys the UI surfaces). It's self-only: it describes the folder and does NOT flow into child docs — put per-doc starting values in a template instead. Each call targets a SINGLE folder by its own `path` (no globs). Use `write({ folder })` to create a new folder, `edit({ folder })` to change an existing one (merge-patch). Clear the folder's frontmatter by passing `frontmatter: {}`, or drop one key with `frontmatter: { key: null }` — the file deletes when empty and `.ok/` auto-cleans if no other tenant remains.
309
322
 
310
323
  ### Creating templates
311
324
 
312
325
  ```ts
313
- folder_config({
314
- action: "write-template",
315
- folder: "meetings/",
316
- name: "prep-notes",
317
- body: "# {Meeting Title}\n\n**Attendees:** \n**Date:** \n\n## Agenda\n- \n",
318
- frontmatter: {
319
- title: "Meeting Prep Notes", // REQUIREDTEMPLATE_TITLE_REQUIRED if missing
320
- description: "Use before a meeting.", // recommended — soft warning if absent
321
- tags: ["meeting", "prep"],
326
+ write({
327
+ template: {
328
+ path: "meetings/prep-notes",
329
+ content: "# {Meeting Title}\n\n**Attendees:** \n**Date:** \n\n## Agenda\n- \n",
330
+ frontmatter: {
331
+ title: "Meeting Prep Notes", // REQUIRED — TEMPLATE_TITLE_REQUIRED if missing
332
+ description: "Use before a meeting.", // recommendedsoft warning if absent
333
+ tags: ["meeting", "prep"],
334
+ },
322
335
  },
323
336
  })
324
337
  ```
325
338
 
326
- **Substitution allowlist:** template bodies MAY use exactly two server-side substitutions — `{{date}}` (today's ISO-8601 date) and `{{user}}` (calling principal display name). Other `{{...}}` tokens are rejected at write time with `TEMPLATE_UNKNOWN_VARIABLE`. Plain `{shape}` placeholders (e.g., `{Meeting Title}`) are LITERAL — agents fill via subsequent `edit_document` calls. Delete a template via `folder_config({ action: "delete-template", folder, name })` (auto-cleans empty `.ok/templates/` and `.ok/`).
339
+ **Substitution allowlist:** template bodies MAY use exactly two server-side substitutions — `{{date}}` (today's ISO-8601 date) and `{{user}}` (calling principal display name). Other `{{...}}` tokens are rejected at write time with `TEMPLATE_UNKNOWN_VARIABLE`. Plain `{shape}` placeholders (e.g., `{Meeting Title}`) are LITERAL — agents fill via subsequent `edit` calls. Delete a template via `delete({ template: { path } })` (auto-cleans empty `.ok/templates/` and `.ok/`).
327
340
 
328
341
  ### Creating a doc from a template
329
342
 
@@ -332,21 +345,22 @@ folder_config({
332
345
  exec("ls -A meetings/")
333
346
  // → templates_available: [{ name: "prep-notes", title: "Meeting Prep Notes", scope: "local" }, ...]
334
347
 
335
- // Instantiate. `template` and `markdown` are mutually exclusive.
336
- write_document({
337
- docName: "meetings/2026-05-02-roadmap-sync",
338
- template: "prep-notes",
339
- position: "replace",
348
+ // Instantiate. `template` and `content` are mutually exclusive.
349
+ write({
350
+ document: {
351
+ path: "meetings/2026-05-02-roadmap-sync",
352
+ template: "prep-notes",
353
+ },
340
354
  })
341
355
 
342
- // Fill the `{shape}` placeholders via follow-up edit_document calls.
356
+ // Fill the `{shape}` placeholders via follow-up edit calls.
343
357
  ```
344
358
 
345
- Templates resolve via leaf → root walk-up at the target's parent folder, closest-wins on filename collision. **`template` and `markdown` are mutually exclusive** — passing both errors with `TEMPLATE_AND_MARKDOWN_BOTH_SET`. Substitution happens at instantiation time only; templates on disk show the raw `{{date}}` token.
359
+ Templates resolve via leaf → root walk-up at the target's parent folder, closest-wins on filename collision. **`template` and `content` are mutually exclusive** — passing both errors with `TEMPLATE_AND_CONTENT_BOTH_SET`. Substitution happens at instantiation time only; templates on disk show the raw `{{date}}` token.
346
360
 
347
361
  ### Editing frontmatter
348
362
 
349
- `edit_document` does NOT change frontmatter (body-only; frontmatter-intersecting find/replace returns HTTP 400). For single-key edits, prefer `edit_frontmatter({ docName, patch: { key: value } })` — JSON Merge Patch (RFC 7396), `null` deletes, field-level CRDT merge, atomic per-call. For full rewrites (≥3-5 keys, or body + frontmatter together), call `write_document({ position: "replace", markdown })` and include the new YAML block.
363
+ `edit({ document: { path, find, replace } })` does NOT change frontmatter (body-only; frontmatter-intersecting find/replace returns HTTP 400). For metadata, use `edit({ document: { path, frontmatter: { key: value } } })` — JSON Merge Patch (RFC 7396), `null` deletes, field-level CRDT merge, atomic per-call. For a full rewrite (body + frontmatter together), call `write({ document: { path, content, frontmatter, position: "replace" } })`.
350
364
 
351
365
  ### Binary-source wrappers (`ingest`-produced)
352
366
 
@@ -398,7 +412,7 @@ The skill carries the trigger ("KB content changed this turn — go look"). The
398
412
  | Read an individual doc | `Read: specs/foo/SPEC.md` | `exec("cat specs/foo/SPEC.md")` |
399
413
  | Explore a markdown-heavy dir | `Agent(Explore): "..."` | Do `exec`-based exploration yourself |
400
414
  | Wait for the server to tell you to open preview | Skip the session-start preview open and wait for the `attach-preview-once` hint | Open the preview browser at session start; the hint is a fallback when you didn't |
401
- | Ignore the attach hint | Skip the `warning: { action: "attach-preview-once" }` hint in write-tool responses | Open the preview when the hint fires (`preview_start`, or `get_preview_url`); otherwise do nothing |
415
+ | Ignore the attach hint | Skip the `warning: { action: "attach-preview-once" }` hint in write-tool responses | Open the preview when the hint fires (`preview_start`, or `preview_url`); otherwise do nothing |
402
416
  | Make the Claude Code Desktop preview work | Read / diagnose / edit `.claude/launch.json` (host-managed config) | Call `preview_start("open-knowledge-ui")` and nothing else; the OK lock-collision proxy bridges any port mismatch transparently |
403
417
  | Open a doc in the app from the Claude Code CLI | Print the `previewUrl` / `openknowledge://` string for the user to click | Run `ok open <doc>` — it opens the OK Desktop app (folders open in the browser), with browser fallback |
404
418
  | Reference another doc | `` `[text](./page.md)` `` (backticked) or HTML `<a>` | `[text](./page.md)` (raw markdown) |
@@ -407,31 +421,31 @@ The skill carries the trigger ("KB content changed this turn — go look"). The
407
421
  | Cite a web source you just fetched | inline `[source](https://...)` because YOU did the fetch (not the user) | `ingest` it — agent-initiated fetches are not exempt from the closed-loop rule |
408
422
  | Finish a turn that changed KB content | move on without checking for a log | check for a `log.md` and follow its contract per Log discipline |
409
423
  | Add an image | empty alt `![](./x.png)` or generic alt `![image](./x)` | meaningful alt + source caption below |
410
- | Catalog folder contents | create `INDEX.md` hub file | `folder_config({ action: "set-rule", rules: [...] })` writes `<folder>/.ok/frontmatter.yml` |
411
- | Write a doc in an unfamiliar folder | go straight to `write_document` with hand-authored markdown | `exec("ls -A <folder>")` first — read the folder description + `templates_available` before writing |
412
- | Land in an existing repo without orienting | go straight to `write_document` when no folder frontmatter / templates exist | invoke `discover` once for the project — extracts conventions from siblings, sets folder frontmatter + templates, activates the link graph |
413
- | Author a doc when a matching template exists | `write_document({ markdown: "..." })` from scratch | `write_document({ template, position: "replace" })` — templates carry the folder's frontmatter + body discipline |
414
- | Change a doc's title / tags | `edit_document` to swap the YAML (rejected — HTTP 400 frontmatter-intersect) | `edit_frontmatter({ docName, patch })` for 1-2 keys; `write_document({ position: "replace", markdown })` for full rewrites |
415
- | Repeat the same frontmatter on sibling docs | hand-set identical `tags` / `title` prefix on every new file | `folder_config({ action: "write-template" })` once — new docs start from the template |
416
- | Re-derive the same body skeleton repeatedly | copy-paste the structure from a sibling each time | `folder_config({ action: "write-template" })` once, then pick from `templates_available` thereafter |
417
- | Scaffold a new folder for a doc category | set folder rule for frontmatter and stop there | pair `folder_config({ action: "set-rule" })` with `folder_config({ action: "write-template" })` in the same turn |
418
- | Delete a markdown doc | `Bash: rm` / `unlink` / native deletion on in-scope `.md` | `delete_document` — `version({ action: "save" })` first if rollback may be needed |
424
+ | Catalog folder contents | create `INDEX.md` hub file | `edit({ folder: { path, frontmatter } })` writes `<folder>/.ok/frontmatter.yml` |
425
+ | Write a doc in an unfamiliar folder | go straight to `write` with hand-authored markdown | `exec("ls -A <folder>")` first — read the folder description + `templates_available` before writing |
426
+ | Land in an existing repo without orienting | go straight to `write` when no folder frontmatter / templates exist | invoke `workflow({ kind: 'discover' })` once for the project — extracts conventions from siblings, sets folder frontmatter + templates, activates the link graph |
427
+ | Author a doc when a matching template exists | `write({ document: { path, content: "..." } })` from scratch | `write({ document: { path, template } })` — templates carry the folder's frontmatter + body discipline |
428
+ | Change a doc's title / tags | `edit({ document: { path, find, replace } })` to swap the YAML (rejected — HTTP 400 frontmatter-intersect) | `edit({ document: { path, frontmatter } })` for metadata; `write({ document: { path, content, frontmatter, position: "replace" } })` for full rewrites |
429
+ | Repeat the same frontmatter on sibling docs | hand-set identical `tags` / `title` prefix on every new file | `write({ template })` once — new docs start from the template |
430
+ | Re-derive the same body skeleton repeatedly | copy-paste the structure from a sibling each time | `write({ template })` once, then pick from `templates_available` thereafter |
431
+ | Scaffold a new folder for a doc category | set folder frontmatter and stop there | pair `edit({ folder })` with `write({ template })` in the same turn |
432
+ | Delete a markdown doc | `Bash: rm` / `unlink` / native deletion on in-scope `.md` | `delete({ document })` — `checkpoint()` first if rollback may be needed |
419
433
  | Fork a skill and expect no stomp | Edit installed SKILL.md | `npx skills remove` before CLI upgrade |
420
434
 
421
435
  ## Workflow tools — when to invoke them
422
436
 
423
- Four MCP tools build on the primitives above. **They return *procedural guidance* (a multi-step instructional body), not fetched data.** Calling `ingest("https://…")` does not download and write a doc for you — it returns a multi-step plan you then execute. Same for `research` / `consolidate` / `discover`. Plan to follow the numbered steps in order; don't skip the STOP gates.
437
+ One MCP tool `workflow` — builds on the primitives above, dispatched on `kind`. **It returns *procedural guidance* (a multi-step instructional body), not fetched data.** Calling `workflow({ kind: 'ingest', source: "https://…" })` does not download and write a doc for you — it returns a multi-step plan you then execute. Same for the `research` / `consolidate` / `discover` kinds. Plan to follow the numbered steps in order; don't skip the STOP gates.
424
438
 
425
- Three correspond to [Karpathy's three-layer knowledge-base pattern](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) (`ingest` / `research` / `consolidate`); the fourth (`discover`) operates at the project-metadata layer and is the brownfield counterpart to the greenfield `ok seed` CLI:
439
+ Three kinds correspond to [Karpathy's three-layer knowledge-base pattern](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) (`ingest` / `research` / `consolidate`); the fourth (`discover`) operates at the project-metadata layer and is the brownfield counterpart to the greenfield `ok seed` CLI:
426
440
 
427
- | Tool | Layer | When to invoke |
441
+ | `kind` | Layer | When to invoke (via `workflow({ kind })`) |
428
442
  | ------------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
429
443
  | `ingest` | Raw sources (immutable) | User shares a URL/PDF/file to preserve verbatim, **OR you fetched a URL** (`WebFetch` / `WebSearch` / equivalent) to ground a claim that's about to land in the knowledge base. The KB is closed-loop — agent-initiated fetches are not exempt. **Binary sources** (PDFs, images, audio, Office docs) are preserved verbatim, not text-scraped — the tool body documents the binary-vs-text classification, write-path STOP gates (executable, size, scheme), re-ingest semantics, and shell-less fallback. No analysis in the file itself — takeaways go back to the user in chat. |
430
444
  | `research` | KB, provisional | User asks you to investigate, compare alternatives, or synthesize multiple sources. Produces a `status: provisional` article with a `sources:` list. Follows scan-first routing, a STOP scoping gate, 3P-external framing, and a validate checklist — the tool body enforces each step. |
431
445
  | `consolidate` | KB, canonical | Team has actually decided after research and wants the outcome committed as source-of-truth. Starts with a STOP gate confirming the decision exists; writes a `status: canonical` article with a `supersedes:` chain. |
432
446
  | `discover` | Project metadata | First arrival at a repo with existing content AND no folder frontmatter / templates set. Extracts conventions from siblings; activates the link graph (orphans, hubs, untextualized mentions); proposes folder frontmatter + templates + `.okignore`; per-phase user confirmation. Phases 1-4 run fs-direct; Phase 5 (link-graph activation) needs `ok start` (Phase 5 step 0 gates). Skip on empty repos (use `ok seed`). One-shot; idempotent on re-run. |
433
447
 
434
- **These tools are your default move, not `write_document`.** When the work fits one of the three layers — preserving an external source, investigating/synthesizing, committing a decided outcome — invoke the corresponding tool instead of going straight to `write_document` / `edit_document`. The tool bodies enforce framing (sources, status, supersedes chains) that hand-written articles routinely miss. `write_document` is correct for everything that does **not** fit the three layers (specs, runbooks, scratch notes, project pages); for the three that do, lead with the tool. This is doubly true in projects that ran `ok seed` — a doc landing in `external-sources/` / `research/` / `articles/` should have come out of `ingest` / `research` / `consolidate`.
448
+ **These tools are your default move, not `write`.** When the work fits one of the three layers — preserving an external source, investigating/synthesizing, committing a decided outcome — invoke the corresponding tool instead of going straight to `write` / `edit`. The tool bodies enforce framing (sources, status, supersedes chains) that hand-written articles routinely miss. `write` is correct for everything that does **not** fit the three layers (specs, runbooks, scratch notes, project pages); for the three that do, lead with the tool. This is doubly true in projects that ran `ok seed` — a doc landing in `external-sources/` / `research/` / `articles/` should have come out of `ingest` / `research` / `consolidate`.
435
449
 
436
450
  Typical day-2 flow: user shares a URL → `ingest` (preserve) → user asks "now research this" → `research` (provisional article + `ingest`s more sources as needed) → decision lands → `consolidate` (canonical article, supersedes the research).
437
451
 
@@ -439,18 +453,18 @@ Typical day-2 flow: user shares a URL → `ingest` (preserve) → user asks "now
439
453
 
440
454
  **Do not chain silently.** After `ingest`, ask the user whether to proceed to `research`. After `research`, let the user decide whether the findings are ready to `consolidate`. Each tool completes on its own terms — the user drives the transitions.
441
455
 
442
- **Repeat invocations.** Workflow tools return their full instructional body on every call, including 2nd / 3rd / Nth invocation in the same session. If you've already received a tool's body earlier this session, you can skim the repeat for changes (the body can evolve across server versions) but you don't need to re-internalize it — proceed to the next step with the new arguments.
456
+ **Repeat invocations.** The `workflow` tool returns its full instructional body on every call, including 2nd / 3rd / Nth invocation in the same session. If you've already received a tool's body earlier this session, you can skim the repeat for changes (the body can evolve across server versions) but you don't need to re-internalize it — proceed to the next step with the new arguments.
443
457
 
444
- **Project scaffolding — two paths.** **Empty repo:** run `ok seed` once from a terminal (scaffolds Karpathy three-layer + seeds `log.md` + registers folder defaults). **Existing content:** invoke `discover` (MCP tool, table above — extracts conventions from siblings, sets folder frontmatter + templates, curates `.okignore`, activates link graph; per-phase confirmation gates). Neither is required; the four workflow tools work against any folder structure. Only mention each when explicitly relevant.
458
+ **Project scaffolding — two paths.** **Empty repo:** run `ok seed` once from a terminal (scaffolds Karpathy three-layer + seeds `log.md` + registers folder defaults). **Existing content:** invoke `workflow({ kind: 'discover' })` (table above — extracts conventions from siblings, sets folder frontmatter + templates, curates `.okignore`, activates link graph; per-phase confirmation gates). Neither is required; the four workflow kinds work against any folder structure. Only mention each when explicitly relevant.
445
459
 
446
460
  ## Server lifecycle
447
461
 
448
- If `write_document` or `edit_document` returns a "Hocuspocus server is not running" error, start it with `ok start` (via Bash) and retry. Never fall back to native `Edit` / `Write` for in-scope markdown — always route through the MCP write tools so edits go through the CRDT with proper attribution.
462
+ If `write` or `edit` returns a "Hocuspocus server is not running" error, start it with `ok start` (via Bash) and retry. Never fall back to native `Edit` / `Write` for in-scope markdown — always route through the MCP write tools so edits go through the CRDT with proper attribution.
449
463
 
450
464
  ## Scope recap
451
465
 
452
- Open Knowledge looks for documents under the resolved `content.dir` (discoverable at runtime via `get_config({ path: ['content', 'dir'] })`). `.gitignore` and `.okignore` (at the project root and at any folder depth) define exclusions. A folder's own metadata + templates live in nested `<folder>/.ok/frontmatter.yml` + `<folder>/.ok/templates/` — NOT in `.ok/config.yml`.
466
+ Open Knowledge looks for documents under the resolved `content.dir` (discoverable at runtime via `config({ key: 'content.dir' })`). `.gitignore` and `.okignore` (at the project root and at any folder depth) define exclusions. A folder's own metadata + templates live in nested `<folder>/.ok/frontmatter.yml` + `<folder>/.ok/templates/` — NOT in `.ok/config.yml`.
453
467
 
454
468
  Default mental model (no jargon): **every `.md` and `.mdx` under `content.dir`** not excluded by `.gitignore` or `.okignore` is an Open Knowledge document — including under `specs/`, `reports/`, `docs/`, etc. Read `.okignore` (and any nested `.okignore` files) once per turn to know what's excluded.
455
469
 
456
- **First session in this project?** If substantial folders have no frontmatter of their own and no `templates_available`, the project isn't onboarded — invoke `discover` (Workflow tools table) before writing.
470
+ **First session in this project?** If substantial folders have no frontmatter of their own and no `templates_available`, the project isn't onboarded — invoke `workflow({ kind: 'discover' })` before writing.
@@ -0,0 +1 @@
1
+ import{t as e}from"./build-Dt6UA4sF.mjs";export{e as default};