diff --git a/.claude/skills/create-thesis-sim-student/SKILL.md b/.claude/skills/create-thesis-sim-student/SKILL.md index e21aff3..c73db8b 100644 --- a/.claude/skills/create-thesis-sim-student/SKILL.md +++ b/.claude/skills/create-thesis-sim-student/SKILL.md @@ -1,6 +1,8 @@ --- name: create-thesis-sim-student description: Create new repo-local thesis-finder simulation student commands. Use when asked to add, scaffold, draft, or generate a new simulated thesis-finder student persona for Claude and Codex. +metadata: + internal: true --- # Create Thesis Simulation Student diff --git a/.claude/skills/run-thesis-simulations/SKILL.md b/.claude/skills/run-thesis-simulations/SKILL.md index e2949e1..f06e722 100644 --- a/.claude/skills/run-thesis-simulations/SKILL.md +++ b/.claude/skills/run-thesis-simulations/SKILL.md @@ -1,6 +1,8 @@ --- name: run-thesis-simulations description: Run and evaluate all repo-local thesis-finder simulation commands. Use when asked to run the thesis simulation suite, evaluate thesis-finder personas, execute all thesis-sim commands, or produce conversation and rating artifacts. +metadata: + internal: true --- # Run Thesis Simulations diff --git a/.codex/skills/create-thesis-sim-student/SKILL.md b/.codex/skills/create-thesis-sim-student/SKILL.md index e21aff3..c73db8b 100644 --- a/.codex/skills/create-thesis-sim-student/SKILL.md +++ b/.codex/skills/create-thesis-sim-student/SKILL.md @@ -1,6 +1,8 @@ --- name: create-thesis-sim-student description: Create new repo-local thesis-finder simulation student commands. Use when asked to add, scaffold, draft, or generate a new simulated thesis-finder student persona for Claude and Codex. +metadata: + internal: true --- # Create Thesis Simulation Student diff --git a/.codex/skills/run-thesis-simulations/SKILL.md b/.codex/skills/run-thesis-simulations/SKILL.md index e2949e1..f06e722 100644 --- a/.codex/skills/run-thesis-simulations/SKILL.md +++ b/.codex/skills/run-thesis-simulations/SKILL.md @@ -1,6 +1,8 @@ --- name: run-thesis-simulations description: Run and evaluate all repo-local thesis-finder simulation commands. Use when asked to run the thesis simulation suite, evaluate thesis-finder personas, execute all thesis-sim commands, or produce conversation and rating artifacts. +metadata: + internal: true --- # Run Thesis Simulations diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index 5927e5f..4265cb8 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -5,6 +5,8 @@ on: pull_request: paths: - 'skills/**' + - '.claude/skills/**' + - '.codex/skills/**' - 'scripts/**' - 'pyproject.toml' - '.github/workflows/qa.yml' diff --git a/STATUS.md b/STATUS.md index c438894..32670ae 100644 --- a/STATUS.md +++ b/STATUS.md @@ -274,6 +274,23 @@ swept. Not all six need to complete for a defensible thesis submission — see t ## Log +- **2026-09-05** — **`npx skills` install path cleaned up after Beat's feedback.** + Beat's mail called the install instructions far too long and asked us to use + for all local agents. The one-command route + already existed (INSTALL.md Route C, Step 0) but was dirty: the CLI also scans + `.claude/skills/` and `.codex/skills/`, so it found **12** skills — the ten public ones + plus the repo-internal `create-thesis-sim-student` and `run-thesis-simulations`, which + `--skill '*'` happily installed into a student's client. Marked both (in each agent + directory) with `metadata.internal: true`, which the CLI honours for the picker *and* + the `'*'` wildcard (`src/skills.ts`, `src/add.ts` in vercel-labs/skills); the key is + part of the Agent Skills frontmatter spec, so both skills still load repo-locally. + Verified end-to-end: `npx skills@latest add --list` now reports exactly 10, and + a real `--skill '*' --agent claude-code` install produces ten skill folders with their + `references/` intact. Added `skills/tests/test_skills_cli_install.py` (fails if a + maintainer skill loses the flag) and extended `qa.yml`'s path filter to the two agent + skill directories. INSTALL.md itself untouched — shortening it is a separate call. + Branch `feat/skills-cli-install`. + - **2026-07-05** — **Independent 1.0-readiness review completed and actioned; project re-paused with Phase 5 fully scoped.** Ran a from-scratch, deliberately skeptical review (`findings/no_db_universal_skill/2026-07-05-fable-1.0-readiness-review.md`) assessing diff --git a/skills/tests/test_skills_cli_install.py b/skills/tests/test_skills_cli_install.py new file mode 100644 index 0000000..b7e2266 --- /dev/null +++ b/skills/tests/test_skills_cli_install.py @@ -0,0 +1,65 @@ +"""Deterministic checks for the `npx skills add` install surface. + +The Vercel skills CLI (https://github.com/vercel-labs/skills) is the one-command +route for local agents. It discovers skills in `skills/` *and* in agent +directories such as `.claude/skills/` and `.codex/skills/`, where this repo keeps +its maintainer-only simulation skills. Those must stay hidden behind +`metadata.internal: true`, or students get them listed in the picker and +installed by `--skill '*'`. +""" + +from __future__ import annotations + +import re +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[2] +SCANNED_DIRS = ( + REPO_ROOT / "skills", + REPO_ROOT / ".claude" / "skills", + REPO_ROOT / ".codex" / "skills", +) +INTERNAL_SKILLS = { + "create-thesis-sim-student", + "run-thesis-simulations", +} +INTERNAL_MARKER = re.compile(r"^metadata:\n(?:[ \t]+.*\n)*?[ \t]+internal:[ \t]*true[ \t]*$", flags=re.MULTILINE) + + +def _frontmatter(skill_md: Path) -> str: + match = re.match(r"^---\n(?P.*?)\n---\n", skill_md.read_text(encoding="utf-8"), flags=re.DOTALL) + assert match, f"{skill_md} is missing YAML frontmatter" + return match.group("body") + "\n" + + +def _discovered_skills() -> dict[str, list[Path]]: + found: dict[str, list[Path]] = {} + for scanned_dir in SCANNED_DIRS: + assert scanned_dir.is_dir(), f"{scanned_dir} is missing" + for path in sorted(scanned_dir.iterdir()): + skill_md = path / "SKILL.md" + if path.is_dir() and skill_md.is_file(): + found.setdefault(path.name, []).append(skill_md) + return found + + +def _public_skills() -> set[str]: + """The published package, as pinned by test_skill_package.py.""" + skills_dir = REPO_ROOT / "skills" + return {path.name for path in skills_dir.iterdir() if path.is_dir() and path.name != "tests" and (path / "SKILL.md").is_file()} + + +def test_installer_offers_exactly_the_public_skills() -> None: + offered = {name for name, skill_mds in _discovered_skills().items() if any(not INTERNAL_MARKER.search(_frontmatter(skill_md)) for skill_md in skill_mds)} + + assert offered == _public_skills() + + +def test_maintainer_skills_are_marked_internal() -> None: + discovered = _discovered_skills() + assert INTERNAL_SKILLS <= set(discovered) + + for name in INTERNAL_SKILLS: + for skill_md in discovered[name]: + assert INTERNAL_MARKER.search(_frontmatter(skill_md)), f"{skill_md} is missing metadata.internal: true"