Files
Nico Bellante d84e3344da Replace developing-with-streamlit with meta-skill for bundled skill discovery (#26)
## Summary

Replaces the old `developing-with-streamlit` skill (21 sub-skills + 17
templates) with a single lightweight **meta-skill** that discovers
Streamlit's bundled skills inside the pip package (>=1.57). One
user-level install works across every project; the discovered skills
always match each project's pinned Streamlit version.

Net diff: **+1,171 / −12,738** across 99 files.

## The contract

`developing-with-streamlit/scripts/discover.py` is the actual logic —
stdlib-only Python, cross-platform via `pathlib` + the `venv` module's
Windows/POSIX awareness. It detects the active Python interpreter
(`$VIRTUAL_ENV` → `./.venv` → `../.venv` → `<git-root>/.venv` →
`$CONDA_PREFIX` → `pipenv` if `Pipfile` → `poetry` if `poetry.lock` →
`pdm` if `pdm.lock` → `uv` if `uv.lock` → system), runs `import
streamlit; print(streamlit.__path__[0])`, and emits one of six exit
codes:

| Exit | Meaning | Agent action |
|---|---|---|
| 0 | Bundled SKILL.md found | Read printed path, follow its routing |
| 1 | Streamlit not installed | Confirm with user, then install with the
**single** package-manager-matched command in the error (`poetry add
streamlit` for poetry, `uv add streamlit` for uv, `<venv-python> -m pip
install streamlit` for venv, etc.) — no buffet |
| 2 | Streamlit < 1.57 | Suggest `pip install --upgrade streamlit` for
version-matched bundled skills; fall back to
`docs.streamlit.io/llms-full.txt` if user can't upgrade |
| 3 | No Python interpreter | Ask user to install Python 3.10+ (suggest
`uv` as the easy path) |
| 4 | Bundled skills exist but expected sub-path missing | Read listed
skills directly |
| 5 | Invalid argument | Fix invocation |

`SKILL.md` is a thin wrapper that documents this contract and tells the
agent to invoke `python <SKILL_DIR>/scripts/discover.py --project-dir
<USER_PROJECT_DIR>`. The `--project-dir` arg makes resolution
deterministic regardless of the agent's CWD at invocation time.

`detect_interpreter()` returns `(cmd, tag)` where `tag` identifies which
detection branch fired. `install_advice(cmd, tag)` maps the tag to one
matching install command, so a poetry project's exit-1 message points at
`poetry add streamlit` instead of dumping pip + uv + poetry + pipenv +
conda and asking the agent to guess.

Why a script instead of prose: same logic runs in production AND in
tests, so no parallel reference impl can drift. Pure-prose meta-skills
need LLM-in-the-loop evals to validate; this gets the bulk of realistic
failure modes from cheap pytest at zero LLM cost — modulo the
description-triggering question, which only LLM eval can answer.

## Tier 1 test matrix

19 pytest tests (26 cases including parametrized) in
`tests/discovery/test_discovery.py`. Each E2E test creates an isolated
`tmp_path` venv via the stdlib `venv` module, installs Streamlit fresh,
runs `discover.py`, and asserts the contract. Coverage:

- **Detection priority**: each interpreter source in our priority order,
including `<git-root>/.venv` (helpful for deep monorepo layouts)
- **Conflict**: `$VIRTUAL_ENV` wins over `./.venv`
- **Fallbacks**: missing-Streamlit (exit 1), pre-1.57 (exit 2),
upstream-restructured (exit 4)
- **Invocation contract**: `--project-dir` argument from outside the
project
- **Tool-marker detection**: `Pipfile` (pipenv), `poetry.lock` (poetry),
`pdm.lock` (pdm), `uv.lock` (uv)
- **uv branch isn't too eager**: doesn't fire on bare `pyproject.toml`
without `uv.lock`
- **Tool-aware install advice**: `install_advice` parametrized over all
10 tags (virtual-env, venv-local, venv-parent, venv-git-root, conda,
pipenv, poetry, pdm, uv, system) — guarantees the per-tag mapping
doesn't regress

Negative-test verified: temporarily reversing priority order in
`discover.py` causes `test_priority_venv_over_local` to fail with a
clear diagnostic; reverting the `uv.lock` marker causes
`test_uv_no_lockfile` to fail similarly. Both prove the tests catch the
regressions they're designed for.

CI runs on `ubuntu-latest` AND `windows-latest`, on PR + push to main +
weekly cron. Tests install whatever `streamlit` is currently latest on
PyPI — failures here are an honest signal that something broke, either
in our code or in upstream's bundled-skill layout.

### Latest passing run

[Run
25827312532](https://github.com/streamlit/agent-skills/actions/runs/25827312532)
on commit `7fd4fae`:

| Job | Result | Duration |
|---|---|---|
|
[`ubuntu-latest`](https://github.com/streamlit/agent-skills/actions/runs/25827312532/job/75883639893)
| 26 passed | 4m 33s |
|
[`windows-latest`](https://github.com/streamlit/agent-skills/actions/runs/25827312532/job/75883639903)
| 26 passed | 9m 13s |

All 26 cases pass on both runners — no skips. Every Windows-specific
concern (Scripts vs bin, `python.exe` vs `python`, path separators,
`Pipfile` / `poetry.lock` / `pdm.lock` / `uv.lock` markers) is
empirically validated. Each E2E test pins the resolved path to its own
venv via `assert_resolves_bundled(..., inside=...)`, so a
wrong-but-valid Streamlit install elsewhere on the runner can't silently
satisfy the assertion.

## End-to-end verification (LLM-in-the-loop)

Hands-on smoke test in a fresh Claude Code v2.1.128 session — done by me
before opening this for review, not by an automated script.

**Setup:** Built a `streamlit==1.57.0` venv at
`/tmp/test-streamlit-install-2/`, installed the skill at
`~/.claude/skills/developing-with-streamlit/`. Started Claude Code in
the project directory with no prior context, no `CLAUDE.md`, no skill
pre-selected.

**Prompt:**

> *I want to add a small KPI dashboard to a new app.py in this directory
— three metric cards showing total revenue, active users, and conversion
rate. Use placeholder values for now. Make it look polished.*

**Observed (full trajectory captured in the session JSONL — 14 tool
calls):**

1. Description match → `Skill(developing-with-streamlit)` fires
automatically. The prompt never names the skill or mentions `discover`.
2. `Bash` runs `discover.py --project-dir <project>` with **no
permission prompt** — the `allowed-tools` frontmatter pre-approves the
bundled script.
3. Stdout returns the bundled `SKILL.md` path inside the venv.
4. `Read` bundled SKILL.md, then `references/dashboards.md` and
`references/design.md`.
5. `ls` + `Read` of the `dashboard-metrics` template.
6. `Write` `app.py` using bundled patterns: `st.metric(border=True)`,
horizontal container, Material icons, sparkline trends.
7. Step 4 of the bundled SKILL.md respected — agent offered to run the
app rather than auto-running.

Whole chain held end-to-end. No skill misroutes, no silent fallthroughs,
no freelancing of Streamlit code without first reading the bundled refs.

## Acknowledged gaps

- **Multi-env disambiguation** — user with both conda + venv pinning
different Streamlit versions gets whichever the priority order picks.
Acceptable without a `cortex env detect`-style CLI.
- **Cross-agent `allowed-tools` support** — the pre-approval works in
Claude Code but is ignored by Cursor / Copilot / Gemini / Codex. They
fall back to their own permission prompts, which is the safe default —
just slightly more friction on first invocation in those agents.
- **Cross-agent end-to-end validation** — the smoke test above was
Claude Code only. Other agents follow the same Agent Skills spec and
should work via `npx skills add`, but individual agents haven't been
verified hands-on.

## Manual verification

All three confirmed by the cold-start hands-on test above:

- [x] Description's trigger phrases auto-fire the skill (no prompt
mentioned `developing-with-streamlit` or `discover`).
- [x] Agent invokes `scripts/discover.py` rather than winging it from
prose.
- [x] `allowed-tools` frontmatter pre-approves the bundled `discover.py`
(no bash permission prompt on first invocation in Claude Code).
2026-05-13 15:06:46 -07:00
..