A god node labelled index() (an ordinary web view) or a community named
after one slugs to "index", so to_wiki wrote its article to index.md and
then replaced it with the catalog; the catalog entry linked back to the
catalog itself. Reserve the index slug in the dedup set, and link the
navigation footer to index.md directly so the label resolver can map an
"index" label to its own deduplicated file.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit aad5e0feab)
The wiki exporter wrote each article as {slug}.md but emitted the link as
quote({slug}.md), so any label with ( ) & # or non-ASCII produced a
percent-encoded target that names no file on disk. Removes the quote() fork
and hardens _safe_filename so the slug is URL-safe by construction; the link
and the on-disk filename are now the same string.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
to_wiki emitted Obsidian `[[Title]]` wikilinks. Outside Obsidian `[[Domain Data
Models]]` resolves to a literal "Domain Data Models.md", but the article file is
the slugged "Domain_Data_Models.md", so nearly every community/god-node link
opened an empty page; god-node articles also linked node-level neighbors that
never get an article file, so those were dead even inside Obsidian.
Links are now standard `[display](slug.md)` with the target URL-encoded (spaces,
&, parentheses, # survive in CommonMark and Obsidian alike); a link whose target
has no article is rendered as plain text instead of left dangling. A label->slug
resolver is built up front so a link points at the real on-disk filename incl. the
case-fold collision suffix.
Ported from PR #1465 by @TPAteeq. Maintainer edit: trimmed the CHANGELOG wording
that overstated the guarantee ("always matches ... collision suffix and all") —
two articles sharing a byte-identical label still keep the first slug (pre-existing
behavior, same as the old [[label]]), so the claim is scoped to the case-fold case.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
to_obsidian / to_canvas / to_wiki keyed filename dedup on the exact-case name,
so two labels differing only by case (e.g. `References` vs `references`) counted
as non-colliding and the second write clobbered the first on case-insensitive
filesystems (macOS/APFS, Windows/NTFS) — silently, no suffix, no warning.
Dedup now folds case (keyed on the lowercased name) while emitting the
original-case filename, so any pair that would collide on disk gets a numeric
suffix. The obsidian/canvas dedup is one shared helper (`_dedup_node_filenames`)
so they can't drift; wiki's slug dedup gets the matching fix; the `_COMMUNITY_*`
overview notes (which had no dedup at all) are covered; and a generated `base_1`
is re-checked so it can't overwrite a node literally labelled `base_1`.
Ported from PR #1457 by @TPAteeq onto current v8. Verified with a rigorous
edge-case battery (case-only collision, base_1 literal re-check -> base_1_1,
community-label case fold, determinism) plus the PR's tests; full suite 2404
passed, ruff + skillgen clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Security: _is_sensitive now flags underscore-prefixed names (api_token.txt, oauth_token.json) by replacing \b with lookarounds; adds _SENSITIVE_DIRS check on parent path components (parts[:-1]) so .ssh/, secrets/, .aws/ directories are always skipped; aligns both patterns to (?![a-zA-Z]) for consistent underscore-after-keyword behavior (#920)
Fix: --wiki Relationships section always empty because _cross_community_links read community from node attrs (always None) instead of the communities dict; _god_node_article had the same bug and never linked to the owning community; fixed by building a node->community map in to_wiki() and threading it through (#925)
Fix: --watch now respects .graphifyignore; patterns loaded once at startup, handler checks _is_ignored before extension filter so node_modules/, .venv/, build/ churn no longer triggers rebuilds (#928)
Fix: C++ struct inheritance edges via base_class_clause; initialize base="" per iteration to prevent stale carryover (#915)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>