Skip to content

Commit 1d1d366

Browse files
fix: deep-review fixes — storage bug, refactoring, test coverage (0.5.1)
Fixes a blocking bug where the lookup_simpler MCP handler used a fresh LocalFileStorage instead of the engine's own storage, breaking custom backends. Also renames type→type_filter to avoid shadowing the builtin, extracts validation helpers and tag maps, and adds 20 tests (coverage 78%→84%). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 656af83 commit 1d1d366

10 files changed

Lines changed: 433 additions & 129 deletions

File tree

CHANGELOG.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,38 @@
22

33
All notable changes to `attune-help` are documented here.
44

5+
## 0.5.1 — 2026-04-12
6+
7+
### Fixed
8+
9+
- **[BLOCKING]** `lookup_simpler` MCP handler created a
10+
fresh `LocalFileStorage()` to read depth back instead
11+
of using the engine's own storage instance — broke
12+
custom storage backends.
13+
- `list_topics` parameter renamed from `type` to
14+
`type_filter` to avoid shadowing the Python builtin.
15+
- `precursor_warnings` extension and filename maps
16+
extracted to module-level constants (`_EXT_TAGS`,
17+
`_NAME_TAGS`), reducing the method from 110 to ~10
18+
lines.
19+
- Handlers category-name map consolidated: now imports
20+
`_PREFIX_MAP` from `templates.py` instead of
21+
re-declaring it.
22+
- Repeated string-validation boilerplate across all 10
23+
MCP handlers extracted into a shared `_require_str()`
24+
helper (~30 lines removed).
25+
- `_auto_detect_renderer` return typed as
26+
`Callable[[PopulatedTemplate], str]` instead of `Any`.
27+
- Added `from __future__ import annotations` to
28+
`demos/__init__.py`.
29+
30+
### Tests
31+
32+
- 20 new tests: renderer branches (error/warning/tip,
33+
marketplace, CLI plain), `engine.get()` coverage, and
34+
MCP handler error-path coverage.
35+
- Overall coverage: 78% → 84%.
36+
537
## 0.5.0 — 2026-04-11
638

739
MCP layer catches up to the 0.4 `HelpEngine` public API.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ levels that `/coach init` generates for each feature.
114114

115115
```python
116116
engine.list_topics() # all slugs
117-
engine.list_topics(type="concepts") # filter by type
117+
engine.list_topics(type_filter="concepts") # filter by type
118118
engine.search("security") # [(slug, score), ...]
119119
engine.suggest("secrity-audit") # ranked slugs
120120
```

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-help"
7-
version = "0.5.0"
7+
version = "0.5.1"
88
description = "Lightweight help runtime with progressive depth and audience adaptation."
99
readme = {file = "README.md", content-type = "text/markdown"}
1010
requires-python = ">=3.10"

