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
19 changes: 14 additions & 5 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
name: Deploy Documentation
name: Documentation

on:
push:
branches: [main]
pull_request:
branches: [main, develop]

permissions:
contents: read
pages: write
id-token: write

# Keyed by ref so concurrent PR doc builds do not serialize behind each other,
# while repeated pushes to the same branch still supersede one another.
concurrency:
group: "pages"
group: pages-${{ github.ref }}
cancel-in-progress: false

jobs:
build:
# Job id is `docs` so it satisfies the required status check of that name.
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -30,20 +35,24 @@ jobs:
- name: Install dependencies
run: uv sync --extra docs

# --strict fails on broken internal links, which is how the dangling
# agent-page links were caught rather than shipped.
- name: Build docs
run: uv run mkdocs build
run: uv run mkdocs build --strict

- name: Upload artifact
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: actions/upload-pages-artifact@v3
with:
path: ./site

deploy:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
needs: docs
steps:
- name: Deploy to GitHub Pages
id: deployment
Expand Down
99 changes: 99 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Changelog

All notable changes to this project are documented in this file.

The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). While the
version is below 1.0.0, breaking changes are released in a minor bump.

## [0.3.0] - 2026-07-24

A library-wide audit of all three pillars — skills, agents, and archetypes — measured
against Anthropic's Agent Skills guidance. Always-resident skill context is down 73%,
the agents concept is gone, every archetype generates a project that runs, and the
authoring rules are now enforced in CI rather than by discipline.

### Removed

- **The `agents/` directory and the agent concept.** The six "agents" were never Claude
Code subagents: four had no YAML frontmatter (so they installed with their H1 as the
entire description and were effectively untriggerable), all six installed into
`.claude/skills/` rather than `.claude/agents/`, `agent.toml` was never parsed by any
code path, and both "blocking" `action.yml` files ran `pixi` against a repo with no
`pixi.toml`. Roughly 77% of their content duplicated existing skills, often diverging
from them. GSD owns agency; whet owns domain knowledge, and knowledge is a skill.
- **`whet install --agents-only` and `--skills-only` flags**, along with
`discover_agents()` and the `agents_dir` config field.
- **Skills `dvc` and `cv-model-selection`.** Guidance that referenced DVC as a generic
concept was rewritten tool-neutral rather than left pointing at a deleted skill.

### Added

- **`model-evaluation`** — detection mAP/IoU via `supervision`, confusion matrices,
per-class and per-size breakdowns, deployment-threshold selection, per-slice failure
analysis, and eval-as-CI regression gates.
- **`pydantic-ai`** — typed LLM/VLM structured outputs and VLM-in-the-loop auto-labeling.
- **`data-pipelines`** — storage-format selection, group-aware leakage-preventing dataset
splitting, schema evolution, and data-quality validation.
- **`whet install --prune`** — removes skills that no longer exist upstream. Scoped by a
`.whet-manifest.json` recording what whet installed, so skills placed in the same
directory by other tools are never touched.
- **`whet install --include-extras`** and a `tier` field in `skill.toml`. Skills outside
the flagship path are opt-in: `aws-sagemaker`, `github-repo-setup`, `gradio`,
`huggingface`, `kubernetes`, `mlflow`, `vscode`.
- **`whet init --with-recommended` / `--no-skills`.**
- **Progressive disclosure** across the library: 29 of 32 skills are now a thin index
plus a `references/` directory loaded on demand — 155 reference files in total.
- **CI enforcement.** A new `archetypes` job renders every archetype and runs the
toolchain against the generated project, plus guards for skill-authoring rules
(line ceiling, reference-link integrity, orphaned references, one-level-deep
references, tables of contents, trigger-first and third-person descriptions,
listing budget) and for archetype composition and documented directory trees.

### Changed

