Files
ar9av__obsidian-wiki/tests/conftest.py
Mike Tsai fd30720223 feat: improve project ingest with optional code-graph-assisted understanding (#167) (#169)
* test: add code-understanding fixtures (fake codegraph CLI, repo builder)

* feat: add code_understanding module with builtin + codegraph providers

Provider abstraction behind code_understand(): builtin uses the regex
ast_extractor plus rg cross-file references (dependency-free fallback);
codegraph shells out to the codegraph CLI with defensive JSON parsing.
Focus-map output is ranked symbols with file:line citations, capped by
max_symbols, and never contains source bodies.

* feat(cli): wire code-understand command and doctor code-understanding checks

New 'obsidian-wiki code-understand --project' subcommand emits the focus
map JSON; doctor --project gains 5 code-understanding capability checks
(builtin, rg, codegraph, codegraph-index, codegraph-fresh). Missing
optional CodeGraph reports warn (exit 0); explicit CODE_UNDERSTANDING_
BACKEND=codegraph with a broken backend fails (exit 1).

* feat(skills): add optional code-understanding focus-map step to wiki-update

Step 3b runs 'obsidian-wiki code-understand --since <last_commit_synced>'
between deciding what to distill and distilling. GUARD-soft-fails like
wiki-ingest Step 1c; mandates file:line citations for architectural
claims and a pruning rule that removes relationships no longer
supported by the codebase. Registers CODE_UNDERSTANDING_BACKEND and
CODE_UNDERSTANDING_CODEGRAPH_BIN in the llm-wiki env reference.

* docs: document CODE_UNDERSTANDING_BACKEND and code-understand command

* docs: add CodeGraph install steps; teach wiki-update to offer install

configuration.md gains a Setup subsection under Code understanding
(npm install -g @colbymchenry/codegraph + verify + CODE_UNDERSTANDING_
CODEGRAPH_BIN fallback). wiki-update Step 3b gains an optional offer:
when codegraph is missing and the user wants the enhanced backend, the
agent may install it (with consent) and re-run the step.

* docs(readme): note code-graph-aware /wiki-update (EN + zh-TW parity)

* docs(readme): clarify zh-TW /wiki-update codegraph note

* fix(codegraph): cap impact queries at 25 seeds on full-project scans

Full scans (no --changed/--since) seed from every tracked file, so an
unbounded impact loop fired one subprocess per symbol — hundreds of
codegraph CLI calls on real repos (observed 120s+ timeout on a 4.3k-node
project). Cap impact at 25 seeds, matching the existing callers/callees
cap (seeds[:5]), keeping full scans bounded while --changed/--since
delta scans stay exhaustive for their small seed sets.

Regression test: test_codegraph_caps_impact_queries_on_full_scan pins the
25-call ceiling (RED: 30 calls -> GREEN: <= 25).

* feat(skills): add standalone code-understand skill

Exposes the code-graph-assisted focus map directly to any coding
agent session, not just as wiki-update Step 3b — so it can be invoked
on demand ("help me understand this codebase", "what breaks if I
change X") independent of a wiki sync.

---------

Co-authored-by: ar9av <ar9avg@gmail.com>
2026-08-15 12:48:11 -07:00

118 lines
4.3 KiB
Python

"""Shared pytest test fixtures for the obsidian-wiki test suite.
These are plain module-level helper functions (not @pytest.fixture) so their
signatures are explicit and tests call them directly:
from conftest import make_project, _run_cli
They support the issue #167 code-graph backend tests: building throwaway git
projects, a fake codegraph CLI binary, and .codegraph index state.
"""
from __future__ import annotations
import json
import os
import stat
import subprocess
import sys
import time
from pathlib import Path
def make_project(tmp_path: Path, files: dict[str, str], *, git: bool = True) -> Path:
"""Write a mini source tree from files (relpath -> content) and, if git,
commit it as an initial commit (needed for --since tests)."""
project = tmp_path / "project"
project.mkdir(parents=True, exist_ok=True)
for relpath, content in files.items():
path = project / relpath
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
if git:
_git(project, "init", "-q")
_git(project, "config", "user.email", "test@example.com")
_git(project, "config", "user.name", "Wiki Tester")
_git(project, "add", "-A")
_git(project, "-c", "commit.gpgsign=false", "commit", "-q", "-m", "initial commit")
return project
def make_fake_codegraph_bin(tmp_path: Path) -> Path:
"""Copy tests/fixtures/codegraph_fake.py into a bin dir, chmod +x, and
return the executable path (to pass as CODE_UNDERSTANDING_CODEGRAPH_BIN)."""
bin_dir = tmp_path / "bin"
bin_dir.mkdir(parents=True, exist_ok=True)
src = Path(__file__).parent / "fixtures" / "codegraph_fake.py"
dest = bin_dir / "codegraph"
dest.write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
dest.chmod(dest.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
return dest
def build_index_state(project: Path, *, fresh: bool = True) -> None:
"""Create project/.codegraph/codegraph.db (empty; mtime matters) plus
source.json. If fresh=False, backdate codegraph.db to 2000-01-01 so it
is older than the tracked source files (stale index)."""
codegraph_dir = project / ".codegraph"
codegraph_dir.mkdir(parents=True, exist_ok=True)
(codegraph_dir / "codegraph.db").write_bytes(b"")
(codegraph_dir / "source.json").write_text(
json.dumps({"sourceDir": str(project.resolve()), "version": 1}),
encoding="utf-8",
)
if not fresh:
stale = time.mktime((2000, 1, 1, 0, 0, 0, 0, 0, -1))
os.utime(codegraph_dir / "codegraph.db", (stale, stale))
def _run_cli(
cwd: Path,
*args: str,
env_overrides: dict[str, str] | None = None,
) -> subprocess.CompletedProcess[str]:
"""Run `python -m obsidian_wiki.cli <args>` in cwd with the current
environment merged with env_overrides."""
env = os.environ.copy()
if env_overrides:
env.update(env_overrides)
return subprocess.run(
[sys.executable, "-m", "obsidian_wiki.cli", *args],
capture_output=True,
text=True,
cwd=cwd,
env=env,
)
def make_repo_two_commits(tmp_path: Path) -> tuple[Path, str]:
"""A project with 2 commits: first adds src/foo.py + src/only_first.py,
second modifies foo.py and adds src/bar.py (only_first.py untouched).
Returns (project, sha_of_first_commit) for --since delta tests.
The untouched third file makes the delta set ({foo, bar}) strictly
smaller than the full tracked set ({foo, bar, only_first}), so tests
actually prove --since filtering rather than passing vacuously."""
project = make_project(
tmp_path,
{
"src/foo.py": "def foo():\n return 1\n",
"src/only_first.py": "x = 1\n",
},
)
sha_first = _git(project, "rev-parse", "HEAD").stdout.strip()
(project / "src/foo.py").write_text("def foo():\n return 2\n", encoding="utf-8")
(project / "src/bar.py").write_text("def bar():\n return 3\n", encoding="utf-8")
_git(project, "add", "-A")
_git(project, "-c", "commit.gpgsign=false", "commit", "-q", "-m", "second commit")
return project, sha_first
def _git(project: Path, *args: str) -> subprocess.CompletedProcess[str]:
return subprocess.run(
["git", *args],
capture_output=True,
text=True,
cwd=project,
check=True,
)