src/attune_help/demos/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
your project's ``.help/templates/`` as a starting point.
66
"""
77

8+
from __future__ import annotations
9+
810
from pathlib import Path
911

1012

src/attune_help/discovery.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,23 +79,24 @@ def build_index(generated_dir: Path) -> dict[str, list[str]]:
7979

8080
def list_topics(
8181
generated_dir: Path,
82-
type: str | None = None,
82+
type_filter: str | None = None,
8383
limit: int | None = None,
8484
) -> list[str]:
8585
"""Enumerate topic slugs.
8686
8787
Args:
8888
generated_dir: Template directory.
89-
type: Optional type filter (e.g. ``"concepts"``).
90-
``None`` returns all types flattened.
89+
type_filter: Optional type filter (e.g.
90+
``"concepts"``). ``None`` returns all types
91+
flattened.
9192
limit: Optional cap on returned items.
9293
9394
Returns:
9495
Sorted list of slugs.
9596
"""
9697
index = build_index(generated_dir)
97-
if type is not None:
98-
result = list(index.get(type, []))
98+
if type_filter is not None:
99+
result = list(index.get(type_filter, []))
99100
else:
100101
result = sorted({s for slugs in index.values() for s in slugs})
101102
if limit is not None:

src/attune_help/engine.py

Lines changed: 48 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import logging
1111
import threading
1212
from pathlib import Path
13-
from typing import Any
1413

1514
from attune_help.progression import populate_progressive
1615
from attune_help.storage import LocalFileStorage, SessionStorage
@@ -88,6 +87,46 @@ def _load_summaries(template_dir: Path) -> dict[str, str]:
8887
2: '\n\n*(reference — deepest level; say "simpler" to step back)*',
8988
}
9089

90+
# Tag maps for precursor_warnings, extracted to module level
91+
# so the method body stays concise and the maps are testable.
92+
_EXT_TAGS: dict[str, list[str]] = {
93+
".py": ["python", "imports", "testing", "error-handling"],
94+
".yml": ["ci", "github-actions"],
95+
".yaml": ["ci", "github-actions"],
96+
".json": ["packaging"],
97+
".toml": ["packaging", "python", "publishing"],
98+
".md": ["claude-code"],
99+
".sql": ["database", "migrations"],
100+
".env": ["config", "secrets"],
101+
".cfg": ["config"],
102+
".ini": ["config"],
103+
".js": ["javascript", "testing", "error-handling"],
104+
".jsx": ["javascript", "testing", "error-handling"],
105+
".ts": ["typescript", "javascript", "testing", "error-handling"],
106+
".tsx": ["typescript", "javascript", "testing", "error-handling"],
107+
".rs": ["rust", "testing", "error-handling"],
108+
".go": ["go", "testing", "error-handling"],
109+
".rb": ["ruby", "testing", "error-handling"],
110+
".java": ["java", "testing", "error-handling"],
111+
}
112+
113+
_NAME_TAGS: dict[str, list[str]] = {
114+
"pyproject.toml": ["deps", "publishing", "packaging"],
115+
"requirements.txt": ["deps"],
116+
"setup.py": ["publishing", "packaging"],
117+
"setup.cfg": ["publishing", "packaging"],
118+
"config.py": ["config"],
119+
"settings.py": ["config"],
120+
"models.py": ["database"],
121+
"alembic.ini": ["database", "migrations"],
122+
"dockerfile": ["ci", "cd"],
123+
"docker-compose.yml": ["ci", "cd"],
124+
".gitignore": ["git"],
125+
".env": ["config", "secrets"],
126+
".env.example": ["config", "secrets"],
127+
"manifest.in": ["publishing"],
128+
}
129+
91130

92131
def _depth_prompt(depth: int) -> str:
93132
"""Return the progressive depth prompt for the current level."""
@@ -289,22 +328,23 @@ def lookup(
289328

290329
def list_topics(
291330
self,
292-
type: str | None = None,
331+
type_filter: str | None = None,
293332
limit: int | None = None,
294333
) -> list[str]:
295334
"""Enumerate available topic slugs.
296335
297336
Args:
298-
type: Optional type filter (``"concepts"``,
299-
``"tasks"``, ``"references"``, etc.).
337+
type_filter: Optional type filter
338+
(``"concepts"``, ``"tasks"``,
339+
``"references"``, etc.).
300340
limit: Optional cap on results.
301341
302342
Returns:
303343
Sorted list of slugs.
304344
"""
305345
from attune_help.discovery import list_topics as _list
306346

307-
return _list(self.generated_dir, type=type, limit=limit)
347+
return _list(self.generated_dir, type_filter=type_filter, limit=limit)
308348

309349
def search(
310350
self,
@@ -486,48 +526,8 @@ def precursor_warnings(
486526
name = Path(file_path).name.lower()
487527

488528
tags: list[str] = []
489-
490-
# Extension-based triggers
491-
ext_map = {
492-
".py": ["python", "imports", "testing", "error-handling"],
493-
".yml": ["ci", "github-actions"],
494-
".yaml": ["ci", "github-actions"],
495-
".json": ["packaging"],
496-
".toml": ["packaging", "python", "publishing"],
497-
".md": ["claude-code"],
498-
".sql": ["database", "migrations"],
499-
".env": ["config", "secrets"],
500-
".cfg": ["config"],
501-
".ini": ["config"],
502-
".js": ["javascript", "testing", "error-handling"],
503-
".jsx": ["javascript", "testing", "error-handling"],
504-
".ts": ["typescript", "javascript", "testing", "error-handling"],
505-
".tsx": ["typescript", "javascript", "testing", "error-handling"],
506-
".rs": ["rust", "testing", "error-handling"],
507-
".go": ["go", "testing", "error-handling"],
508-
".rb": ["ruby", "testing", "error-handling"],
509-
".java": ["java", "testing", "error-handling"],
510-
}
511-
tags.extend(ext_map.get(ext, []))
512-
513-
# Filename-based triggers
514-
name_map = {
515-
"pyproject.toml": ["deps", "publishing", "packaging"],
516-
"requirements.txt": ["deps"],
517-
"setup.py": ["publishing", "packaging"],
518-
"setup.cfg": ["publishing", "packaging"],
519-
"config.py": ["config"],
520-
"settings.py": ["config"],
521-
"models.py": ["database"],
522-
"alembic.ini": ["database", "migrations"],
523-
"dockerfile": ["ci", "cd"],
524-
"docker-compose.yml": ["ci", "cd"],
525-
".gitignore": ["git"],
526-
".env": ["config", "secrets"],
527-
".env.example": ["config", "secrets"],
528-
"manifest.in": ["publishing"],
529-
}
530-
tags.extend(name_map.get(name, []))
529+
tags.extend(_EXT_TAGS.get(ext, []))
530+
tags.extend(_NAME_TAGS.get(name, []))
531531

532532
if not tags:
533533
return []
@@ -577,7 +577,7 @@ def precursor_warnings(
577577
return results
578578

579579
@staticmethod
580-
def _auto_detect_renderer() -> Any:
580+
def _auto_detect_renderer() -> Callable[[PopulatedTemplate], str]:
581581
"""Detect the best renderer for the current environment.
582582
583583
Priority:

0 commit comments

Comments
 (0)