- **`whet init` now installs the archetype's skills** (with their `references/`) instead
of printing a `whet add ...` hint. An archetype's value over a folder copy is the skill
set it composes, so leaving that as homework made `[skills]` advisory.
- **Skill `pydantic-strict` renamed to `pydantic`.**
- **All skill descriptions rewritten** as third-person, trigger-first "use this skill
when…" statements with explicit disambiguation between adjacent skills. They were topic
summaries, which under-trigger.
- **Every archetype template now generates a project that runs.** Previously four of six
generated only a four-file stub, and the two with templates produced projects that could
not import their own entry point, crashed on the first training step, or shipped no ONNX
despite being named for it. Template files went from 19 to 125.
- **pixi is the canonical environment manager** for generated projects; manifests migrated
from the deprecated `[project]` table to `[workspace]`.
- **Archetype skill composition** now reflects what each template actually ships — every
archetype requires `pixi` and `code-quality`, and `cv-inference-service` finally
requires `fastapi`.

### Fixed

- **The published wheel shipped no skills.** `[tool.hatch.build.targets.wheel]` packaged
only `src/whet`, and the bundled directories were resolved relative to the repository
root — so `uv tool install whet` produced a CLI that reported "No skills found" outside
a source checkout. The wheel now force-includes `skills/`, `archetypes/`, and
`settings/` under `whet/_data/`, and a single resolver prefers the packaged copy while
falling back to the repo layout for development.
- Template files under a `data/` path were silently excluded by the repository's own
`.gitignore`, so a clean checkout produced projects missing `data/raw`, `data/processed`,
and an entire Hydra `data` config group.
- `.ipynb` files were not treated as text by the scaffold engine, so `${package_name}`
inside a notebook was never substituted.
- Adapters now install a skill's `references/` directory (Claude, Antigravity) or inline
it (Cursor, Copilot), so deep-dive links resolve on every platform.
- Documented directory trees in archetype READMEs and docs pages are regenerated from the
templates; 78 documented-but-absent files were removed.

## [0.2.1] - 2026-07-23

Published to PyPI outside this repository's tag-based release workflow; no corresponding
git tag exists. Recorded here for continuity.

## [0.1.0] - 2026-02-09

Initial whet CLI implementation.

[0.3.0]: https://github.com/ortizeg/whet/releases/tag/v0.3.0
10 changes: 8 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "whet"
version = "0.1.0"
version = "0.3.0"
description = "Sharpen your AI coder — install expert skills into AI coding agents"
authors = [{name = "Enrique G. Ortiz", email = "ortizeg@gmail.com"}]
readme = "README.md"
Expand Down Expand Up @@ -57,11 +57,17 @@ build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/whet"]

# The skills, archetypes, and settings ARE the product; without this the wheel
# ships only the CLI and `whet list` reports "No skills found".
[tool.hatch.build.targets.wheel.force-include]
"skills" = "whet/_data/skills"
"archetypes" = "whet/_data/archetypes"
"settings" = "whet/_data/settings"

[tool.hatch.build.targets.sdist]
include = [
"src/whet/",
"skills/",
"agents/",
"archetypes/",
"settings/",
]
Expand Down
2 changes: 1 addition & 1 deletion src/whet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

from __future__ import annotations

__version__ = "0.1.0"
__version__ = "0.3.0"
8 changes: 4 additions & 4 deletions src/whet/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

from pydantic import BaseModel, Field

from whet.core.paths import bundled_dir

CONFIG_PATH = Path.home() / ".config" / "whet" / "config.json"


Expand Down Expand Up @@ -51,10 +53,8 @@ class WhetConfig(BaseModel):
"""Whet configuration persisted to disk."""

target: Platform = Platform.CLAUDE
skills_dir: Path = Field(default_factory=lambda: Path(__file__).resolve().parents[3] / "skills")
archetypes_dir: Path = Field(
default_factory=lambda: Path(__file__).resolve().parents[3] / "archetypes"
)
skills_dir: Path = Field(default_factory=lambda: bundled_dir("skills"))
archetypes_dir: Path = Field(default_factory=lambda: bundled_dir("archetypes"))

@classmethod
def load(cls) -> WhetConfig:
Expand Down
31 changes: 31 additions & 0 deletions src/whet/core/paths.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""Locate whet's bundled content (skills, archetypes, settings).

