@inkeep/open-knowledge 0.6.0-beta.1 → 0.6.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 (132) hide show
  1. package/dist/assets/skills/discovery/SKILL.md +90 -0
  2. package/dist/assets/skills/{open-knowledge → project}/SKILL.md +128 -211
  3. package/dist/cli.mjs +25 -25
  4. package/dist/constants-Dg34Tv0a.mjs +2 -0
  5. package/dist/dist-7_PKsuCz.mjs +1 -0
  6. package/dist/{dist-B_Nlx5d6.mjs → dist-soQLoIV1.mjs} +91 -110
  7. package/dist/index.d.mts +15 -9
  8. package/dist/index.mjs +1 -1
  9. package/dist/init-BDgKRRe5.mjs +1 -0
  10. package/dist/{init-Dx6AdWPr.mjs → init-CNnZmq0z.mjs} +10 -10
  11. package/dist/loader-BhwWH_ja.mjs +1 -0
  12. package/dist/{loader-B_c4LV6V.mjs → loader-TXH7WI7l.mjs} +3 -3
  13. package/dist/preview-BeJHolBZ.mjs +1 -0
  14. package/dist/{preview-DBHMZYsR.mjs → preview-BkWWpoVx.mjs} +2 -2
  15. package/dist/public/assets/ActivityModeContent-CkiL2cwS.js +2 -0
  16. package/dist/public/assets/ActivityPanelDiffView-LNHry3BJ.js +13 -0
  17. package/dist/public/assets/ConsentDialogBody-DkNr2OnV.js +2 -0
  18. package/dist/public/assets/DocumentContext-BK7c8nfO.js +52 -0
  19. package/dist/public/assets/GraphPanel-BG0uuCJI.js +46 -0
  20. package/dist/public/assets/{McpConsentDialogBody-Dw8kQ_BH.js → McpConsentDialogBody-BzCqdeeh.js} +1 -1
  21. package/dist/public/assets/SettingsDialogBody-BN4xM_LZ.js +7 -0
  22. package/dist/public/assets/SourceEditor-CxLbpv43.js +2 -0
  23. package/dist/public/assets/{agent-presence-DUe_PdKt.js → agent-presence-DxGcS3q2.js} +1 -1
  24. package/dist/public/assets/{architectureDiagram-Q4EWVU46-C4pUyyy6.js → architectureDiagram-Q4EWVU46-CIVh0TLx.js} +1 -1
  25. package/dist/public/assets/{blockDiagram-DXYQGD6D-VkwkYpUF.js → blockDiagram-DXYQGD6D-Cg-UHasz.js} +1 -1
  26. package/dist/public/assets/button-DenIdY-r.js +1 -0
  27. package/dist/public/assets/{c4Diagram-AHTNJAMY-uR85wHTj.js → c4Diagram-AHTNJAMY-KFeHAAOO.js} +1 -1
  28. package/dist/public/assets/channel-BqqURnVG.js +1 -0
  29. package/dist/public/assets/checkbox-sqsyVLp7.js +1 -0
  30. package/dist/public/assets/{chunk-336JU56O-CcUNcCyG.js → chunk-336JU56O-DHqaXNrn.js} +2 -2
  31. package/dist/public/assets/{chunk-426QAEUC-D-Drhg6m.js → chunk-426QAEUC-CuPmuy8t.js} +1 -1
  32. package/dist/public/assets/{chunk-4TB4RGXK-DylOpICA.js → chunk-4TB4RGXK-QLafKs7A.js} +1 -1
  33. package/dist/public/assets/{chunk-5FUZZQ4R-COpN1802.js → chunk-5FUZZQ4R-DoG5zk7O.js} +1 -1
  34. package/dist/public/assets/{chunk-5PVQY5BW-SLEOUaCC.js → chunk-5PVQY5BW--zDnbXdQ.js} +1 -1
  35. package/dist/public/assets/{chunk-EDXVE4YY-Cr4fB83o.js → chunk-EDXVE4YY-BfT15Ufb.js} +1 -1
  36. package/dist/public/assets/{chunk-ENJZ2VHE-jO5Yn0Ti.js → chunk-ENJZ2VHE-DEtEhITq.js} +1 -1
  37. package/dist/public/assets/{chunk-ICPOFSXX-DO-wz1LJ.js → chunk-ICPOFSXX-C8AElYY_.js} +1 -1
  38. package/dist/public/assets/{chunk-OYMX7WX6-DlOfgU2U.js → chunk-OYMX7WX6-CX3nrwxJ.js} +1 -1
  39. package/dist/public/assets/{chunk-U2HBQHQK-fFQ_IUwS.js → chunk-U2HBQHQK-CfAyyLJz.js} +1 -1
  40. package/dist/public/assets/{chunk-X2U36JSP-JbIA9pf5.js → chunk-X2U36JSP-CmACymAN.js} +1 -1
  41. package/dist/public/assets/{chunk-YZCP3GAM-Cx9wuApW.js → chunk-YZCP3GAM-CiWxV9XS.js} +1 -1
  42. package/dist/public/assets/{chunk-ZZ45TVLE-CdiDiZYV.js → chunk-ZZ45TVLE-B7t5_MqN.js} +1 -1
  43. package/dist/public/assets/classDiagram-6PBFFD2Q-DrN_pUa5.js +1 -0
  44. package/dist/public/assets/classDiagram-v2-HSJHXN6E-C1MJ1Ukt.js +1 -0
  45. package/dist/public/assets/clone-CecJCxEU.js +1 -0
  46. package/dist/public/assets/{collapsible-DfUFsfqv.js → collapsible-BhpeB6tu.js} +1 -1
  47. package/dist/public/assets/compiler-runtime-Cs91PcD2.js +1 -0
  48. package/dist/public/assets/config-validation-events-RWCM3tpA.js +10 -0
  49. package/dist/public/assets/{dagre-D0NgAcgy.js → dagre-BuFRtOcC.js} +1 -1
  50. package/dist/public/assets/{dagre-KV5264BT-D00jhTEj.js → dagre-KV5264BT-BpBW6R71.js} +1 -1
  51. package/dist/public/assets/{diagram-5BDNPKRD-53lAVvrc.js → diagram-5BDNPKRD-B5phtkJp.js} +1 -1
  52. package/dist/public/assets/{diagram-G4DWMVQ6-DvnMPxCL.js → diagram-G4DWMVQ6-tjReW6yx.js} +1 -1
  53. package/dist/public/assets/{diagram-MMDJMWI5-bSOxNuvv.js → diagram-MMDJMWI5-b4CgKryF.js} +1 -1
  54. package/dist/public/assets/{diagram-TYMM5635-ftQ9f3sM.js → diagram-TYMM5635-D5B0g2lj.js} +1 -1
  55. package/dist/public/assets/{dialog-bA5Nvc2p.js → dialog-DUff25Wx.js} +1 -1
  56. package/dist/public/assets/{dist-SeK3HGs7.js → dist-By89CQBy.js} +1 -1
  57. package/dist/public/assets/{dist-semR8ZB_.js → dist-D-oqC0-R.js} +1 -1
  58. package/dist/public/assets/{dist-DNQ2xKmK.js → dist-D3YXABda.js} +1 -1
  59. package/dist/public/assets/{dist-BUk_vKk2.js → dist-DQ1oWohr.js} +1 -1
  60. package/dist/public/assets/{dist-C8kKU7Ya.js → dist-DXgyWuz5.js} +1 -1
  61. package/dist/public/assets/{dist-DiQSHtaM.js → dist-DllAuqQQ.js} +1 -1
  62. package/dist/public/assets/{dist-CruwjFAC.js → dist-DpEGxZ9h.js} +1 -1
  63. package/dist/public/assets/{doc-hash-BrKkCdCz.js → doc-hash-Biw40x7n.js} +157 -147
  64. package/dist/public/assets/{erDiagram-SMLLAGMA-B1J-oh72.js → erDiagram-SMLLAGMA-CRJz10Jv.js} +1 -1
  65. package/dist/public/assets/{flowDiagram-DWJPFMVM-lLrSyhC0.js → flowDiagram-DWJPFMVM-BDCJTgJ7.js} +1 -1
  66. package/dist/public/assets/{ganttDiagram-T4ZO3ILL-CUjectuq.js → ganttDiagram-T4ZO3ILL-cPQKlGYc.js} +1 -1
  67. package/dist/public/assets/{gitGraphDiagram-UUTBAWPF-vFBdpaIg.js → gitGraphDiagram-UUTBAWPF-BMyjc352.js} +1 -1
  68. package/dist/public/assets/{graphlib-C3wGX8GR.js → graphlib-BU8FnRz1.js} +1 -1
  69. package/dist/public/assets/index-BBSdGAfb.js +1916 -0
  70. package/dist/public/assets/index-CXzfIokG.css +1 -0
  71. package/dist/public/assets/{infoDiagram-42DDH7IO-p5UnX5VQ.js → infoDiagram-42DDH7IO-BZ6v5y-j.js} +1 -1
  72. package/dist/public/assets/{ishikawaDiagram-UXIWVN3A-Dc_uLtDG.js → ishikawaDiagram-UXIWVN3A-DA7_P2Up.js} +1 -1
  73. package/dist/public/assets/{journeyDiagram-VCZTEJTY-D-OWzt5X.js → journeyDiagram-VCZTEJTY-CzZoB161.js} +1 -1
  74. package/dist/public/assets/{kanban-definition-6JOO6SKY-Dg3mF5uv.js → kanban-definition-6JOO6SKY-DKg7pXv1.js} +1 -1
  75. package/dist/public/assets/{label-Bdsqm1CY.js → label-BGXy6Okv.js} +1 -1
  76. package/dist/public/assets/{line-Blj6fR0j.js → line-DGc-MCyQ.js} +1 -1
  77. package/dist/public/assets/{mermaid.core-DKgSmEOt.js → mermaid.core-DRx0wZe0.js} +3 -3
  78. package/dist/public/assets/{mindmap-definition-QFDTVHPH-CzdOizWD.js → mindmap-definition-QFDTVHPH-C9GRZxO6.js} +1 -1
  79. package/dist/public/assets/{panel-CxlToFCs.js → panel-BNBODYwB.js} +1 -1
  80. package/dist/public/assets/{pieDiagram-DEJITSTG-CCKPY52f.js → pieDiagram-DEJITSTG-L6I3ft9Z.js} +1 -1
  81. package/dist/public/assets/{propagation-api-CobBjsTC.js → propagation-api-3kmMXRwS.js} +1 -1
  82. package/dist/public/assets/{quadrantDiagram-34T5L4WZ-7k7hUHeX.js → quadrantDiagram-34T5L4WZ-Dp2IeEPC.js} +1 -1
  83. package/dist/public/assets/{requirementDiagram-MS252O5E-DtBI2UGc.js → requirementDiagram-MS252O5E-BxNWyIZg.js} +1 -1
  84. package/dist/public/assets/{sankeyDiagram-XADWPNL6-CsyAV-jZ.js → sankeyDiagram-XADWPNL6-CRn0_xkw.js} +1 -1
  85. package/dist/public/assets/{sequenceDiagram-FGHM5R23-BVVas0PB.js → sequenceDiagram-FGHM5R23-Drkjb1j4.js} +1 -1
  86. package/dist/public/assets/{stateDiagram-FHFEXIEX-DSU8iqSJ.js → stateDiagram-FHFEXIEX-BBUFKS0u.js} +1 -1
  87. package/dist/public/assets/stateDiagram-v2-QKLJ7IA2-BtYsWsIg.js +1 -0
  88. package/dist/public/assets/{target-navigation-intent-B5Xuyiwx.js → target-navigation-intent-HRo6WpEX.js} +1 -1
  89. package/dist/public/assets/{telemetry-impl-BiWd9bHi.js → telemetry-impl-DPRMO_b_.js} +1 -1
  90. package/dist/public/assets/{textarea-CQGiqhkV.js → textarea-C_bHQrXh.js} +1 -1
  91. package/dist/public/assets/{timeline-definition-GMOUNBTQ-Cz06Vse0.js → timeline-definition-GMOUNBTQ-CLoe9y-z.js} +1 -1
  92. package/dist/public/assets/{toggle-group-Cimrwku9.js → toggle-group-pPF0BJ9M.js} +1 -1
  93. package/dist/public/assets/typing-burst-detector-S1xj6fRY.js +2 -0
  94. package/dist/public/assets/{vennDiagram-DHZGUBPP-Cf-DwVPO.js → vennDiagram-DHZGUBPP-q2zdiy4F.js} +1 -1
  95. package/dist/public/assets/{wardleyDiagram-NUSXRM2D-CYXaSU9R.js → wardleyDiagram-NUSXRM2D-Njg6YHso.js} +1 -1
  96. package/dist/public/assets/{xychartDiagram-5P7HB3ND-Czjmctj-.js → xychartDiagram-5P7HB3ND-Ct8Wgp45.js} +1 -1
  97. package/dist/public/index.html +25 -25
  98. package/dist/{repair-launch-json-BGZghXjn.mjs → repair-launch-json-BhjUucaZ.mjs} +2 -2
  99. package/dist/{repair-mcp-configs-71a8eAG0.mjs → repair-mcp-configs-BcIhDXbC.mjs} +2 -2
  100. package/dist/src-BwG6spwT.mjs +2 -0
  101. package/dist/start-BX81DKP-.mjs +1 -0
  102. package/dist/{start-BwpG-0n6.mjs → start-CBl5qsCR.mjs} +2 -2
  103. package/package.json +2 -2
  104. package/dist/constants-C0dm6XrR.mjs +0 -2
  105. package/dist/dist-Bq1Nm7Hl.mjs +0 -1
  106. package/dist/init-jAKvTa9U.mjs +0 -1
  107. package/dist/loader-DpGoDP4I.mjs +0 -1
  108. package/dist/preview-CifNm6Oe.mjs +0 -1
  109. package/dist/public/assets/ActivityModeContent-nwB2ciii.js +0 -2
  110. package/dist/public/assets/ActivityPanelDiffView-d4ec9M69.js +0 -13
  111. package/dist/public/assets/ConsentDialogBody-BncX7dD1.js +0 -2
  112. package/dist/public/assets/DocumentContext-Bc83ypCd.js +0 -52
  113. package/dist/public/assets/GraphPanel-DwHnIo6r.js +0 -46
  114. package/dist/public/assets/SettingsDialogBody-o-ExGcAl.js +0 -11
  115. package/dist/public/assets/SourceEditor-DF322IKX.js +0 -2
  116. package/dist/public/assets/button-JrGyO3-P.js +0 -1
  117. package/dist/public/assets/channel-DtU-CCDV.js +0 -1
  118. package/dist/public/assets/checkbox-CTrUepCv.js +0 -1
  119. package/dist/public/assets/classDiagram-6PBFFD2Q-BZpd1T4J.js +0 -1
  120. package/dist/public/assets/classDiagram-v2-HSJHXN6E-C6GyLa0q.js +0 -1
  121. package/dist/public/assets/clone-CvvNDZ2x.js +0 -1
  122. package/dist/public/assets/compiler-runtime-CVnuRdak.js +0 -1
  123. package/dist/public/assets/config-validation-events-BCNaZEDf.js +0 -10
  124. package/dist/public/assets/index-Bzs40SFf.css +0 -1
  125. package/dist/public/assets/index-zU1ANKhn.js +0 -1928
  126. package/dist/public/assets/stateDiagram-v2-QKLJ7IA2-B8ew63Gv.js +0 -1
  127. package/dist/public/assets/typing-burst-detector-ByUXt_f8.js +0 -2
  128. package/dist/src-B_7W-2eF.mjs +0 -2
  129. package/dist/start-CMyq5RDd.mjs +0 -1
  130. /package/dist/public/assets/{katex-qwlL5fSd.js → katex-DX-tM4Fr.js} +0 -0
  131. /package/dist/public/assets/{trace-api-3dczfLSc.js → trace-api-CiCjX4Sz.js} +0 -0
  132. /package/dist/public/assets/{w3c-keyname-BSTW6KzM.js → w3c-keyname-B5t0fahT.js} +0 -0
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: open-knowledge
3
- description: "MUST invoke when the project contains a .ok/ directory — before any read or edit of .md / .mdx files, any mcp__open-knowledge__ tool call, and any write_document / edit_document. Skip if no .ok/ not an Open Knowledge project. Carries preview-attach (open preview browser at session start; one-shot on `action: attach-preview-once`; surface to user on `action: start-ui` when no UI is running), STOP rules for native Read/Grep/Edit on in-scope markdown, grounding rules (every factual claim needs a source), standard markdown linking with get_dead_links verification, image sourcing + alt-text + source-citation rules, folder-first organization with opt-in nested .ok/ frontmatter + templates, and the anti-pattern table."
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."
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.6.0-beta.1"
6
+ version: "0.6.0-beta.10"
7
7
  author: "Inkeep"
