feat(wiki): port upstream knowledge quality improvements
setup / install-leaves-tree-clean (push) Waiting to run
setup / install-leaves-tree-clean (push) Waiting to run
Selectively port source coverage and redirect lint, ignore-aware provenance, staging and trust checks from upstream 055d720. Preserve local archive metadata and adapt source-date and defuddle skill guidance. Keep the existing skills directory, file index and MCP interfaces. Record per-module upstream baselines and exclusions without migrating vault data or changing services. Validation: 625 tests passed; git diff --check passed.
This commit is contained in:
@@ -1065,3 +1065,127 @@ def test_lint_vault_warns_on_undated_counts_and_skips_dated_ones(tmp_path: Path)
|
||||
lines = page.read_text(encoding="utf-8").splitlines()
|
||||
assert all(lines[claim["line"] - 1].strip() == claim["text"] for claim in claims)
|
||||
assert report["status"] == "warn"
|
||||
|
||||
|
||||
def _write_ignored_archive(vault: Path) -> None:
|
||||
"""`_write_archive_and_manifest`, plus a second archive `.okignore` covers."""
|
||||
_write_archive_and_manifest(vault)
|
||||
hidden = vault / "_raw" / "_archived" / "private" / "hidden.md"
|
||||
hidden.parent.mkdir(parents=True, exist_ok=True)
|
||||
hidden.write_text("# clip\n", encoding="utf-8")
|
||||
manifest = json.loads((vault / ".manifest.json").read_text(encoding="utf-8"))
|
||||
manifest["sources"]["_raw/_archived/private/hidden.md"] = {"pages_produced": ["concepts/alpha.md"]}
|
||||
(vault / ".manifest.json").write_text(json.dumps(manifest), encoding="utf-8")
|
||||
(vault / ".okignore").write_text("_raw/_archived/private\n", encoding="utf-8")
|
||||
|
||||
|
||||
def test_snapshot_mismatch_does_not_name_an_ignored_archive(tmp_path: Path) -> None:
|
||||
vault = tmp_path / "vault"
|
||||
_clean_pair(vault)
|
||||
_write_ignored_archive(vault)
|
||||
|
||||
rows = lint_vault(vault)["findings"]["snapshot_mismatch"]
|
||||
|
||||
assert [row["expected"] for row in rows] == [["_raw/_archived/a.md"]]
|
||||
|
||||
|
||||
def test_a_page_citing_an_ignored_archive_is_not_a_mismatch(tmp_path: Path) -> None:
|
||||
vault = tmp_path / "vault"
|
||||
_clean_pair(vault, alpha_snapshots="[_raw/_archived/a.md, _raw/_archived/private/hidden.md]")
|
||||
_write_ignored_archive(vault)
|
||||
|
||||
assert lint_vault(vault)["findings"]["snapshot_mismatch"] == []
|
||||
|
||||
|
||||
def test_uncovered_sources_reports_sources_with_no_surviving_page(tmp_path: Path) -> None:
|
||||
vault = tmp_path / "vault"
|
||||
_clean_pair(vault)
|
||||
(vault / ".manifest.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"sources": [
|
||||
{"path": "docs/kept.md", "pages_produced": ["concepts/alpha.md"]},
|
||||
{"path": "docs/partly.md", "pages_produced": ["concepts/beta", "concepts/gone.md"]},
|
||||
{"path": "docs/empty.md", "pages_produced": []},
|
||||
{"path": "docs/lost.md", "pages_created": ["concepts/gone.md"]},
|
||||
{"path": "docs/counts-only.md", "pages_created": 3},
|
||||
]
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
report = lint_vault(vault)
|
||||
|
||||
assert report["findings"]["uncovered_sources"] == [
|
||||
{"source": "docs/empty.md", "issue": "no_pages", "missing": []},
|
||||
{"source": "docs/lost.md", "issue": "pages_missing", "missing": ["concepts/gone.md"]},
|
||||
]
|
||||
assert report["status"] == "warn"
|
||||
|
||||
|
||||
def _redirect_stub(vault: Path, relpath: str, target: str, *, title: str | None = None) -> Path:
|
||||
"""A wiki-dedup merge leftover: same title/stem as the canonical page,
|
||||
with a `redirects_to:` pointer in its frontmatter."""
|
||||
path = _page(vault, relpath, title=title)
|
||||
text = path.read_text(encoding="utf-8")
|
||||
path.write_text(
|
||||
text.replace("\n---\n", f'\nredirects_to: "[[{target}]]"\n---\n', 1),
|
||||
encoding="utf-8",
|
||||
)
|
||||
return path
|
||||
|
||||
|
||||
def test_lint_redirect_stub_is_not_a_duplicate(tmp_path: Path) -> None:
|
||||
vault = tmp_path / "vault"
|
||||
_page(vault, "concepts/example.md", title="Example")
|
||||
_redirect_stub(vault, "entities/example.md", "concepts/example", title="Example")
|
||||
|
||||
report = lint_vault(vault, require_trust_ledger=False)
|
||||
|
||||
assert report["findings"]["duplicate_titles"] == []
|
||||
assert report["findings"]["duplicate_stems"] == []
|
||||
assert report["findings"]["redirect_issues"] == []
|
||||
|
||||
|
||||
def test_lint_redirect_stub_with_missing_target_is_reported(tmp_path: Path) -> None:
|
||||
vault = tmp_path / "vault"
|
||||
_page(vault, "concepts/example.md", title="Example")
|
||||
_redirect_stub(vault, "entities/example.md", "concepts/gone", title="Example")
|
||||
|
||||
report = lint_vault(vault, require_trust_ledger=False)
|
||||
|
||||
assert report["findings"]["redirect_issues"] == [
|
||||
{"page": "entities/example.md", "target": "concepts/gone", "issue": "missing_target"}
|
||||
]
|
||||
# A broken stub still stays out of the duplicate checks.
|
||||
assert report["findings"]["duplicate_titles"] == []
|
||||
assert report["findings"]["duplicate_stems"] == []
|
||||
|
||||
|
||||
def test_lint_redirect_chain_is_reported(tmp_path: Path) -> None:
|
||||
vault = tmp_path / "vault"
|
||||
_page(vault, "concepts/example.md", title="Example")
|
||||
_redirect_stub(vault, "entities/example.md", "concepts/example", title="Example")
|
||||
_redirect_stub(vault, "references/example.md", "entities/example", title="Example")
|
||||
|
||||
report = lint_vault(vault, require_trust_ledger=False)
|
||||
|
||||
assert report["findings"]["redirect_issues"] == [
|
||||
{"page": "references/example.md", "target": "entities/example", "issue": "redirect_chain"}
|
||||
]
|
||||
|
||||
|
||||
def test_lint_two_real_pages_with_same_title_still_duplicate(tmp_path: Path) -> None:
|
||||
vault = tmp_path / "vault"
|
||||
_page(vault, "concepts/example.md", title="Example")
|
||||
_page(vault, "entities/example.md", title="Example")
|
||||
|
||||
report = lint_vault(vault, require_trust_ledger=False)
|
||||
|
||||
assert report["findings"]["duplicate_titles"] == [
|
||||
{"title": "example", "pages": ["concepts/example.md", "entities/example.md"]}
|
||||
]
|
||||
assert report["findings"]["duplicate_stems"] == [
|
||||
{"stem": "example", "pages": ["concepts/example.md", "entities/example.md"]}
|
||||
]
|
||||
|
||||
@@ -4,6 +4,7 @@ from pathlib import Path
|
||||
|
||||
from obsidian_wiki.provenance import (
|
||||
archive_wikilink_relpath,
|
||||
clip_url_index,
|
||||
expected_snapshots_for_page,
|
||||
invert_pages,
|
||||
parse_snapshots_field,
|
||||
@@ -218,3 +219,13 @@ def test_archive_wikilink_relpath_rejects_parent_traversal(tmp_path: Path) -> No
|
||||
vault = tmp_path / "vault"
|
||||
vault.mkdir()
|
||||
assert archive_wikilink_relpath(vault, "_raw/_archived/../attention") is None
|
||||
|
||||
|
||||
def test_clip_url_index_honors_okignore(tmp_path: Path) -> None:
|
||||
for rel, url in (("kept", "https://example.test/kept"), ("private/hidden", "https://example.test/hidden")):
|
||||
clip = tmp_path / "_raw" / "_archived" / f"{rel}.md"
|
||||
clip.parent.mkdir(parents=True, exist_ok=True)
|
||||
clip.write_text(f"---\nurl: {url}\n---\n\nclip\n", encoding="utf-8")
|
||||
(tmp_path / ".okignore").write_text("_raw/_archived/private\n", encoding="utf-8")
|
||||
|
||||
assert clip_url_index(tmp_path) == {"https://example.test/kept": ["_raw/_archived/kept.md"]}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from obsidian_wiki.staging import (
|
||||
@@ -200,3 +202,26 @@ def test_log_records_one_line_per_batch(vault):
|
||||
def test_log_is_silent_when_nothing_happened(vault):
|
||||
log_decisions(vault, [])
|
||||
assert not (vault / "log.md").exists()
|
||||
|
||||
|
||||
def test_list_staged_honors_okignore(tmp_path):
|
||||
write(tmp_path, "_staging/concepts/kept.md", "kept\n")
|
||||
write(tmp_path, "_staging/private/hidden.md", "hidden\n")
|
||||
# Staged over an ignored live page: listing it would diff against that page.
|
||||
write(tmp_path, "notes/old/live.md", "secret\n")
|
||||
write(tmp_path, "_staging/notes/old/live.md", "revised\n")
|
||||
write(tmp_path, ".okignore", "_staging/private\n/notes/old\n")
|
||||
|
||||
assert [Path(entry.live_path).as_posix() for entry in list_staged(tmp_path)] == ["concepts/kept.md"]
|
||||
|
||||
|
||||
def test_list_staged_does_not_follow_a_symlinked_directory(tmp_path):
|
||||
vault = tmp_path / "vault"
|
||||
write(vault, "_staging/concepts/kept.md", "kept\n")
|
||||
write(tmp_path, "outside/secret.md", "secret\n")
|
||||
try:
|
||||
(vault / "_staging" / "link").symlink_to(tmp_path / "outside", target_is_directory=True)
|
||||
except (OSError, NotImplementedError):
|
||||
pytest.skip("symlinks unavailable on this platform")
|
||||
|
||||
assert [Path(entry.live_path).as_posix() for entry in list_staged(vault)] == ["concepts/kept.md"]
|
||||
|
||||
@@ -86,6 +86,7 @@ def test_reviewed_ledger_is_authoritative_instead_of_reclassifying_sources(tmp_p
|
||||
"score_mismatches": 0,
|
||||
"missing_pages": 0,
|
||||
"errors": 0,
|
||||
"ignored_entries": 0,
|
||||
}
|
||||
assert {item["page"] for item in report["reviewed"]} == {
|
||||
"concepts/alpha.md",
|
||||
@@ -881,3 +882,50 @@ def test_required_trust_ledger_fails_closed_when_missing(tmp_path: Path) -> None
|
||||
|
||||
strict_report = lint_vault(vault, require_trust_ledger=True, strict_trust=True)
|
||||
assert strict_report["status"] == "fail"
|
||||
|
||||
|
||||
def _reviewed_then_ignored(tmp_path: Path) -> Path:
|
||||
vault = tmp_path / "vault"
|
||||
_page(vault, "concepts/alpha.md")
|
||||
_page(vault, "private/hidden.md")
|
||||
_write_ledger(vault)
|
||||
(vault / ".okignore").write_text("private/\n", encoding="utf-8")
|
||||
return vault
|
||||
|
||||
|
||||
def test_check_counts_a_reviewed_page_ignored_later_without_naming_it(tmp_path: Path) -> None:
|
||||
vault = _reviewed_then_ignored(tmp_path)
|
||||
|
||||
report = check_trust_ledger(vault)
|
||||
|
||||
# Still a warning: a page leaving the review gate has to stay visible.
|
||||
assert report["status"] == "warn"
|
||||
assert report["ignored_entries"] == 1
|
||||
assert report["counts"]["ignored_entries"] == 1
|
||||
assert report["missing_pages"] == []
|
||||
assert "hidden" not in json.dumps(report)
|
||||
|
||||
|
||||
def test_a_deleted_page_is_still_reported_missing_by_name(tmp_path: Path) -> None:
|
||||
vault = _reviewed_then_ignored(tmp_path)
|
||||
(vault / "concepts" / "alpha.md").unlink()
|
||||
|
||||
report = check_trust_ledger(vault)
|
||||
|
||||
assert report["missing_pages"] == [{"page": "concepts/alpha.md", "reason": "reviewed_page_missing"}]
|
||||
assert report["ignored_entries"] == 1
|
||||
|
||||
|
||||
def test_partial_review_prunes_ignored_entries_without_changing_other_reviews(tmp_path):
|
||||
vault = _reviewed_then_ignored(tmp_path)
|
||||
ledger_path = vault / "_meta/trust-ledger.json"
|
||||
before = json.loads(ledger_path.read_text())
|
||||
updated = update_trust_ledger(
|
||||
vault, ledger_path, reviewed_at="2026-11-07T00:00:00Z",
|
||||
page_paths=["concepts/alpha.md"],
|
||||
)
|
||||
assert updated["removed_ignored"] == 1
|
||||
assert set(updated["pages"]) == {"concepts/alpha.md"}
|
||||
assert updated["pages"]["concepts/alpha.md"]["material_fingerprint"] == before["pages"]["concepts/alpha.md"]["material_fingerprint"]
|
||||
write_trust_ledger(ledger_path, updated)
|
||||
assert check_trust_ledger(vault)["ignored_entries"] == 0
|
||||
|
||||
@@ -83,3 +83,31 @@ def test_staging_cli_conflict_keeps_reviewed_and_live_files(tmp_path, capsys):
|
||||
]) == 9
|
||||
assert live.read_text() == "Concurrent live update"
|
||||
assert staged.read_text() == "Reviewed proposal"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("shape", ["dict", "list"])
|
||||
def test_source_coverage_accepts_local_archive_metadata_and_preserves_manifest(tmp_path, shape):
|
||||
page = tmp_path / "concepts/a.md"
|
||||
page.parent.mkdir()
|
||||
page.write_text("---\ntitle: A\n---\n")
|
||||
entries = [
|
||||
{"path": "original.pdf", "archive_path": "_sources/archive/a.pdf",
|
||||
"pages_produced": ["concepts/a.md"], "bjd_part_numbers": ["BJD-123"]},
|
||||
{"path": "lost.pdf", "pages_updated": ["concepts/deleted.md"]},
|
||||
{"path": "legacy.pdf", "pages_created": 2},
|
||||
]
|
||||
manifest = tmp_path / ".manifest.json"
|
||||
manifest.write_text(json.dumps({"sources": {e["path"]: e for e in entries} if shape == "dict" else entries}))
|
||||
before = manifest.read_bytes()
|
||||
report = lint_vault(tmp_path, require_trust_ledger=False)
|
||||
assert report["findings"]["uncovered_sources"] == [
|
||||
{"source": "lost.pdf", "issue": "pages_missing", "missing": ["concepts/deleted.md"]}
|
||||
]
|
||||
assert manifest.read_bytes() == before
|
||||
|
||||
|
||||
def test_lint_cli_exposes_source_coverage_in_json_and_strict_exit(tmp_path, capsys):
|
||||
(tmp_path / ".manifest.json").write_text(json.dumps({"sources": {"lost.pdf": {"pages_produced": []}}}))
|
||||
assert main(["lint", str(tmp_path), "--json", "--strict"]) == 1
|
||||
report = json.loads(capsys.readouterr().out)
|
||||
assert report["stats"]["findings"]["uncovered_sources"] == 1
|
||||
|
||||
Reference in New Issue
Block a user