These directories live at the repository root during development, but must also be
found when whet is installed as a wheel — where the package lands in site-packages
and the repository layout no longer exists above it. The build force-includes them
under `whet/_data/`, so resolution prefers that and falls back to the repo layout.
"""

from __future__ import annotations

from pathlib import Path

# .../src/whet/core/paths.py -> parents[1] is the `whet` package.
_PACKAGE_ROOT = Path(__file__).resolve().parents[1]
# .../src/whet/core/paths.py -> parents[3] is the repository root in a source checkout.
_REPO_ROOT = Path(__file__).resolve().parents[3]

_BUNDLED = _PACKAGE_ROOT / "_data"


def bundled_dir(name: str) -> Path:
"""Path to a bundled content directory such as "skills" or "archetypes".

Prefers the packaged copy so an installed wheel works outside any checkout;
falls back to the repository root so a source checkout keeps working with no
build step.
"""
packaged = _BUNDLED / name
if packaged.is_dir():
return packaged
return _REPO_ROOT / name
4 changes: 3 additions & 1 deletion src/whet/settings/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

from pydantic import BaseModel, Field

from whet.core.paths import bundled_dir


class SettingsTemplate(BaseModel, frozen=True):
"""A settings template with permissions."""
Expand Down Expand Up @@ -103,7 +105,7 @@ def write_settings(settings_path: Path, template: SettingsTemplate) -> None:

def get_template_path(platform: str) -> Path:
"""Get the path to a platform's settings template."""
templates_dir = Path(__file__).resolve().parents[3] / "settings"
templates_dir = bundled_dir("settings")
template_file = templates_dir / f"{platform}.json"
if template_file.exists():
return template_file
Expand Down
69 changes: 69 additions & 0 deletions tests/test_packaging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""Guard that the built wheel actually ships the product.

whet's value is the skills, archetypes, and settings — not the CLI that copies them.
The wheel once packaged only `src/whet`, so `uv tool install whet` produced a CLI
that reported "No skills found" outside a source checkout. These tests assert the
bundled content is both declared for packaging and reachable at runtime.
"""

from __future__ import annotations

import sys
from pathlib import Path

if sys.version_info >= (3, 12):
import tomllib
else:
import tomli as tomllib

from whet.core.paths import bundled_dir

BUNDLED = ("skills", "archetypes", "settings")


def test_wheel_force_includes_bundled_content() -> None:
"""Bundled dirs live outside src/whet, so the wheel needs an explicit include."""
with open("pyproject.toml", "rb") as f:
raw = tomllib.load(f)

include = raw["tool"]["hatch"]["build"]["targets"]["wheel"]["force-include"]
missing = [name for name in BUNDLED if name not in include]
assert not missing, (
f"wheel does not force-include {missing}; an installed whet would find no skills"
)
for name in BUNDLED:
assert include[name] == f"whet/_data/{name}", (
f"{name} must map to whet/_data/{name} so bundled_dir() resolves it"
)


def test_sdist_includes_bundled_content() -> None:
with open("pyproject.toml", "rb") as f:
raw = tomllib.load(f)

include = raw["tool"]["hatch"]["build"]["targets"]["sdist"]["include"]
missing = [name for name in BUNDLED if f"{name}/" not in include]
assert not missing, f"sdist does not include {missing}"


def test_sdist_does_not_ship_removed_directories() -> None:
"""agents/ was deleted; packaging must not still reference it."""
with open("pyproject.toml", "rb") as f:
raw = tomllib.load(f)

include = raw["tool"]["hatch"]["build"]["targets"]["sdist"]["include"]
stale = [entry for entry in include if not Path(entry.rstrip("/")).exists()]
assert not stale, f"packaging references directories that no longer exist: {stale}"


def test_bundled_dir_resolves_in_a_source_checkout() -> None:
"""Development must keep working with no build step."""
for name in BUNDLED:
resolved = bundled_dir(name)
assert resolved.is_dir(), f"bundled_dir({name!r}) -> {resolved} does not exist"


def test_bundled_skills_are_the_real_library() -> None:
"""Catch a resolver that finds an empty or wrong directory."""
skills = list(bundled_dir("skills").glob("*/SKILL.md"))
assert len(skills) >= 25, f"bundled skills dir looks wrong: {len(skills)} SKILL.md files"
Loading