diff --git a/skills/core/open-knowledge/references/linking.md b/skills/core/open-knowledge/references/linking.md index 0344fe4..ee26f8a 100644 --- a/skills/core/open-knowledge/references/linking.md +++ b/skills/core/open-knowledge/references/linking.md @@ -10,7 +10,7 @@ - **Every link must resolve to a doc that exists by the time you're done.** Within a single multi-doc authoring pass, linking a page you'll create later in the same pass is fine — `brokenLinks` reports it as `no-such-doc` until the target lands, an expected transient forward-reference rather than a wrong-path error. What's not acceptable is *leaving* a dead link behind: create the target 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. - **Never wrap a link in backticks.** `` `[text](./foo.md)` `` is a bug — the backticks make it render as literal code rather than a link. - **Never use HTML anchors** (``). Markdown link syntax only. -- **`brokenLinks` on the write/edit response is your primary check — read it before walking away.** Every `write`/`edit` returns `brokenLinks` in the SAME payload: `[]` means every outbound link resolves only when `brokenLinkSuppression` is absent and `warnings` has no `link-check-deferred` entry (that entry means the server was still starting and checked nothing — that doc's links are re-checked only when you write or edit it again after startup, or by an `audit`, which can time out until then); a populated list names each broken `href` with its `resolvedTo` + `reason` (`no-such-doc` = resolved to a doc that doesn't exist; `no-such-file` = resolved to a linked asset / source file that isn't on disk at that path; `unresolvable` = the path escapes the content root, usually one `../` too many). This validates **every** local link, not just doc links: a wrong-depth `[src](../../foo.py)` to a source file is caught too. It is report-only — the write landed regardless; fix or remove every one in a follow-up `edit`. +- **`brokenLinks` on the write/edit response is your primary check — read it before walking away.** Every `write`/`edit` returns `brokenLinks` in the SAME payload: `[]` means every outbound link resolves only when `brokenLinkSuppression` is absent and `warnings` has no `link-check-deferred` entry (that entry means the server was still starting and checked nothing — that doc's links are re-checked only when you write or edit it again after startup, or by an `audit`, which can time out until then); a populated list names each broken `href` with its `resolvedTo` + `reason` (`no-such-doc` = resolved to a doc that doesn't exist; `no-such-file` = resolved to a linked asset / source file that isn't on disk at that path; `excluded` = the file is on disk but a `.gitignore` or `.okignore` rule keeps it out of the project, so re-include it or its folder with a `!` rule in `.okignore` (for example `!ignored/`) or link something else; `unresolvable` = the path escapes the content root, usually one `../` too many). This validates **every** local link, not just doc links: a wrong-depth `[src](../../foo.py)` to a source file is caught too. It is report-only — the write landed regardless; fix or remove every one in a follow-up `edit`. - **An empty `brokenLinks` alongside a `brokenLinkSuppression` does not mean every link resolves.** That field appears only when the project's reserved-log policy withheld findings, and it carries `{ reason: "reserved-log-policy", count: N }` and nothing else. No href comes back because none of them is repair work: the write landed in a lowercase-stemmed `log.md` or `log.mdx`, an append-only history whose entries deliberately reference pages that moved or were never written. **Never rewrite a reserved log's existing entries to clear a link.** Append new entries instead, and treat `links({ kind: "dead" })` on that file as a deliberate audit of raw state, never as a repair queue. The project can turn the policy off (**Settings ▸ This project ▸ Preferences ▸ Content rules**), in which case those findings come back through the normal channels. - **`audit` is the authoritative end-state link check** — run it once at the end of a multi-doc pass. Read `brokenLinkSuppression` on its result too: when present, the reserved-log policy withheld `count` findings, so a clean result is filtered rather than proof that every raw link resolves. Do not repair those log findings; use `links({ kind: "dead" })` only when deliberately inspecting raw state. Caveat: `audit` reports links only while the project's `validation.links` setting is not `off` (at `off` it returns clean with no warning), so if the project has silenced link validation, fall back to `links({ kind: "dead" })` — the unconditional read of the dead-link graph, which also lists skill-bundle docs excluded from the Problems plane (optionally scoped: `links({ kind: "dead", sourceDocuments: ["your/doc"] })`). Otherwise `links` is the graph-navigation reader, supplemental to `audit`, not a substitute. Companion `links` kinds: `backlinks` (incoming), `forward` (outgoing), `orphans` (no incoming), `hubs` (high-incoming), `suggest` (untextualized mentions worth linking). - **The tool and the editor agree.** Both resolve a bare name the same way — exact, then slug, then folder index, then basename — so a link that opens when you click it is never reported dead, and a link reported dead is genuinely unresolvable. A target that names an asset, or names nothing at all, stays silent rather than being reported as a broken doc link.