8
8
  repository: "https://github.com/inkeep/open-knowledge"
9
9
  ---
@@ -17,25 +17,20 @@ Open Knowledge (OK) is a markdown-CRDT collaboration platform exposed via MCP. T
17
17
 
18
18
  ## TL;DR — the 90% case
19
19
 
20
- 1. **Reads:** `exec("cat …")` for a single doc, `exec("ls -A …")` for a directory, `search` for ranked, `grep` for literal. 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 patches, `frontmatter_patch` for 1-2 frontmatter keys (preferred). Full frontmatter rewrites use `write_document({ position: "replace" })`. `edit_document` rejects frontmatter (HTTP 400).
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).
22
22
  3. **Preview:** every OK tool response carries the preview URL (`ui.baseUrl` on reads, `previewUrl` on writes). Navigate your in-app browser to it from the first response you see; refresh from later responses if Electron/UI restarted. Surface to the user on a `start-ui` warning (no UI running). Don't `preview_screenshot` after every edit.
23
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.
24
24
 
25
25
  Everything below is depth. Read on demand.
26
26
 
27
- ## Tool index — by family
27
+ ## Tool index — 17 tools
28
28
 
29
- The full MCP surface, grouped by intent. Each family has one canonical instruction site later in this doc (linked). Reach for the family before the individual tool.
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
- - **Read** — `exec` (primary; shell-style), `read_document` (typed; one doc), `search` (ranked, BM25 + recency), `grep` (literal, frontmatter-enriched), `list_documents` (folder + `frontmatter_defaults` + `templates_available` cascade). See *Reads — examples*.
32
- - **Write / edit** — `write_document` (new or full-replace, incl. `template:` instantiation), `edit_document` (body-only find/replace), `frontmatter_patch` (JSON Merge Patch for 1-2 keys), `delete_document`. See *Writing* and *Frontmatter conventions*.
33
- - **Restructure** — `rename_document` (single file; updates referrers), `rename_folder` (folder + descendants; updates referrers). Always prefer these over `delete_document` + re-write — they preserve history and rewrite incoming links.
34
- - **Versioning** — `save_version` (named snapshot), `get_history` (versions for a doc), `rollback_to_version` (restore a snapshot). Call `save_version` before any 1-way operation (delete, large rewrite, folder rename) you may need to undo.
35
- - **Link graph** — `get_dead_links` (verify after writes; strict-exact), `get_backlinks` (incoming), `get_forward_links` (outgoing), `get_orphans` (no incoming), `get_hubs` (high-incoming), `suggest_links` (untextualized mentions). See *Linking*.
36
- - **Components** — `get_components` (fetch param schemas + literal `example` bytes). See *Components — prefer canonicals*.
37
- - **Folder defaults + templates** — `set_folder_rule` (writes `<folder>/.ok/frontmatter.yml`), `write_template` / `delete_template` (writes `<folder>/.ok/templates/`), `get_config` (read resolved config). See *Folder structure + metadata*.
38
- - **Workflow tools (return procedural guides, not data)** — `ingest`, `research`, `consolidate`, `discover`. See *Workflow tools — when to invoke them*.
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 schemas).
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'`).
33
+ - **Workflow** — `ingest`, `research`, `consolidate`, `discover` (return procedural guides, not data).
39
34
 
