Skip to content

Commit 0ae7ab5

Browse files
refactor(commands): proxy 3 help.* commands to attune-author (D3) (#26)
Phase D3 of the architecture-realignment spec, gui side. The three ``help.*`` executor bodies (lookup, search, list) moved to attune-author 0.9.1; this change drops the in-line CommandSpecs + ``_help_engine`` factory and replaces them with three ``_proxy_command(...)`` registrations. None of the three needs workspace pre-resolution or post-dispatch invalidation, so plain ``_proxy_command`` is sufficient — no ``_author_proxy``-style specialization required. Diff stat: +107 / -275. Bumps gui to 0.6.2; pins attune-author[ai] to >=0.9.1,<0.10. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 1e3dc8d commit 0ae7ab5

4 files changed

Lines changed: 107 additions & 275 deletions

File tree

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,23 @@
33
All notable changes to `attune-gui` are documented here.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
55

6+
## [0.6.2] — 2026-05-08
7+
8+
### Changed
9+
10+
- **All three `help.*` commands now dispatch through `attune_author.orchestration`.** Phase D3 of the architecture-realignment spec: `lookup`, `search`, `list`. The executor bodies live in attune-author 0.9.1; the gui keeps thin proxy `CommandSpec`s registered via `_proxy_command(...)`. None of the three need workspace pre-resolution or post-dispatch invalidation, so plain `_proxy_command` is sufficient.
11+
- `commands.py` shrank by ~200 more lines (three executors + `_help_engine` factory + three in-line specs replaced by three proxy registrations + a one-line import).
12+
- `_help_engine` factory removed from `attune_gui.commands` — no production callers after this PR. A lazy-import variant lives in attune-author at `attune_author.orchestration.commands.help._help_engine`.
13+
14+
### Tests
15+
16+
- `TestHelpEngineFactory` and `TestHelpExecutors` removed; coverage now lives in `attune-author/tests/test_orchestration_commands_help.py`.
17+
- New `TestHelpProxies` class: registration check, metadata mirroring, dispatch through `run_command` for all three commands.
18+
19+
### Dependencies
20+
21+
- Bumped `attune-author[ai]` constraint from `>=0.9.0,<0.10` to `>=0.9.1,<0.10`.
22+
623
## [0.6.1] — 2026-05-08
724

825
### Changed

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "attune-gui"
7-
version = "0.6.1"
7+
version = "0.6.2"
88
description = "Local dashboard for attune-rag / attune-help / attune-author. Server-rendered Jinja2 UI — ships clean via PyPI with no npm step."
99
readme = "README.md"
1010
requires-python = ">=3.10"
@@ -30,7 +30,7 @@ dependencies = [
3030
"pydantic>=2.0,<3.0",
3131
"structlog>=24.0,<26.0",
3232
"attune-rag>=0.1.12,<0.2",
33-
"attune-author[ai]>=0.9.0,<0.10",
33+
"attune-author[ai]>=0.9.1,<0.10",
3434
"attune-help>=0.10.0,<1.0",
3535
"jinja2>=3.1,<4.0",
3636
"python-frontmatter>=1.1,<2.0",

sidecar/attune_gui/commands.py

Lines changed: 8 additions & 196 deletions
Original file line numberDiff line numberDiff line change
@@ -484,205 +484,17 @@ async def _exec_author_generate(args: dict[str, Any], ctx: JobContext) -> dict[s
484484

485485

486486
# ---------------------------------------------------------------------------
487-
# Help: lookup
487+
# help.* commands now live in attune_author.orchestration.commands.help
488+
# (Phase D3 of the architecture-realignment spec). None of the three
489+
# need workspace pre-resolution or post-dispatch invalidation, so plain
490+
# _proxy_command is sufficient.
488491
# ---------------------------------------------------------------------------
489492

493+
import attune_author.orchestration.commands.help # noqa: F401, E402
490494

491-
def _help_engine(template_dir: str | None, job_id: str):
492-
from pathlib import Path as _Path # noqa: PLC0415
493-
494-
from attune_help import HelpEngine # noqa: PLC0415
495-
496-
return HelpEngine(
497-
template_dir=_Path(template_dir).resolve() if template_dir else None,
498-
renderer="plain",
499-
user_id=job_id,
500-
)
501-
502-
503-
async def _exec_help_lookup(args: dict[str, Any], ctx: JobContext) -> dict[str, Any]:
504-
topic = args["topic"]
505-
depth = args.get("depth", "concept")
506-
template_dir = args.get("template_dir") or None
507-
508-
depth_steps = {"concept": 1, "task": 2, "reference": 3}
509-
steps = depth_steps.get(depth, 1)
510-
511-
engine = _help_engine(template_dir, ctx.job_id)
512-
ctx.log(f"Looking up {topic!r} at depth={depth} ({steps} step(s))…")
513-
514-
content = None
515-
for _ in range(steps):
516-
content = await asyncio.to_thread(engine.lookup, topic, suggest_on_miss=True)
517-
518-
if content is None:
519-
raise ValueError(f"No help found for topic {topic!r}.")
520-
521-
topics = await asyncio.to_thread(engine.list_topics)
522-
ctx.log(f"Retrieved from {engine.generated_dir}")
523-
return {
524-
"topic": topic,
525-
"depth": depth,
526-
"content": content,
527-
"template_dir": str(engine.generated_dir),
528-
"total_topics": len(topics),
529-
}
530-
531-
532-
COMMANDS["help.lookup"] = CommandSpec(
533-
name="help.lookup",
534-
title="Help lookup",
535-
domain="help",
536-
description="Look up a help topic with progressive depth (concept → task → reference).",
537-
args_schema={
538-
"type": "object",
539-
"properties": {
540-
"topic": {
541-
"type": "string",
542-
"title": "Topic",
543-
"minLength": 1,
544-
"description": "Topic slug or feature name to look up.",
545-
},
546-
"depth": {
547-
"type": "string",
548-
"title": "Depth",
549-
"default": "concept",
550-
"description": "concept | task | reference",
551-
},
552-
"template_dir": {
553-
"type": "string",
554-
"title": "Template dir",
555-
"default": "",
556-
"description": (
557-
"Path to .help/templates/ directory. " "Leave blank to use bundled templates."
558-
),
559-
"ui:widget": "path",
560-
},
561-
},
562-
"required": ["topic"],
563-
},
564-
executor=_exec_help_lookup,
565-
cancellable=False,
566-
profiles=("developer", "author"),
567-
)
568-
569-
570-
# ---------------------------------------------------------------------------
571-
# Help: search
572-
# ---------------------------------------------------------------------------
573-
574-
575-
async def _exec_help_search(args: dict[str, Any], ctx: JobContext) -> dict[str, Any]:
576-
query = args["query"]
577-
limit = int(args.get("limit", 10))
578-
template_dir = args.get("template_dir") or None
579-
580-
engine = _help_engine(template_dir, "search")
581-
ctx.log(f"Searching {query!r} (limit={limit})…")
582-
583-
results = await asyncio.to_thread(engine.search, query, limit=limit)
584-
ctx.log(f"Found {len(results)} result(s)")
585-
return {
586-
"query": query,
587-
"results": results,
588-
"count": len(results),
589-
"template_dir": str(engine.generated_dir),
590-
}
591-
592-
593-
COMMANDS["help.search"] = CommandSpec(
594-
name="help.search",
595-
title="Help search",
596-
domain="help",
597-
description="Fuzzy-search help topics by keyword across the template library.",
598-
args_schema={
599-
"type": "object",
600-
"properties": {
601-
"query": {
602-
"type": "string",
603-
"title": "Query",
604-
"minLength": 1,
605-
"description": "Keyword or phrase to search for.",
606-
},
607-
"limit": {
608-
"type": "integer",
609-
"title": "Limit",
610-
"default": 10,
611-
"minimum": 1,
612-
"maximum": 50,
613-
},
614-
"template_dir": {
615-
"type": "string",
616-
"title": "Template dir",
617-
"default": "",
618-
"description": (
619-
"Path to .help/templates/ directory. " "Leave blank to use bundled templates."
620-
),
621-
"ui:widget": "path",
622-
},
623-
},
624-
"required": ["query"],
625-
},
626-
executor=_exec_help_search,
627-
cancellable=False,
628-
profiles=("developer", "author"),
629-
)
630-
631-
632-
# ---------------------------------------------------------------------------
633-
# Help: list
634-
# ---------------------------------------------------------------------------
635-
636-
637-
async def _exec_help_list(args: dict[str, Any], ctx: JobContext) -> dict[str, Any]:
638-
type_filter = args.get("type_filter") or None
639-
template_dir = args.get("template_dir") or None
640-
641-
engine = _help_engine(template_dir, "list")
642-
ctx.log(f"Listing topics{f' (type={type_filter})' if type_filter else ''}…")
643-
644-
topics = await asyncio.to_thread(engine.list_topics, type_filter=type_filter)
645-
ctx.log(f"{len(topics)} topic(s) found in {engine.generated_dir}")
646-
return {
647-
"topics": topics,
648-
"count": len(topics),
649-
"type_filter": type_filter,
650-
"template_dir": str(engine.generated_dir),
651-
}
652-
653-
654-
COMMANDS["help.list"] = CommandSpec(
655-
name="help.list",
656-
title="List topics",
657-
domain="help",
658-
description=(
659-
"List all available help topics, "
660-
"optionally filtered by type (concepts, tasks, references)."
661-
),
662-
args_schema={
663-
"type": "object",
664-
"properties": {
665-
"type_filter": {
666-
"type": "string",
667-
"title": "Type filter",
668-
"default": "",
669-
"description": "concepts | tasks | references | (blank for all)",
670-
},
671-
"template_dir": {
672-
"type": "string",
673-
"title": "Template dir",
674-
"default": "",
675-
"description": (
676-
"Path to .help/templates/ directory. " "Leave blank to use bundled templates."
677-
),
678-
"ui:widget": "path",
679-
},
680-
},
681-
},
682-
executor=_exec_help_list,
683-
cancellable=False,
684-
profiles=("developer", "author"),
685-
)
495+
COMMANDS["help.lookup"] = _proxy_command("help.lookup")
496+
COMMANDS["help.search"] = _proxy_command("help.search")
497+
COMMANDS["help.list"] = _proxy_command("help.list")
686498

687499

688500
COMMANDS["author.regen"] = _author_proxy(

0 commit comments

Comments
 (0)