fix(export)+test: all-dots Obsidian label falls back to unnamed; .env.example regression test (#2205, #2184)

Follow-ups on the cherry-picked #2242/#2232: an all-dots label ('...') no
longer produces an empty 'dot-' Obsidian stem (falls back to 'unnamed'),
and the .env.example carve-out gets the regression test it shipped without
(templates graphable, real .env still sensitive, secrets/.env.example still dropped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
safishamsi
2026-07-28 10:18:01 +01:00
co-authored by Claude Opus 4.8
parent 2099873ae6
commit 829224acd0
3 changed files with 54 additions and 2 deletions
+5 -2
View File
@@ -444,8 +444,11 @@ def _obsidian_safe_stem(label: str) -> str:
label.replace("\r\n", " ").replace("\r", " ").replace("\n", " "),
).strip()
cleaned = re.sub(r"\.(md|mdx|qmd|markdown)$", "", cleaned, flags=re.IGNORECASE)
# Obsidian treats a leading-dot filename as a hidden file (#2205).
if cleaned.startswith("."):
# Obsidian treats a leading-dot filename as a hidden file (#2205). Only
# prefix when something nameable remains after the dots: an all-dots label
# like "..." would otherwise become the meaningless stem "dot-" instead of
# falling through to the "unnamed" guard below (#1409).
if cleaned.startswith(".") and re.search(r"\w", cleaned.lstrip("."), flags=re.UNICODE):
cleaned = "dot-" + cleaned.lstrip(".")
# A stem of only punctuation (e.g. "@", "*", "#") survives the unsafe-char
# strip above but is empty once a downstream tool re-slugs on word chars
+39
View File
@@ -2530,3 +2530,42 @@ def test_sensitive_bare_keyword_prose_still_dropped():
assert _is_sensitive(Path("secrets.md"))
assert _is_sensitive(Path("token.rst"))
assert not _is_sensitive(Path("token-lifecycle.md")) # multi-word slug indexed
# ── #2232 / #2184: committed dotenv templates (.env.example etc.) are graphable ──
@pytest.mark.parametrize("path", [
".env.example",
".env.sample",
".env.template",
".env.dist",
".ENV.EXAMPLE", # case-insensitive, real on macOS/Windows
".envrc.sample", # direnv template
".env.production.example", # per-environment template
])
def test_sensitive_filter_indexes_env_templates(path):
"""Placeholder-only committed templates must not be treated as live secrets."""
assert not _is_sensitive(Path(path)), f"{path} is a committed template, must be indexed (#2184)"
@pytest.mark.parametrize("path", [
".env",
".env.local",
".env.production",
".envrc",
".env.example.local", # template suffix not final -> a real local override
".env.example.bak", # backup of a (possibly filled-in) env file
])
def test_sensitive_filter_still_excludes_real_env_files(path):
"""The template carve-out is suffix-anchored; live env files stay excluded."""
assert _is_sensitive(Path(path)), f"{path} is a live env file, must stay excluded (#2184)"
@pytest.mark.parametrize("path", [
"secrets/.env.example",
"deploy/credentials/.env.example",
])
def test_sensitive_env_template_inside_secrets_dir_still_dropped(path):
"""Stage 1 dir guard runs before the Stage 2 template exemption: anything
under a secrets/credentials dir stays excluded, template suffix or not."""
assert _is_sensitive(Path(path)), f"{path} is under a secrets dir, must stay excluded (#2184)"
+10
View File
@@ -459,6 +459,16 @@ def test_to_obsidian_leading_dot_labels_are_not_hidden_filenames():
assert not any(s.startswith(".") for s in file_stems), file_stems
def test_obsidian_safe_stem_all_dots_label_falls_back_to_unnamed():
"""#2205 follow-up: the `dot-` prefix only applies when a word char survives
the dot strip. An all-dots label like "..." must hit the #1409 "unnamed"
fallback, not produce the meaningless stem "dot-"."""
from graphify.export import _obsidian_safe_stem
assert _obsidian_safe_stem(".env") == "dot-env" # #2205 fix unchanged
assert _obsidian_safe_stem("...") == "unnamed" # not "dot-"
assert _obsidian_safe_stem("Database") == "Database" # normal labels untouched
# ── Existing-vault safety: graphify must not clobber user notes / .obsidian (#1506) ──
def _two_node_graph():