From c9887f0e08c0700e61454a254178e3ed8a189e81 Mon Sep 17 00:00:00 2001 From: ayushcodes10 Date: Thu, 17 Sep 2026 20:48:42 +0530 Subject: [PATCH 1/3] Let explicit ignore rules exclude memory notes for issue 3637 detect() always scanned the memory directory inside the output directory and bypassed every ignore check there, leaving no way to keep a specific saved query note out of the graph. A deliberate dot graphifyignore or exclude flag rule is now honored inside that directory, while a plain dot gitignore entry (the documented convention for the whole output directory) still cannot drop notes by default, matching the protection already given to the converted sidecar directory. Co-Authored-By: Claude Sonnet 5 --- graphify/detect.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/graphify/detect.py b/graphify/detect.py index eec8aaf1d2..747ae66f5b 100644 --- a/graphify/detect.py +++ b/graphify/detect.py @@ -1817,6 +1817,11 @@ def _ignored_for_scan(path: Path) -> bool: explicit_cache=explicit_ignore_cache, ) + def _explicitly_ignored_for_scan(path: Path) -> bool: + # Only .graphifyignore/--exclude rules, never a plain .gitignore entry + # (see the memory_dir handling below, #3637). + return _is_ignored(path, root, explicit_ignore_patterns, _cache=explicit_ignore_cache) + # Always include graphify-out/memory/ - query results filed back into the graph memory_dir = root / GRAPHIFY_OUT / "memory" scan_paths = [root] @@ -1856,6 +1861,21 @@ def _on_walk_error(err: OSError) -> None: if parent_real == real or parent_real.startswith(real + os.sep): dirnames.clear() continue + if in_memory_tree: + # Memory notes stay visible regardless of noise-dir rules or + # the graphify-out/ blanket .gitignore convention the docs + # recommend -- only a deliberate .graphifyignore/--exclude + # rule prunes here, never a plain .gitignore entry, so users + # get an actual way to keep specific notes out of the graph + # without losing the "memory is always visible" default (#3637). + kept_memory_dirs: list[str] = [] + for d in dirnames: + child = dp / d + if _explicitly_ignored_for_scan(child): + ignored.append(str(child) + os.sep) + continue + kept_memory_dirs.append(d) + dirnames[:] = kept_memory_dirs if not in_memory_tree: # dp == root was already loaded by _load_graphifyignore (root is # the last entry in its ancestor chain); every other directory @@ -1935,7 +1955,13 @@ def _on_walk_error(err: OSError) -> None: # Skip files inside our own converted/ dir (avoid re-processing sidecars) if str(p).startswith(str(converted_dir)): continue - if not in_memory and _ignored_for_scan(p): + if _ignored_for_scan(p): + ignored.append(str(p)) + continue + elif _explicitly_ignored_for_scan(p): + # #3637: same reasoning as the directory level prune above -- + # only a deliberate .graphifyignore/--exclude rule excludes a + # memory note, never the graphify-out/ blanket .gitignore entry. ignored.append(str(p)) continue if not _resolves_under_root(p, root): From d65117cacfebdd821750eb25ad0efe3543fab7bf Mon Sep 17 00:00:00 2001 From: ayushcodes10 Date: Thu, 17 Sep 2026 20:48:45 +0530 Subject: [PATCH 2/3] Add regression tests for memory dir ignore rules, issue 3637 Covers the default (a plain dot gitignore entry on the whole output directory still cannot drop a memory note), the new behavior (a deliberate dot graphifyignore rule can exclude one note or a whole subdirectory of notes), and a boundary check confirming a plain dot gitignore match alone is not treated as deliberate enough to exclude a note by itself. Co-Authored-By: Claude Sonnet 5 --- tests/test_detect.py | 65 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/tests/test_detect.py b/tests/test_detect.py index 22099029a5..431eb8c803 100644 --- a/tests/test_detect.py +++ b/tests/test_detect.py @@ -924,6 +924,71 @@ def fake_convert(path, out_dir, *, xlsx_to_markdown=None, root=None): assert result["files"]["document"][0].endswith("notes_converted.md") +def test_detect_memory_note_survives_a_gitignored_output_dir(tmp_path): + """#3637 baseline: memory notes still default to always visible even + when graphify-out/ is gitignored per the documented convention, exactly + like the converted/ sidecar case above -- a plain .gitignore entry on + the tool's own output dir must not silently drop query notes filed back + into the graph.""" + (tmp_path / ".gitignore").write_text("graphify-out/\n", encoding="utf-8") + memory_dir = tmp_path / "graphify-out" / "memory" + memory_dir.mkdir(parents=True) + (memory_dir / "note.md").write_text("# Query Memory\n\nSome saved answer.", encoding="utf-8") + + result = detect(tmp_path) + file_list = result["files"]["document"] + assert any("note.md" in f for f in file_list) + + +def test_detect_graphifyignore_excludes_specific_memory_note(tmp_path): + """#3637: a deliberate .graphifyignore rule targeting a memory note (or + subpath) is honored, giving users an actual way to keep a specific note + out of the graph -- the hard include only protects the default, it must + not make the directory unconditionally immune to explicit exclusion.""" + (tmp_path / ".graphifyignore").write_text("graphify-out/memory/scratch.md\n", encoding="utf-8") + memory_dir = tmp_path / "graphify-out" / "memory" + memory_dir.mkdir(parents=True) + (memory_dir / "scratch.md").write_text("# Scratch\n\nNot for the graph.", encoding="utf-8") + (memory_dir / "keep.md").write_text("# Keep\n\nA real saved answer.", encoding="utf-8") + + result = detect(tmp_path) + file_list = result["files"]["document"] + assert any("keep.md" in f for f in file_list) + assert not any("scratch.md" in f for f in file_list) + + +def test_detect_graphifyignore_excludes_memory_subdirectory(tmp_path): + """#3637: an explicit .graphifyignore rule can prune a whole subdirectory + of the memory dir, not just single files.""" + (tmp_path / ".graphifyignore").write_text("graphify-out/memory/drafts/\n", encoding="utf-8") + memory_dir = tmp_path / "graphify-out" / "memory" + drafts = memory_dir / "drafts" + drafts.mkdir(parents=True) + (drafts / "wip.md").write_text("# WIP\n\nUnfinished.", encoding="utf-8") + (memory_dir / "keep.md").write_text("# Keep\n\nA real saved answer.", encoding="utf-8") + + result = detect(tmp_path) + file_list = result["files"]["document"] + assert any("keep.md" in f for f in file_list) + assert not any("wip.md" in f for f in file_list) + + +def test_detect_memory_note_plain_gitignore_entry_is_not_authoritative(tmp_path): + """#3637: a plain .gitignore rule that happens to match a memory note + (as opposed to a deliberate .graphifyignore/--exclude rule) must NOT + exclude it -- only .graphifyignore/--exclude are treated as a deliberate + opt out inside the memory dir; a generic VCS .gitignore entry is not + strong enough evidence of intent to drop a query note by itself.""" + (tmp_path / ".gitignore").write_text("*.md\n", encoding="utf-8") + memory_dir = tmp_path / "graphify-out" / "memory" + memory_dir.mkdir(parents=True) + (memory_dir / "note.md").write_text("# Query Memory\n\nSome saved answer.", encoding="utf-8") + + result = detect(tmp_path) + file_list = result["files"]["document"] + assert any("note.md" in f for f in file_list) + + def test_detect_includes_video_key(tmp_path): """detect() result always includes a 'video' key even with no video files.""" (tmp_path / "main.py").write_text("x = 1") From e490cf0e64ac9ba0b27d3b4fc248f38a4e076431 Mon Sep 17 00:00:00 2001 From: ayushcodes10 Date: Thu, 17 Sep 2026 20:48:47 +0530 Subject: [PATCH 3/3] Add changelog entry for issue 3637 Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a0daf515f..802b3cb413 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu ## 0.9.63 (2026-09-16) +- Fix: `detect()` no longer treats `graphify-out/memory/` as unconditionally immune to every ignore rule. A plain `.gitignore` entry on the tool's own output dir still cannot drop a saved query note (the same protection the `converted/` sidecar directory already had), but a deliberate `.graphifyignore`/`--exclude` rule targeting a memory note or subpath is now honored, giving users an actual way to keep a specific note out of the graph (#3637, thanks @ayushcodes10). - Feature: Elixir `alias`/`import`/`require`/`use` targets now resolve onto the module's `defmodule` node across files, so the internal module dependency graph is no longer dropped as dangling. Only top-level modules are indexed (a nested `defmodule`, labeled with its bare inner name, cannot capture an unrelated `use ` from another file), and a same-file reference is left unresolved so it cannot clobber the structural `contains` edge (#3603, thanks @ayushcodes10). - Feature: a Rust `self.method()` call now resolves to a method defined on the same type in another file (the common split-`impl`-block layout), pooling methods across every `impl` of one type and refusing to link when two unrelated types share a bare name (#3602, thanks @ayushcodes10). - Feature: a Ruby member call `obj.foo` on a known-type receiver now resolves to a method `foo` inherited from a superclass, including across files, using the same conservative promotion as the implicit-self resolver — a single owning class, matching method kind, and one unambiguous ancestry chain, or it stays dangling (#3585, thanks @oleksii-tumanov).