Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ Versions below 1.0 are pre-production — API and file formats may change.

## [Unreleased] — post-v1.0 cleanup

### Fixed

- **`StaleCandidates` lint rule crashed with `NameError: name 'Path' is not defined`** (#51 follow-up) — the rule used `isinstance(page_path, Path)` without importing `Path`, so the `Lint + build seeded wiki` GH Actions job crashed on every push after #51 landed. Added `from pathlib import Path` inside the method (matching the existing lazy-import pattern). Regression test now exercises the rule against a seeded tmp_path wiki.
- **`tests/test_candidates.py` rejected by Python 3.9** (#51 follow-up) — line 55 nested an f-string with `\n` inside an outer f-string expression; Python 3.9 rejects backslashes inside f-string parts (only 3.12+ permits it), breaking `lint-and-test (3.9)` CI. Extracted the default body into a local variable before interpolation.

### Added

- **`wiki/candidates/` approval workflow** (#51) — new `llmwiki/candidates.py` module with `list`, `promote`, `merge`, `discard`, and `stale_candidates` primitives. New pages from `/wiki-ingest` that represent brand-new entities/concepts can now land in `wiki/candidates/<kind>/<slug>.md` with `status: candidate` instead of going straight into the trusted wiki. `/wiki-review` slash command (`.claude/commands/wiki-review.md`) + `llmwiki candidates <action>` CLI walk through the queue. Merge folds the candidate's body under a `## Candidate merge — <date>` heading in the target and archives the source. Discard moves to `wiki/archive/candidates/<timestamp>/` with a timestamped `.reason.txt` audit file. New `stale_candidates` lint rule (12th overall) flags candidates sitting idle > 30 days. 34 tests cover: all 4 action paths, frontmatter status rewrite, staleness computation, kind inference, error handling.
Expand Down
1 change: 1 addition & 0 deletions llmwiki/lint/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ class StaleCandidates(LintRule):
STALE_DAYS = 30

def run(self, pages, *, llm_callback=None):
from pathlib import Path
from llmwiki.candidates import stale_candidates, candidates_dir
# load_pages gives us the real wiki dir from page[path]
issues = []
Expand Down
6 changes: 5 additions & 1 deletion tests/test_candidates.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,13 @@ def _write_candidate(
) -> Path:
path = wiki / "candidates" / kind / f"{slug}.md"
title = title or slug
# Python 3.9 disallows backslashes inside f-string expressions, so build
# the default body first and interpolate via a plain name.
default_body = f"# {title}\n\nCandidate body."
body_text = body or default_body
path.write_text(
f'---\ntitle: "{title}"\ntype: {kind[:-1]}\nstatus: candidate\n'
f'last_updated: {date}\n---\n\n{body or f"# {title}\\n\\nCandidate body."}\n',
f'last_updated: {date}\n---\n\n{body_text}\n',
encoding="utf-8",
)
return path
Expand Down
31 changes: 31 additions & 0 deletions tests/test_lint_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,37 @@ def test_registered_rule_names():
assert set(REGISTRY.keys()) == expected


# ─── 12. StaleCandidates — regression for Path import (#51 follow-up) ─


def test_stale_candidates_rule_runs_without_nameerror(tmp_path: Path):
"""Regression: the rule referenced `Path` without importing it, so
running it against any real page raised NameError (discovered when
the GH Actions seeded-wiki job failed). Keep this test — if the
import is dropped again it reproduces immediately.
"""
from llmwiki.lint.rules import StaleCandidates

# Build one page that looks like it came from load_pages()
wiki = tmp_path / "wiki"
(wiki / "entities").mkdir(parents=True)
entity = wiki / "entities" / "Sample.md"
entity.write_text(
"---\ntitle: Sample\ntype: entity\n---\n\nBody.\n", encoding="utf-8"
)
pages = {
"entities/Sample.md": {
"path": entity,
"rel": "entities/Sample.md",
"text": entity.read_text(encoding="utf-8"),
"meta": {"title": "Sample", "type": "entity"},
"body": "Body.\n",
}
}
# Should return an empty list (no candidates seeded) instead of raising.
assert StaleCandidates().run(pages) == []


# ─── 1. FrontmatterCompleteness ──────────────────────────────────────


Expand Down
Loading