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
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,41 @@ and this project adheres to
Work in progress for the next release. Add entries here as
changes land, not at tag time.

## [0.10.0] - 2026-05-08

### Added

- **`attune_author.skill_export`** — exporter that writes one
Claude Code `SKILL.md` per concept template in an
attune-help-compatible corpus. Frontmatter `name` is the
canonical feature slug (with `tool-` / `task-` prefixes
stripped for parity with `summaries.json`); `description`
resolves through summaries.json → frontmatter → first-paragraph
extraction → generic stub. Body is the concept template
content. Per-feature output goes to
`<output>/<feature>/SKILL.md`.
- **`attune-author export-skills`** CLI subcommand. Defaults
to attune-help's bundled corpus (resolved via
`attune_help.adapters.rag.AttuneHelpAdapter`); `--source`
overrides for project-local templates. `--overwrite` replaces
existing SKILL.md files; absent it skips them with a recorded
reason.
- 18 new tests in `tests/test_skill_export.py` covering canonical
slug stripping, first-paragraph extraction, summary lookup,
fallback chain, edge cases (missing summaries.json, empty
body, unsafe slug, overwrite semantics).

### Notes

- Exporting the bundled attune-help corpus today produces 43
skills covering the documented attune-ai workflows. Skill
consumers (Claude Code, the marketplace) can load these as
native skills with description-driven triggering — the
attune-help corpus becomes a *generator* for skills rather
than a runtime competitor. Phase one of the
attune-help-as-skill recommendation from the 2026-05-08
enhancements briefing.

## [0.9.1] - 2026-05-08

### Added
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "attune-author"
version = "0.9.1"
version = "0.10.0"
description = "Documentation authoring and maintenance for the attune ecosystem — generate, maintain, and validate help content with AI assistance."
readme = {file = "README.md", content-type = "text/markdown"}
requires-python = ">=3.10"
Expand Down
2 changes: 1 addition & 1 deletion src/attune_author/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
attune-help (reader) and attune-ai (full dev workflows).
"""

__version__ = "0.9.1"
__version__ = "0.10.0"

from attune_author.manifest import Feature, Manifest, load_manifest
from attune_author.staleness import StalenessReport, check_staleness, compute_source_hash
Expand Down
81 changes: 81 additions & 0 deletions src/attune_author/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,36 @@ def _build_parser() -> argparse.ArgumentParser:
help="Target audience (default: %(default)s).",
)

p_skills = sub.add_parser(
"export-skills",
help="Export an attune-help corpus as Claude Code skill bundles",
description=(
"Walk an attune-help-compatible templates/ directory and write "
"one Claude Code SKILL.md per concept template. Each output "
"directory becomes a loadable skill: SKILL.md frontmatter "
"carries the name and description (sourced from "
"summaries.json) and the body is the concept template."
),
)
p_skills.add_argument(
"--source",
default=None,
help=(
"Path to a templates/ directory (must contain concepts/ and "
"summaries.json). Defaults to attune-help's bundled corpus."
),
)
p_skills.add_argument(
"--output",
default="./skills",
help="Directory to write SKILL.md files into (default: %(default)s).",
)
p_skills.add_argument(
"--overwrite",
action="store_true",
help="Replace any existing SKILL.md instead of skipping it.",
)

return parser


Expand Down Expand Up @@ -277,6 +307,7 @@ def _dispatch(args: argparse.Namespace, parser: argparse.ArgumentParser) -> int:
"docs": _cmd_docs,
"cache": _cmd_cache,
"edit": _cmd_edit,
"export-skills": _cmd_export_skills,
}
handler = handlers.get(args.command)
if handler is None:
Expand Down Expand Up @@ -771,5 +802,55 @@ def _load_feature_names_for_welcome() -> list[str] | None:
return names or None


def _cmd_export_skills(args: argparse.Namespace) -> int:
"""Handle the ``export-skills`` command.

Resolves ``--source`` (defaulting to attune-help's bundled
templates dir via the rag adapter), runs :func:`export_skills`,
and prints a written/skipped summary.

Returns:
``0`` on success — even when some features were skipped.
``1`` if the source directory can't be resolved or doesn't
contain a ``concepts/`` subtree.
"""
from attune_author.skill_export import export_skills

source: Path
if args.source:
source = Path(args.source).expanduser().resolve()
else:
try:
from attune_help.adapters.rag import AttuneHelpAdapter
except ImportError:
print(
"Could not import attune_help; pass --source <templates-dir> "
"or install attune-help.",
file=sys.stderr,
)
return 1
source = AttuneHelpAdapter().templates_root

if not (source / "concepts").is_dir():
print(
f"Source has no concepts/ subdirectory: {source}\n"
f" Hint: pass a templates/ root, not the project or .help/ dir.",
file=sys.stderr,
)
return 1

output = Path(args.output).expanduser().resolve()
result = export_skills(source, output, overwrite=args.overwrite)

print(f"Exported {len(result.written)} skill(s) to {output}")
for record in result.written:
print(f" + {record.feature:<32} {record.body_chars} chars")
if result.skipped:
print(f"\nSkipped {len(result.skipped)}:")
for feature, reason in result.skipped:
print(f" - {feature:<32} {reason}")
return 0


if __name__ == "__main__":
sys.exit(main())
Loading
Loading