40
35
  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.
41
36
 
@@ -45,13 +40,13 @@ When this workspace has Open Knowledge MCP configured, do **not** use your host'
45
40
 
46
41
  - **Native `Read` / `Grep` / `Glob` on in-scope `.md` / `.mdx`** — the original case.
47
42
  - **`Bash ls` / `Bash find` / `Bash cat` on dirs containing in-scope markdown** — use `exec("ls -A …")` / `exec("find … -name '*.md'")` / `exec("cat …")` instead. Native returns bare names; `exec` returns frontmatter, backlink counts, and recent activity per child. `-A` shows hidden entries (`.ok/`, `.okignore`) which OK projects carry; omit `.` and `..` rows that `-a` would add.
48
- - **Glob patterns that target markdown** (`**/*.md`, any dir known to be markdown-heavy like `specs/**`, `reports/**`, `docs/**`) — use `exec` with `find`, or `list_documents({ dir })`.
49
- - **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` / `grep`. Subagents remain appropriate for **source-code** exploration.
43
+ - **Glob patterns that target markdown** (`**/*.md`, any dir known to be markdown-heavy like `specs/**`, `reports/**`, `docs/**`) — use `exec` with `find`, or `exec("ls -A <dir>")`.
44
+ - **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.
50
45
  - **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.
51
46
 
52
47
  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.
53
48
 
54
- **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` / `grep` / `read_document` via your client's documented MCP invocation (including any generic "call MCP tool" flow). Registration is the test, not top-level-symbol visibility.
49
+ **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.
55
50
 
56
51
  **Escape hatch.** Native `Read` / `Grep` / `Glob` on `.md` / `.mdx` is allowed **only** when no Open Knowledge MCP server is registered for this project, **or** immediately after you tried an MCP call and it failed — then begin a user-visible sentence with `Open Knowledge MCP unavailable:`. Never use the hatch because you skipped your client's MCP path, didn't see `exec` as a top-level tool, or rationalized the skill wasn't necessary.
57
52
 
@@ -59,19 +54,19 @@ Why: native tools skip frontmatter, backlinks, shadow-repo activity, and project
59
54
 
60
55
  ## Reads — examples
61
56
 
62
- - Read a file: `exec("cat <path>.md")` — contents + full rich enrichment
63
- - List a directory: `exec("ls -A <dir>")` — per-child frontmatter, recursive markdown counts, most-recently-updated doc per subdir. Prefer `-A` over plain `ls`: OK projects carry dot-prefixed entries (`.ok/`, nested `.ok/`, `.okignore`) that plain `ls` omits. `-A` shows hidden entries without the noisy `.`/`..` rows that `-a` adds, and without the verbose long-format columns that `-la` adds (the per-child enrichment already carries the useful metadata `-l` would surface).
64
- - Search: `exec("grep -rn <term> <dir> | head -5")` — matches + enrichment on matched files
65
- - Typed tools (`read_document`, `search`, `grep`, `list_documents`) remain available — prefer them when a structured `structuredContent` shape is useful (e.g., passing results to another tool). For interactive reads, `exec` is lighter. **Pick the right search:** `search` for ranked retrieval (cmd-K parity title boost + body BM25 + recency); `grep` for every literal-string occurrence grouped by file with frontmatter enrichment.
57
+ - Read a file: `exec("cat <path>.md")` — contents + full rich enrichment.
58
+ - List a directory: `exec("ls -A <dir>")` — per-child frontmatter, recursive markdown counts, most-recently-updated doc per subdir, folder-level `frontmatter_defaults` + `templates_available`. Prefer `-A` over plain `ls` to surface dot-prefixed entries (`.ok/`, `.okignore`) without the noisy `.`/`..` rows that `-a` adds.
59
+ - Literal search: `exec("grep -rn <term> <dir> | head -5")` — matches + enrichment on matched files.
60
+ - Ranked search: `search({ query })` cmd-K parity (title boost + body BM25 + recency); use when picking the best doc, not when listing every occurrence.
66
61
 
67
62
  ## Preview — open the browser at session start
68
63
 
69
- **The invariant.** If OK Electron is open for this project OR `ok ui` is running for it, every OK tool response carries the preview URL — plain HTTP, no custom URL schemes, works in any browser including agent in-app browsers (Claude Desktop, Cursor, Codex, Cowork). Read tools (`exec`, `grep`, `search`, `list_documents`, `read_document`) carry it in `ui.baseUrl` (top-level) and per-doc `previewUrl` fields; write tools carry it in `previewUrl` + the optional `warning` shape. Never construct this URL; always read it from the latest tool response.
64
+ **The invariant.** If OK Electron is open for this project OR `ok ui` is running for it, every OK tool response carries the preview URL — plain HTTP, no custom URL schemes, works in any browser including agent in-app browsers (Claude Desktop, Cursor, Codex, Cowork). Read tools (`exec`, `search`, `links`) carry it in `ui.baseUrl` (top-level) and per-doc `previewUrl` fields; write tools carry it in `previewUrl` + the optional `warning` shape. Never construct this URL; always read it from the latest tool response.
70
65
 
71
- **The default agent move.** Make your first OK tool call (any read works — `list_documents`, `exec("ls -A")`, or `read_document` is enough). The response carries the preview URL. Navigate to it immediately, then proceed with your real work. The user watches edits land live; you can re-navigate later to verify a CRDT edit landed when a response looks ambiguous. Per host:
66
+ **The default agent move.** Make your first OK tool call (any read works — `exec("ls -A")` is enough). The response carries the preview URL. Navigate to it immediately, then proceed with your real work. The user watches edits land live; you can re-navigate later to verify a CRDT edit landed when a response looks ambiguous. Per host:
72
67
 
73
68
  - **In-app browser hosts** (Claude Desktop, Cursor, Codex, Cowork): navigate the in-app browser to the `previewUrl`. Default.
74
- - **Claude Code Desktop**: call `preview_start("open-knowledge-ui")` (host tool, not OK MCP).
69
+ - **Claude Code Desktop**: call `preview_start("open-knowledge-ui")` (host tool, not OK MCP) — and nothing else. Do NOT read, diagnose, or edit `.claude/launch.json`; that file is host-managed config, and the OK ui lock-collision proxy handles the UI-already-running case automatically. If `preview_start` fails, surface the `previewUrl` from any recent OK tool response and report the issue to the user — do not try to "fix" `launch.json`.
75
70
  - **Stdio-only hosts**: surface the URL in chat; `open <url>` (macOS) if the host can shell out.
76
71
 
77
72
  **Four signals to check if it's already open** (you usually can't pre-check, so read these from each write response):
@@ -83,13 +78,13 @@ Why: native tools skip frontmatter, backlinks, shadow-repo activity, and project
83
78
 
84
79
  Warnings fire at most once per session in the fresh-start case.
85
80
 
86
- **`previewUrl: null` only means "no UI reachable" on the three attach-warning tools: `write_document` / `edit_document` / `frontmatter_patch`.** Workflow tools return prose and don't carry `previewUrl`. `delete_document` / `rename_document` / `rename_folder` emit `previousPreviewUrl` (different field, for closing stale tabs) and don't fire attach warnings.
81
+ **`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.
87
82
 
88
83
  **Always read `previewUrl` from the latest write response.** Don't cache the session-start value — Electron quit/reopen (or `ok ui` restart) can change the port; the resolver picks up the new port automatically.
89
84
 
90
85
  If you see `"Hocuspocus server is not running"`, run `ok start` and retry. NEVER construct preview URLs by hand.
91
86
 
92
- OK Electron and `ok ui` cannot serve the same project at once they share `ui.lock`. A UI-lock collision means the other is running for that project (use that one, or quit it first).
87
+ OK Electron and `ok ui` share `ui.lock` — only one can be the primary server for a project. When a second `ok ui` tries to bind a different port (Claude Code Desktop's preview pane spawning a sibling, for example), the OK lock-collision handler proxies the new port to the live UI server transparently. **Do not nudge the user to quit OK Electron to free a port** — the proxy handles it, and quitting would tear down a UI the user is actively using.
93
88
 
94
89
  **The preview is read-only for the agent.** Navigate to verify edits landed; you cannot click or type to drive edits — the CRDT flow is one-way (agent → MCP → CRDT → preview).
95
90
 
@@ -99,39 +94,26 @@ OK Electron and `ok ui` cannot serve the same project at once — they share `ui
99
94
 
100
95
  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.
101
96
 
102
- 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 from this tool call `save_version` first if you may need to roll back (restore via `rollback_to_version`; list snapshots via `get_history`), and `get_backlinks` first if you want to fix the referrers that will become redlinks. To move or rename a doc instead of delete + rewrite, use `rename_document` (single file) or `rename_folder` (folder + descendants) both rewrite incoming references atomically.
97
+ 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.
103
98
 
104
99
  **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.
105
100
 
106
101
  ## Components — prefer canonicals when one fits
107
102
 
108
- Some Open Knowledge projects ship a registry of custom JSX components (callouts, tabs, math, file attachments, …) that render with richer affordances than plain CommonMark / GFM. Discovery and authoring follow a three-step pattern — no separate listing call required:
103
+ OK projects ship a registry of custom JSX components (callouts, tabs, math, file attachments, …) with richer affordances than plain CommonMark / GFM. Three-step pattern:
109
104
 
110
- 1. **Discover (point-of-authoring).** The JSON-schema `description` of `write_document` and `edit_document` carries an inline inventory of the canonical JSX components available for this project: a list of `{ id, displayName, description, kind }` entries. The agent's host renders that description in its tool-list view, so the catalog is visible at the moment of invoking either write tool. The `kind` is one of `jsx-block` (children-bearing JSX) or `jsx-void` (self-closing JSX).
105
+ 1. **Discover.** `write_document` and `edit_document` carry an inline inventory of available canonicals as part of their `description` your host renders it in its tool-list view. `kind` is `jsx-block` (children) or `jsx-void` (self-closing).
106
+ 2. **Fetch.** `get_components({ ids: [...] })` returns per-entry `example` (literal copy-paste bytes) + form-aware `params` schema (`type`, `values?` for enums, `required`, `defaultValue?`, `description`).
107
+ 3. **Write.** Compose markdown inline using the `example` syntax + your prop values, then call `write_document` or `edit_document`. Shapes: `<BlockComponent prop="...">body</BlockComponent>` or `<VoidComponent prop="..." />`.
111
108
 
112
- 2. **Fetch full details.** Call `get_components({ ids: ["..."] })` with the ids you picked from the inventory. The response carries per-entry `example` (the literal source-form bytes to copy-paste) and a form-aware `params` schema (each prop's `type`, `values?` for enums, `required`, `defaultValue?`, `description`). Use whichever components are **semantically useful in any part of the doc** — a Callout for a warning, a Tabs/Tab pair for alternative content, etc.
109
+ **Fenced code blocks are a separate authoring path** (NOT in the inventory, no `get_components` fetch needed):
110
+ - ` ```mermaid ` — Mermaid diagram (flowchart, sequence, class, state, ER, gantt, pie).
111
+ - ` ```html preview ` (also `htm preview` / `xml preview`) — live iframe preview. Author a standalone HTML/JS/CSS page in the fence; it executes inside the editor. Use this for anything interactive (charts via Chart.js / D3, calculators, animations, demos). Optional `h=<value>` / `w=<value>` fence-meta tokens set iframe size.
112
+ - ` ```<lang> ` (other languages) — syntax-highlighted code block, no preview.
113
113
 
114
- 3. **Write.** Compose your markdown body inline using the `example` syntax + your chosen prop values, then call `write_document` or `edit_document` once. Generic placeholder shapes per `kind`:
114
+ **Fallback:** if no canonical matches, write any `<TagName>...</TagName>` JSX OK renders unregistered components as raw MDX. Prefer canonicals when one matches.
115
115
 
116
- ```mdx
117
- <BlockComponent prop="value">
118
- Body content here.
119
- </BlockComponent>
120
- ```
121
-
122
- ```mdx
123
- <VoidComponent prop="value" />
124
- ```
125
-
126
- **Fenced code blocks are a separate authoring path.** Open Knowledge renders certain fenced code blocks specially — author these the way you would in any markdown doc; they are NOT in the component inventory and don't need a `get_components` fetch:
127
-
128
- - ` ```mermaid ` — renders the block as a Mermaid diagram (flowchart, sequence, class, state, ER, gantt, pie).
129
- - ` ```html preview ` (also ` ```htm preview ` / ` ```xml preview `) — renders the block as a live iframe preview. Author a standalone HTML/JS/CSS page in the fence and it executes inside the editor. **Use this whenever you want anything interactive or JS-powered** (charts via Chart.js / D3, calculators, animations, demos of an idea, anything that needs live JavaScript). Optional fence-meta tokens: `h=<value>` and `w=<value>` set iframe size (e.g. ` ```html preview h=400px w=600px `).
130
- - ` ```<lang> ` (any other language) — renders as a syntax-highlighted code block (no preview).
131
-
132
- **No-canonical-fits fallback.** If none of the available canonical JSX components matches what you want to author, you can still write any `<TagName>...</TagName>` JSX — Open Knowledge renders unregistered components as raw MDX (the wildcard tolerance contract). Prefer canonicals when one matches; arbitrary JSX is the long-tail escape hatch.
133
-
134
- **Don't enumerate first-party canonicals here.** Component names, counts, and instance-specific examples are project-specific — the source of truth is the inventory the write tools currently advertise. Reading SKILL.md never replaces calling `get_components` for current shape.
116
+ **Don't enumerate first-party canonicals here** — names/counts are project-specific; the inventory in the write tools' descriptions is the source of truth.
135
117
 
136
118
  ## Grounding — every factual claim needs a source (MUST)
137
119
 
@@ -156,167 +138,92 @@ Knowledge-base docs are factual artifacts — whether the project is a wiki, an
156
138
  - **Internal cross-refs between OK docs** → `[text](./other-doc.md)` — link liberally to aid navigation.
157
139
  - **Never wrap a link in backticks.** `` `[text](./foo.md)` `` is a bug — the backticks make it render as literal code rather than a link.
158
140
  - **Never use HTML anchors** (`<a href="...">`). Markdown link syntax only.
159
- - **Verify before walking away.** After writing a doc, call `get_dead_links({ sourceDocNames: ['your/doc'] })` to find broken references. Fix each redlink or explicitly accept it. Companion link-graph reads (same family): `get_backlinks` (incoming), `get_forward_links` (outgoing), `get_orphans` (no incoming), `get_hubs` (high-incoming), `suggest_links` (untextualized mentions worth linking).
160
- - **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). `get_dead_links` is strict-exact — trust the tool, not the visual.
141
+ - **Verify before walking away.** After writing a doc, call `links({ kind: "dead", sourceDocNames: ["your/doc"] })` to find broken references. Fix each redlink or explicitly accept it. Companion `links` kinds: `backlinks` (incoming), `forward` (outgoing), `orphans` (no incoming), `hubs` (high-incoming), `suggest` (untextualized mentions worth linking).
142
+ - **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.
161
143
 
162
144
  **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.
163
145
 
164
146
  ## Media — images and attachments
165
147
 
166
- ### 1. Markdown syntax only
167
-
168
- - Use markdown image syntax: `![alt text](./path/to/image.png)`.
169
- - Do NOT emit HTML `<img src="...">` tags. They get preserved in the CRDT but don't participate in OK's content graph and don't render consistently across Fumadocs / preview surfaces.
170
- - Paths resolve relative to the doc's own path (standard CommonMark).
171
-
172
- ### 2. Image sourcing — save locally, don't hot-link
173
-
174
- - Agents MUST NOT embed external image URLs directly (e.g., `![pic](https://somesite.com/pic.png)`). Hot-linked images rot when the source disappears, leak referrers, and don't travel if content is exported or archived.
175
- - To use an image from an external source:
176
- 1. Fetch it (`WebFetch` / `curl` / your host's equivalent) and save to a local path.
177
- 2. Reference with a relative markdown image link.
178
- 3. Cite the source in a caption (see §4 below).
179
- - **Conventional location depends on placement model.** OK runs two distinct asset-placement models:
180
- - **(a) Free-form image embeds inside a doc you're authoring** — co-located **alongside the referencing doc** (same directory as the doc that embeds them). This matches the editor's drop-to-doc-folder default and the `attachmentFolderPath: "./"` semantic from the asset-embed surface. Sha256 same-directory dedup operates on this layout.
181
- - **(b) Raw sources preserved via `ingest`** — co-located **inside `external-sources/`** under the content root, alongside the wrapper markdown that embeds them (`external-sources/<slug>.<ext>` + `external-sources/<slug>.md`). This is the Karpathy layer-1 convention; the wrapper IS the referring doc, so binary + wrapper are same-dir-as-referring per the asset-embed contract. Cross-folder consumers (e.g., `research/foo.md`) resolve `![[<slug>.<ext>]]` via the basename index.
182
- - If the project already has a different convention, follow it — check via `exec("ls -A")` first.
183
- - If you cannot fetch (no network, paywalled source, etc.): DON'T invent a local path. Either omit the image or mark inline `(TODO: image needs sourcing from <URL>)` for a human.
184
-
185
- ### 3. Alt-text discipline
186
-
187
- - Every image needs **meaningful alt text** describing what the image shows, not what it is.
188
- - Bad: `![](./aang.png)` (empty — invisible to assistive tech, zero searchability)
189
- - Bad: `![image](./aang.png)` (generic — same problem)
190
- - Bad: `![aang.png](./aang.png)` (filename as alt — still generic)
191
- - Good: `![Aang using the Avatar State to defeat Ozai](./aang.png)`
192
- - Alt text is both an accessibility requirement AND a searchability signal — OK indexes alt text.
193
-
194
- ### 4. Cite image sources (Grounding rule applies)
195
-
196
- - Every image pulled from the web needs a source caption right below it, per the Grounding rule:
148
+ - **Markdown syntax only:** `![alt text](./path/to/image.png)`. Do NOT emit HTML `<img>` tags — they don't participate in OK's content graph and don't render consistently across Fumadocs / preview surfaces. Paths resolve relative to the doc.
149
+ - **Save locally, don't hot-link.** Hot-linked external image URLs rot when the source disappears. Fetch (`WebFetch` / `curl`), save to a local path, reference via relative markdown link, cite the source below.
150
+ - **Placement model.** Free-form image embeds co-located alongside the referencing doc (sha256 same-directory dedup). Raw sources via `ingest` → `external-sources/<slug>.<ext>` + `external-sources/<slug>.md` (the wrapper-binary pair). Check via `exec("ls -A")` if the project uses a different convention.
151
+ - **Cannot fetch** (no network, paywall) don't invent a local path. Omit, or mark inline `(TODO: image needs sourcing from <URL>)`.
152
+ - **Meaningful alt text required** — describes WHAT the image shows, not what it is. `![]()` / `![image]()` / `![filename.png]()` all fail. OK indexes alt text — it's both accessibility AND searchability.
153
+ - **Cite web image sources** below the image (Grounding rule):
197
154
  ```markdown
198
155
  ![Aang using the Avatar State to defeat Ozai](./assets/images/aang/avatar-state.png)
199
156
  *Source: [Avatar Wiki — Aang](https://avatar.fandom.com/wiki/Aang#Avatar_State)*
200
157
  ```
201
- - Original images (your own diagrams, screenshots of your own tool, etc.) may caption `*Original*` or omit the caption.
202
- - Unattributed web images are a failure mode equivalent to unsourced factual claims.
158
+ Original diagrams/screenshots may caption `*Original*` or omit. Unattributed web images are equivalent to unsourced factual claims.
203
159
 
204
- ## Frontmatter conventions
160
+ ## Folders, frontmatter, templates
205
161
 
206
- Every `.md` / `.mdx` file in the knowledge base needs YAML frontmatter — `title` and `description` required, `tags` recommended:
162
+ Every `.md` / `.mdx` file needs YAML frontmatter — `title` + `description` required, `tags` recommended:
207
163
 
208
164
  ```yaml
209
165
  ---
210
166
  title: Article Title
211
167
  description: Brief summary
212
- tags:
213
- - relevant
214
- - tags
168
+ tags: [relevant, tags]
215
169
  ---
216
170
  ```
217
171
 
218
- Folder-level defaults that any new doc here inherits live in opt-in nested `<folder>/.ok/frontmatter.yml` and merge at read timesee *Folder structure + metadata* below.
172
+ Folder defaults live in opt-in nested `<folder>/.ok/frontmatter.yml`; templates live in `<folder>/.ok/templates/`. **Most folders have NO `.ok/`** sparse, lazy-create, auto-clean. A folder gets one only when it declares defaults or carries templates.
219
173
 
220
- **Binary-source wrappers (`ingest`-produced).** Docs that wrap a co-located binary file under `external-sources/` carry extra frontmatter keys so the wrapper-binary pair is fully described and re-ingest can detect changes:
221
-
222
- ```yaml
223
- ---
224
- title: ...
225
- description: ...
226
- source_url: https://example.com/file.pdf
227
- source_path: ./<slug>.<ext> # relative to this wrapper
228
- media_type: application/pdf # RFC 6838 type/subtype
229
- bytes: 1234567 # integer
230
- sha256: <64-char hex> # of the embedded binary
231
- date_fetched: YYYY-MM-DD
232
- preservation: binary # OR: text-only (shell-less fallback) / text-extracted (genuine HTML source)
233
- supersedes: # OPTIONAL — present on dated-sibling wrappers from a re-ingest
234
- - <prior-slug>.md # YAML list (matches consolidate's shape so downstream tooling reads uniformly)
235
- tags:
236
- - source
237
- - immutable
238
- - layer-ingest
239
- - binary # OR: text — matches the `preservation` mode
240
- ---
241
-
242
- ![[<slug>.<ext>]]
174
+ ```
175
+ content-root/
176
+ ├── .ok/ ← project root .ok/ (config.yml, cache)
177
+ ├── meetings/
178
+ │ ├── .ok/
179
+ │ │ ├── frontmatter.yml ← folder defaults
180
+ │ │ └── templates/
181
+ │ │ └── prep-notes.md
182
+ │ └── 2026-05-01.md
183
+ └── research/ ← no .ok/ — inherits root cascade
184
+ └── auth-providers.md
243
185
  ```
244
186
 
245
- The wrapper body is just the wiki-embed (`![[<slug>.<ext>]]`). For PDFs and opaque attachments this renders as a Notion-style File row that click-dispatches to the appropriate viewer; explicit `<Pdf src="./<slug>.pdf" />` JSX is the opt-in inline canvas viewer. See `ingest`'s tool body for the full re-ingest + size + executable rules.
246
-
247
- **Editing frontmatter.** `edit_document` does NOT change frontmatter — it's body-only by design; frontmatter-intersecting find/replace calls return HTTP 400. For single-key edits (set / create / delete one or a few properties), prefer `frontmatter_patch({ docName, patch: { key: value } })` — JSON Merge Patch (RFC 7396), `null` deletes, field-level CRDT merge for concurrent edits, atomic per-call. For full-document rewrites (≥3-5 frontmatter keys changing, or body and frontmatter together), call `write_document({ docName, position: "replace", markdown })` and include the new YAML block at the top of `markdown`. Folder defaults from the nested `.ok/frontmatter.yml` cascade still merge at read time, so you only need to declare keys that differ from the cascade.
187
+ **Cascade merge:** scalars (`title`, `description`) replace last-wins root leaf; arrays (`tags`) union-and-dedup; file frontmatter wins per-scalar over folder defaults, tags union with the cascade.
248
188
 
249
- ## Follow project conventions — read folder defaults before writing (MUST)
189
+ ### Read the folder before writing (MUST)
250
190
 
251
- Before creating or editing docs in a folder, **always** call `list_documents(<folder>)` (or `exec("ls -A <folder>")`) once and act on what it returns. Skipping this step is how agents land docs that violate the folder's discipline (wrong tags, no template, missing frontmatter shape) and force a follow-up cleanup pass.
191
+ Before creating or editing docs in a folder, **always** call `exec("ls -A <folder>")` once. The response carries `frontmatter_defaults` (the merged cascade) + `templates_available` (the template menu for `write_document({ template })`). Skipping this is how agents land docs that violate folder discipline.
252
192
 
253
193
  Pre-write checklist:
254
194
 
255
- 0. **First-contact check.** If `list_documents(<folder>)` returns empty `frontmatter_defaults` AND empty `templates_available` AND `exec("ls -A")` shows substantial content elsewhere, the project hasn't been onboarded — STOP and invoke `discover` (see Workflow tools table) before writing. Skip this check on subsequent writes in the same session once you've confirmed the project is configured.
256
-
257
- 1. **Read `frontmatter_defaults`** — the merged folder defaults (title shape, description, tags) that any new doc here will inherit. Surfaces nested `<folder>/.ok/frontmatter.yml` cascade walked root leaf, leaf wins per-key. **Don't redeclare** keys the cascade already provides let inheritance carry them. Override per-file only when the file truly differs.
258
- 2. **Read `templates_available`**the menu of starter shapes for `write_document({ template })`. Each entry has `name`, `title`, `description`, and `scope` (`local` / `inherited`). If an entry matches, prefer it over free-form markdown — see "When to use a template" and "When to create a template" below.
259
- 3. **Read recent siblings** — `list_documents` enrichment shows recent edits and per-child frontmatter. New docs should match the shape of existing ones (filename pattern, frontmatter keys, body structure). Inconsistency is the enemy.
260
- 4. **Confirm content scope** — `content.dir` (in `.ok/config.yml`) defines the content root. `.gitignore` and `.okignore` files (gitignore syntax, nested at any depth) define which paths are excluded from the document index. Anything excluded is regular source code, not a knowledge-base doc.
195
+ 0. **First-contact check.** If `frontmatter_defaults` AND `templates_available` are 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.
196
+ 1. **Read `frontmatter_defaults`** — don't redeclare keys the cascade already provides; let inheritance carry them.
197
+ 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 contracttemplates carry frontmatter + body structure hand-authored docs routinely miss).
198
+ 3. **Read recent siblings** new docs should match the shape of existing ones (filename, frontmatter, body structure).
199
+ 4. **Confirm content scope** — `content.dir` (`.ok/config.yml`) defines the root. `.gitignore` / `.okignore` (nested at any depth) define exclusions.
261
200
 
262
- If a project uses `ok seed` to scaffold the Karpathy three-layer layout (`external-sources/` `research/` → `articles/`), the seed encodes layer rules in the project config so each layer's defaults show up in `list_documents` enrichment for that folder. Projects with custom layouts put their own discipline in their own folder defaults. Either way: **read the folder before writing**.
263
-
264
- **Once per folder per session.** If you already ran the checklist for a folder earlier in the session, you can skip re-running it for subsequent docs in the same folder — unless you (or the user) changed a folder rule or template since.
201
+ **Once per folder per session** the checklist doesn't repeat unless you (or the user) changed a folder rule or template since.
265
202
 
266
203
  ### When to use a template (MUST when one fits)
267
204
 
268
- If `templates_available` lists a template whose `title` / `description` matches what you're about to write, instantiate it via `write_document({ template, docName, position: "replace" })` instead of free-form `markdown:`. This is not a stylistic preference — it's the folder's contract:
269
-
270
- - Templates carry frontmatter (title shape, tags, status) that hand-authored docs routinely miss.
271
- - Templates encode body structure (required sections, attendee/agenda blocks, status fields) that downstream tooling and humans rely on.
272
- - Inherited templates (`scope: "inherited"`) are equally valid — when you write a doc in a subfolder, a template defined on an ancestor folder still surfaces in `templates_available` and is the right tool. Don't dismiss inherited entries because they don't live in the leaf folder's `.ok/`.
205
+ 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.
273
206
 
274
- Skip the template only when (a) `templates_available` is empty, (b) no entry matches the doc's purpose, OR (c) the user explicitly asked for free-form content. If you skip, briefly note why in chat so the user can correct course (e.g. "no template matched — writing free-form").
207
+ ### When to create a template
275
208
 
276
- ### When to create a template (encouraged — don't wait to be asked)
209
+ Templates make folder structure durable. Create them proactively:
277
210
 
278
- Templates are how a folder's structure becomes durable. Create them proactively, not just when asked:
211
+ - 2+ sibling docs share a skeleton in a folder with no template extract via `folder_config({ action: "write-template" })`.
212
+ - About to write a doc in a folder where no template fits, AND the shape is reusable → save as template the same turn.
213
+ - Scaffolding a new folder for a doc category → pair the rule (`folder_config({ action: "set-rule" })`) with a template in the same turn.
214
+ - The user describes a recurring doc shape ("we always log meetings with attendees, agenda, action items") → author the template once.
279
215
 
280
- - **You're onboarding an existing repo with 2+ siblings in a folder and no template yet.** Don't extract one folder at a time — invoke `discover` (Workflow tools table) for the multi-folder sweep. The bullets below apply during normal session work; `discover` is the bulk path.
281
- - **You're about to write a doc in a folder where no template fits, AND the shape you're about to use is reusable.** Save it as a template the same turn (`write_template` with the body you'd have hand-authored), then instantiate via `template:`. The first doc carries the same body either way; the difference is whether the next agent gets a menu entry or re-derives it from a sibling.
282
- - **You spot a sibling pattern in a folder that has no template.** Two or more docs sharing the same body skeleton (heading order, required sections, frontmatter shape) is enough — extract the skeleton via `write_template` so subsequent docs pick from `templates_available` instead of copying a sibling.
283
- - **You're scaffolding a new folder for a doc category.** Pair the folder rule (`set_folder_rule` for tags/title shape) with a template (`write_template` for body structure) in the same turn. Don't ship a folder-with-discipline-but-no-template — it leaves the next agent to invent the body each time.
284
- - **The user describes a recurring doc shape.** "We always log meetings with attendees, agenda, action items" → that's a template request whether or not the word "template" was used. Author it once.
285
-
286
- Authoring API (frontmatter requirements, substitution allowlist, `{shape}` semantics): see "Creating templates" below. When you create a template, briefly note it in chat ("saved this as a template at `meetings/.ok/templates/prep-notes.md` for next time") so the user understands the folder's discipline grew.
216
+ 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.
287
217
 
288
218
  ### When to declare folder defaults (MUST when a pattern emerges)
289
219
 
290
- If you find yourself writing the **same** frontmatter (tags, title prefix, description shape) on multiple sibling docs by hand, that's the signal to call `set_folder_rule` once and let the cascade do it. Pair it with a template (above) when the body skeleton repeats too — folder rules cover frontmatter; templates cover body shape.
291
-
292
- Repetition is the smell; folder rules and templates are the fix. Don't accumulate ad-hoc per-file frontmatter when one folder rule would carry it.
293
-
294
- **Bulk onboarding.** If most folders already have siblings but no frontmatter, invoke `discover` (Workflow tools table) for the multi-folder sweep instead of per-folder hand-application.
295
-
296
- ## Folder structure + metadata — nested `<folder>/.ok/`
297
-
298
- Folder defaults and templates live in opt-in nested `.ok/` directories — sparse, lazy-create, auto-clean. **Most folders have NO `.ok/`**. A folder gets one only when it declares its own frontmatter defaults or carries templates.
299
-
300
- ```
301
- content-root/
302
- ├── .ok/ ← project root .ok/ (config.yml, cache, etc.)
303
- ├── meetings/
304
- │ ├── .ok/ ← opt-in: this folder declares defaults + templates
305
- │ │ ├── frontmatter.yml
306
- │ │ └── templates/
307
- │ │ ├── prep-notes.md
308
- │ │ └── post-notes.md
309
- │ └── 2026-05-01.md
310
- └── research/ ← no .ok/ — declares nothing, inherits root cascade
311
- └── auth-providers.md
312
- ```
220
+ If you're writing the same frontmatter (tags, title prefix) on multiple siblings, call `folder_config({ action: "set-rule" })` once and let the cascade do it. Pair with a template when the body skeleton also repeats.
313
221
 
314
222
  ### Editing folder defaults
315
223
 
316
- Use the `set_folder_rule` MCP tool. It writes nested `<folder>/.ok/frontmatter.yml`:
317
-
318
224
  ```ts
319
- set_folder_rule({
225
+ folder_config({
226
+ action: "set-rule",
320
227
  rules: [
321
228
  { match: "meetings/**", frontmatter: { title: "Meetings", tags: ["meeting"] } },
322
229
  { match: "meetings/prep-notes/**", frontmatter: { tags: ["meeting", "prep"] } },
@@ -324,63 +231,73 @@ set_folder_rule({
324
231
  })
325
232
  ```
326
233
 
327
- Each `match` glob must resolve to a SINGLE target folder (leading literal segments + trailing `*`/`**`). Multi-folder globs like `specs/*/evidence/**` are rejected with `MULTI_FOLDER_GLOB` — split into one rule per folder.
328
-
329
- To remove a folder rule, pass an empty `frontmatter: {}` — the file is deleted and `.ok/` is auto-cleaned if no other tenant remains.
330
-
331
- Cascade rules (D6 — asymmetric per scalar vs list semantics):
332
-
333
- - **Scalars** (`title`, `description`): walk root → leaf, last-wins per key (leaf replaces ancestor).
334
- - **Tags**: union-and-dedup along the chain, first-occurrence preserved. Root `tags: [kb]` plus a leaf `tags: [spec]` produces `[kb, spec]` at the leaf — tags accumulate naturally; you don't need to redeclare ancestor tags at each level.
335
- - **File frontmatter** wins per-scalar over folder defaults; file tags union with the cascade.
234
+ 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.
336
235
 
337
236
  ### Creating templates
338
237
 
339
- Templates are markdown starter shapes. Use `write_template`:
340
-
341
238
  ```ts
342
- write_template({
239
+ folder_config({
240
+ action: "write-template",
343
241
  folder: "meetings/",
344
242
  name: "prep-notes",
345
243
  body: "# {Meeting Title}\n\n**Attendees:** \n**Date:** \n\n## Agenda\n- \n",
346
244
  frontmatter: {
347
- title: "Meeting Prep Notes", // MUST be present (D14 hard error if missing)
348
- description: "Use before a meeting.", // SHOULD be present (D14 — soft warning)
245
+ title: "Meeting Prep Notes", // REQUIREDTEMPLATE_TITLE_REQUIRED if missing
246
+ description: "Use before a meeting.", // recommended — soft warning if absent
349
247
  tags: ["meeting", "prep"],
350
248
  },
351
249
  })
352
250
  ```
353
251
 
354
- `title` is the menu surface agents pick by title, so it's required at write time (`TEMPLATE_TITLE_REQUIRED` if missing). `description` is recommended for menu disambiguation but produces only a warning when absent.
355
-
356
- **Substitution allowlist (D5).** Template bodies MAY use exactly two server-side substitutions: `{{date}}` (today's ISO-8601 date) and `{{user}}` (calling principal display name). Any other `{{...}}` token is rejected at write time with `TEMPLATE_UNKNOWN_VARIABLE`. Plain placeholder text in `{shape}` form (e.g., `{Meeting Title}`) is LITERAL — agents fill those in via subsequent `edit_document` calls. Substitution happens at instantiation time only; templates on disk show the raw `{{date}}` token.
357
-
358
- To delete a template: `delete_template({ folder, name })` — auto-cleans empty `.ok/templates/` and `.ok/`.
252
+ **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/`).
359
253
 
360
254
  ### Creating a doc from a template
361
255
 
362
- This is the default path when `templates_available` (from the pre-write checklist) shows a matching entry. Three steps:
363
-
364
256
  ```ts
365
- // 1. Inspect the menu (same call you already made in the pre-write checklist).
366
- list_documents("meetings/", { depth: 1 })
367
- // → templates_available: [{ name: "prep-notes", title: "Meeting Prep Notes", scope: "local" }, ...]
257
+ // Inspect the menu (already done in the pre-write checklist).
258
+ exec("ls -A meetings/")
259
+ // → templates_available: [{ name: "prep-notes", title: "Meeting Prep Notes", scope: "local" }, ...]
368
260
 
369
- // 2. Instantiate. `template` and `markdown` are mutually exclusive — pass `template`.
261
+ // Instantiate. `template` and `markdown` are mutually exclusive.
370
262
  write_document({
371
263
  docName: "meetings/2026-05-02-roadmap-sync",
372
264
  template: "prep-notes",
373
265
  position: "replace",
374
266
  })
375
267
 
376
- // 3. Fill the literal `{shape}` placeholders the template body declares
377
- // (e.g. "{Meeting Title}", "{Attendees}") via follow-up `edit_document`
378
- // calls — those are author-fill markers, not server-side substitutions.
268
+ // Fill the `{shape}` placeholders via follow-up edit_document calls.
379
269
  ```
380
270
 
381
- Templates resolve via leaf → root walk-up at the target's parent folder, with closest-wins on filename collision. The `scope` field has two values: `"local"` (template lives in this folder's `.ok/templates/`) and `"inherited"` (template lives in an ancestor's). Templates are always project-scopedthey live alongside the docs that consume them, so a clone of the project carries the same template menu on any machine. Descendant templates do NOT appear in the parent's array — they surface only inside `subfolders[].templates_available` when `list_documents` is called with `depth > 1`.
271
+ 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.
272
+
273
+ ### Editing frontmatter
274
+
275
+ `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.
276
+
277
+ ### Binary-source wrappers (`ingest`-produced)
278
+
279
+ Docs that wrap a co-located binary file under `external-sources/` carry extra frontmatter so the wrapper-binary pair is fully described:
280
+
281
+ ```yaml
282
+ ---
283
+ title: ...
284
+ description: ...
285
+ source_url: https://example.com/file.pdf
286
+ source_path: ./<slug>.<ext> # relative to this wrapper
287
+ media_type: application/pdf
288
+ bytes: 1234567
289
+ sha256: <64-char hex> # of the embedded binary
290
+ date_fetched: YYYY-MM-DD
291
+ preservation: binary # OR: text-only / text-extracted
292
+ supersedes: # OPTIONAL — dated-sibling re-ingest
293
+ - <prior-slug>.md
294
+ tags: [source, immutable, layer-ingest, binary]
295
+ ---
296
+
297
+ ![[<slug>.<ext>]]
298
+ ```
382
299
 
383
- **`template` and `markdown` are mutually exclusive** (D21). Passing both errors with `TEMPLATE_AND_MARKDOWN_BOTH_SET`. The template body becomes the new doc's body verbatim (after `{{date}}`/`{{user}}` substitution); fill `{shape}`-style placeholders via subsequent `edit_document` calls.
300
+ Body is just the wiki-embed. PDFs/opaque attachments render as a click-dispatching File row; `<Pdf src="./<slug>.pdf" />` is the opt-in inline viewer. See `ingest`'s tool body for full re-ingest / size / executable rules.
384
301
 
385
302
  ## Cadence
386
303
 
@@ -403,27 +320,27 @@ The skill carries the trigger ("KB content changed this turn — go look"). The
403
320
  | List a markdown-heavy dir | `Bash: ls specs/` | `exec("ls -A specs/")` |
404
321
  | Find all SPEC.md files | `Glob: **/SPEC.md` | `exec("find specs -name SPEC.md")` |
405
322
  | Find the most relevant page for a query | `Grep: "pattern" *.md` then read three files | `search({ query: "pattern" })` (ranked: title + body BM25 + recency) |
406
- | Find every literal occurrence of a phrase | `Grep: "pattern" *.md` | `grep({ query: "pattern" })` (literal, grouped by file, with frontmatter) |
407
- | Read an individual doc | `Read: specs/foo/SPEC.md` | `exec("cat specs/foo/SPEC.md")` or `read_document(...)` |
323
+ | Find every literal occurrence of a phrase | `Grep: "pattern" *.md` | `exec("grep -rn pattern <dir>")` (literal, grouped by file, with frontmatter) |
324
+ | Read an individual doc | `Read: specs/foo/SPEC.md` | `exec("cat specs/foo/SPEC.md")` |
408
325
  | Explore a markdown-heavy dir | `Agent(Explore): "..."` | Do `exec`-based exploration yourself |
409
326
  | 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 |
410
327
  | Ignore the attach hint | Skip the `warning: { action: "attach-preview-once" }` hint in write-tool responses | Open the `previewUrl` when the hint fires; otherwise do nothing |
328
+ | 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 |
411
329
  | Reference another doc | `` `[text](./page.md)` `` (backticked) or HTML `<a>` | `[text](./page.md)` (raw markdown) |
412
330
  | Embed an image | `<img src="...">` (HTML) or hot-linked external URL | Fetch + save locally + `![meaningful alt](./assets/images/path)` |
413
331
  | Write a factual claim in a KB doc | plausible prose without citation, OR inline `[source](https://URL)` | `ingest` the source first, then cite the local path per Grounding |
414
332
  | 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 |
415
333
  | 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 |
416
334
  | Add an image | empty alt `![](./x.png)` or generic alt `![image](./x)` | meaningful alt + source caption below |
417
- | Catalog folder contents | create `INDEX.md` hub file | `set_folder_rule({ rules: [{ match, frontmatter }] })` writes `<folder>/.ok/frontmatter.yml` |
418
- | Write a doc in an unfamiliar folder | go straight to `write_document` with hand-authored markdown | `list_documents(<folder>)` first — read `frontmatter_defaults` + `templates_available` before writing |
419
- | Land in an existing repo without orienting | go straight to `write_document` or run the per-folder pre-write checklist ad-hoc when no folder frontmatter / templates exist | invoke `discover` once for the project — it extracts conventions from siblings, sets folder frontmatter, writes templates, and activates the link graph |
335
+ | Catalog folder contents | create `INDEX.md` hub file | `folder_config({ action: "set-rule", rules: [...] })` writes `<folder>/.ok/frontmatter.yml` |
336
+ | Write a doc in an unfamiliar folder | go straight to `write_document` with hand-authored markdown | `exec("ls -A <folder>")` first — read `frontmatter_defaults` + `templates_available` before writing |
337
+ | 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 |
420
338
  | 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 |
421
- | Change a doc's title / tags | `edit_document` to swap the YAML (rejected — HTTP 400 frontmatter-intersect) | `frontmatter_patch({ docName, patch })` for 1-2 keys; `write_document({ position: "replace", markdown })` for full rewrites |
422
- | Repeat the same frontmatter on sibling docs | hand-set identical `tags` / `title` prefix on every new file | `set_folder_rule(...)` once — the cascade carries it to every child |
423
- | 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 |
424
- | Scaffold a new folder for a doc category | `set_folder_rule` for frontmatter and stop there | pair `set_folder_rule` with `write_template` in the same turn — discipline + body shape |
425
- | Author a reusable doc shape "just this once" | hand-author the body and move on | `write_template(...)` first, then instantiate slightly more setup, but durable for every subsequent doc |
426
- | Delete a markdown doc | `Bash: rm` / `unlink` / native deletion on in-scope `.md` | `delete_document` — `save_version` first if rollback may be needed |
339
+ | 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 |
340
+ | Repeat the same frontmatter on sibling docs | hand-set identical `tags` / `title` prefix on every new file | `folder_config({ action: "set-rule" })` once — the cascade carries it to every child |
341
+ | 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 |
342
+ | 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 |
343
+ | Delete a markdown doc | `Bash: rm` / `unlink` / native deletion on in-scope `.md` | `delete_document` `version({ action: "save" })` first if rollback may be needed |
427
344
  | Fork a skill and expect no stomp | Edit installed SKILL.md | `npx skills remove` before CLI upgrade |
428
345
 
429
346
  ## Workflow tools — when to invoke them
@@ -437,7 +354,7 @@ Three correspond to [Karpathy's three-layer knowledge-base pattern](https://gist
437
354
  | `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. |
438
355
  | `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. |
439
356
  | `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. |
440
- | `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. Requires `ok start` running (Phase 1 step 0 gates). Skip on empty repos (use `ok seed`). One-shot; idempotent on re-run. |
357
+ | `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. |
441
358
 
442
359
  **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`.
443
360