| name | wiki-fix-links |
| description | Resolve dead [[wikilinks]] in the wiki — find every link that points to no page, research the target in the source repos, and create the missing page so no link is empty. Use when the user runs /wiki-fix-links, asks to "fix dead links", "fill empty wikilinks", "find broken links", "make all links resolve", or "no empty links". Standalone version of wiki-sync's resolve-links step; does NOT re-scan repos or advance scan state. |
/wiki-fix-links — make every wikilink resolve to a file
The one job: zero empty [[links]] — in BOTH resolvers. A link is "empty" two different
ways and you must fix both:
- No target page — the link matches no page
title/alias. Fix: research + create the
missing page (or a flagged draft stub).
- Target page exists but Obsidian fades it — the page is there, but Obsidian resolves
[[X]] by filename or alias (not frontmatter title), and our filenames are kebab slugs
(order-hold.md) while titles are human-readable (Order Hold). So [[Order Hold]]
resolves in the wiki engine yet shows faded in Obsidian. Fix: add the page's title to its
own aliases (do NOT rename files). This is why dead-links can say 0 while the vault is
full of faded links — see step 1b.
This is exactly wiki-sync's resolve-links step, runnable on its own — no repo scan, no
.sync-state.json change. Read CLAUDE.md first; obey its §8 invariants (especially §8.8 No
dead links — in both resolvers, §4 Filenames & link resolution, §7 one concept, one page).
Arguments
- (none) — resolve every dead link found.
--dry-run — just list the dead links + the plan; write nothing.
Procedure
1. Find the dead links (deterministic)
bash scripts/sync.sh dead-links
Table → stderr (human), JSON envelope → stdout. The envelope:
dead_count, available_clones, dead_links[].
- Each
dead_links[] item: target (+ displays), occurrences, linked_from[] (each with
title/type/owner/source_repos/source_refs and a prose excerpt around the link),
research_repos / research_refs (where to research), related_known_concepts (siblings).
If dead_count: 0 → there are no missing-page links, but you are not done — go to 1b.
If --dry-run → show the table + your intended action per target, then stop.
1b. Fix Obsidian-faded links (title not in its own aliases)
dead-links only catches missing pages. It does not catch pages whose [[Title]] fades in
Obsidian because the title isn't a filename or alias. Sweep for those: every page whose
title differs from its filename slug must carry that title in its aliases.
python3 scripts/validate.py # fails any page whose title is not Obsidian-resolvable
For each offending page, add its title as the first entry of aliases (keep the kebab
filename — never rename to Title Case; that churns git and breaks path refs). New pages get this
automatically from new-page.py; this step repairs hand-edited or legacy pages. Re-run
validate.py until it reports no such failures. Then continue (if dead_count was also 0 and
this sweep is clean, skip to step 5 to rebuild + confirm).
2. Make sure the clones are present (to research, not just stub)
Fixing well needs the source repos under .state/sources/. Check available_clones:
3. Resolve each dead link (JUDGMENT)
For every dead_links[] item, end with a real file:
- Research. Read
research_refs files and grep research_repos clones under
.state/sources/<repo>/ for the target. If research_repos is empty (linker had no source),
grep the related_known_concepts' repos, then all clones. Use the excerpt + siblings to
disambiguate the meaning.
- Decide quality (high-confidence auto-create):
- Confident + sourced → full page. Anchor
source_refs to where it's defined; if it's
an external/un-onboarded service (used here, defined in a repo not in repos.yml), anchor
to the consumer usage, tag confidence/inferred, and say so under "Source of truth".
- Thin / ambiguous → draft stub (
status: draft, stub: true) so the link resolves;
flag it in the report as "needs human detail". Never leave a link dead.
- Owner: the target's real owner if research reveals it (CODEOWNERS of its defining path);
else inherit the linking page's
owner.
- Pick the type (concept | standard | decision | scenario | glossary) and the right folder.
Before creating, check the target isn't just an un-aliased name of an existing page — if
so, add it to that page's
aliases instead of making a new file (one concept, one page). If
the dead link is genuine noise (typo, one-off), fix it by editing/removing the link in the
source page — don't mint a junk page. Note such cases in the report.
- Create the file via the writer (guarantees valid frontmatter + GENERATED markers):
echo '<spec-json>' | python3 scripts/new-page.py
Spec: title, type, status, owner, aliases, tags (reuse the vocab —
python3 scripts/validate.py --tags), source_refs (first = source of truth), source_repos,
verified_refs, summary (H1 blockquote), sections: [{heading, body}], stub (bool),
today (YYYY-MM-DD). Returns {created} or .
4. Cascade until clean
New pages can introduce new links — which may themselves be dead. Re-run
bash scripts/sync.sh dead-links and repeat step 3 until dead_count: 0.
5. Rebuild the link graph + validate (deterministic)
The new pages must be wired into backlinks/MOCs and validated. Don't run a full sync; run just
the sweep + rendering + validation:
python3 scripts/extract-candidates.py --sweep
python3 scripts/build-backlinks.py
python3 scripts/render-mocs.py
python3 scripts/validate.py
(The --sweep finds the NEW pages' mentions in any already-scanned clones under
.state/sources/ — without it their "Mentioned in source" blocks stay empty until the next
sync. No clones on this machine → it's a cheap no-op.)
validate.py must end 0 error(s) — in particular no dead wikilink and no title that
isn't Obsidian-resolvable (the 1b check). If it still reports dead links, you missed one (or a
new page added one): go back to step 1. If it reports an unresolvable title, go back to 1b. Do
not edit any GENERATED block by hand.
This skill never advances .sync-state.json and never commits — it's link maintenance, not a
repo sync. Run /wiki-sync for the full scan-and-refresh cycle.
6. Report
Scannable summary: dead links found, pages created (full vs draft stubs needing human
detail), pages given a title-alias for Obsidian (step 1b), aliases added instead of new
pages, links unlinked as noise, repos you had to fetch, and confirmation that dead_count is 0
and validate is clean (both resolvers).
Hard rules (see CLAUDE.md §8)
Never hand-edit generated blocks or .backlinks.json. Every page has an owner. No secrets.
No implementation dumps. Forward links only. One concept, one page. Every [[link]] resolves
to a file — in both the wiki engine AND Obsidian (title-in-aliases; never rename files).