Skip to content

Commit 829f03f

Browse files
feat(staleness): add check_workspace_staleness convenience helper + bump 0.14.0 (#39)
Adds a thin public wrapper that locates `.help/` under a workspace, loads the manifest, and runs check_staleness — returning an empty StalenessReport when no manifest is present. Motivates the attune-gui staleness model fix: the GUI currently uses mtime > 14 days as a stale signal and disagrees with attune-author's content-drift definition. With this helper, the GUI can call one function with just a workspace path instead of duplicating the manifest-load + `.help/` convention. Also fixes the stale __version__ string in __init__.py (was pinned at 0.11.1 through the 0.12/0.13 releases). 3 new tests cover: parity with the direct API, empty-report-on-missing manifest, and features= filter passthrough. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent ce31103 commit 829f03f

5 files changed

Lines changed: 89 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@ changes land, not at tag time.
1515

1616
### Added
1717

18+
- **`check_workspace_staleness` public helper.** New convenience
19+
wrapper that takes a workspace path, locates `.help/` under it,
20+
loads the manifest, and runs `check_staleness` — returning an
21+
empty `StalenessReport` when no manifest is present. Lets
22+
downstream tools (attune-gui, future CLIs) ask "is this
23+
workspace's templates stale?" without duplicating the
24+
manifest-load + `.help/` convention. The underlying
25+
`check_staleness` API is unchanged. Exported from
26+
`attune_author` top-level. Also fixes the stale
27+
`__version__` string in `__init__.py` (was pinned at `0.11.1`
28+
through the 0.12/0.13 releases).
29+
1830
- **Polish fact-check Phase 4 — tutorial code-fence static
1931
check.** Polished tutorials (`docs/tutorials/*.md`) now have
2032
their ```python code fences extracted, syntax-checked with

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "attune-author"
7-
version = "0.13.0"
7+
version = "0.14.0"
88
description = "Documentation authoring and maintenance for the attune ecosystem — generate, maintain, and validate help content with AI assistance."
99
readme = {file = "README.md", content-type = "text/markdown"}
1010
requires-python = ">=3.10"

src/attune_author/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@
55
attune-help (reader) and attune-ai (full dev workflows).
66
"""
77

8-
__version__ = "0.11.1"
8+
__version__ = "0.14.0"
99

1010
from attune_author.manifest import Feature, Manifest, load_manifest
11-
from attune_author.staleness import StalenessReport, check_staleness, compute_source_hash
11+
from attune_author.staleness import (
12+
StalenessReport,
13+
check_staleness,
14+
check_workspace_staleness,
15+
compute_source_hash,
16+
)
1217

1318
__all__ = [
1419
# Manifest
@@ -18,5 +23,6 @@
1823
# Staleness
1924
"StalenessReport",
2025
"check_staleness",
26+
"check_workspace_staleness",
2127
"compute_source_hash",
2228
]

src/attune_author/staleness.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from dataclasses import dataclass, field
2323
from pathlib import Path
2424

25-
from attune_author.manifest import Feature, FeatureManifest, is_safe_feature_name
25+
from attune_author.manifest import Feature, FeatureManifest, is_safe_feature_name, load_manifest
2626

2727
logger = logging.getLogger(__name__)
2828

@@ -456,6 +456,40 @@ def check_staleness(
456456
return StalenessReport(help_entries=help_entries, doc_entries=doc_entries)
457457

458458

459+
def check_workspace_staleness(
460+
workspace: str | Path,
461+
features: list[str] | None = None,
462+
) -> StalenessReport:
463+
"""Check staleness for a workspace using the conventional ``.help/`` layout.
464+
465+
Convenience wrapper for callers that just have a project root and
466+
want the answer to "are any templates/docs out of date?" without
467+
knowing the manifest loader or where ``.help/`` lives. The workspace
468+
serves as both the project root (for resolving source globs) and
469+
the parent of ``.help/`` (for the manifest and template hashes).
470+
471+
If ``<workspace>/.help/features.yaml`` is absent, returns an empty
472+
report rather than raising — a workspace without a manifest has no
473+
templates to be stale about.
474+
475+
Args:
476+
workspace: Project root containing ``.help/`` (typically a git
477+
repo root).
478+
features: Optional list of feature names to check. Defaults to
479+
all features in the manifest.
480+
481+
Returns:
482+
StalenessReport (empty if no manifest is present).
483+
"""
484+
root = Path(workspace)
485+
help_dir = root / ".help"
486+
try:
487+
manifest = load_manifest(help_dir)
488+
except FileNotFoundError:
489+
return StalenessReport()
490+
return check_staleness(manifest, help_dir, root, features=features)
491+
492+
459493
def _infer_kind(feat: Feature, path_field: str) -> str:
460494
"""Infer the doc kind for a path field.
461495

tests/test_staleness.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
StalenessReport,
1313
_read_frontmatter_value,
1414
check_staleness,
15+
check_workspace_staleness,
1516
compute_source_hash,
1617
parse_doc_footer,
1718
)
@@ -118,6 +119,38 @@ def test_filter_by_feature_name(self, help_dir: Path, project_root: Path) -> Non
118119
assert report.help_entries[0].feature == "auth"
119120

120121

122+
class TestCheckWorkspaceStaleness:
123+
"""Tests for check_workspace_staleness() convenience helper."""
124+
125+
def test_matches_direct_call(self, help_dir: Path, project_root: Path) -> None:
126+
"""Helper produces the same report as the explicit-args API."""
127+
# help_dir fixture creates .help/ under tmp_path; project_root == tmp_path.
128+
manifest = load_manifest(help_dir)
129+
direct = check_staleness(manifest, help_dir, project_root)
130+
via_helper = check_workspace_staleness(project_root)
131+
132+
assert via_helper.stale_count == direct.stale_count
133+
assert sorted(e.feature for e in via_helper.help_entries) == sorted(
134+
e.feature for e in direct.help_entries
135+
)
136+
137+
def test_empty_report_when_no_manifest(self, tmp_path: Path) -> None:
138+
"""A workspace without .help/features.yaml yields an empty report, not a raise."""
139+
report = check_workspace_staleness(tmp_path)
140+
141+
assert isinstance(report, StalenessReport)
142+
assert report.help_entries == []
143+
assert report.doc_entries == []
144+
assert report.stale_count == 0
145+
146+
def test_propagates_feature_filter(self, help_dir: Path, project_root: Path) -> None:
147+
"""features= filter narrows the report just like the underlying API."""
148+
report = check_workspace_staleness(project_root, features=["auth"])
149+
150+
assert len(report.help_entries) == 1
151+
assert report.help_entries[0].feature == "auth"
152+
153+
121154
class TestProjectDocFooterStaleness:
122155
"""Integration: project-doc generator writes a footer that check_staleness reads.
123156

0 commit comments

Comments
 (0)