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
140 changes: 116 additions & 24 deletions skills/setup-docs/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,36 @@ For refreshing already-tracked libraries after upgrades, use `sync-docs`.
dependencies.
- Major reorganization: the user explicitly wants to rebuild the full doc set.

## Design principle — narrow by default, expand on request

Fetching docs for **every** dependency sounds complete, but in practice it
pollutes `AGENTS.md` with toolchain packages (ESLint plugins, build tools,
type-only shims) that an AI agent will never reference when writing
application code. It also balloons wall-clock time and failure rate, since
those packages rarely have docs in the places the registry/sources look.

This skill therefore defaults to a **focused set**:

1. **Runtime `dependencies` only** — skip `devDependencies` and `peerDependencies`
unless the user asks for them.
2. **Deny-list filter** — even inside `dependencies`, drop packages that match
the patterns in [`references/deny-list.md`](./references/deny-list.md)
(linters, bundlers, test runners, git hooks, polyfills, etc.).
3. **User override is always one step away** — the confirmation prompt shows
what was excluded so the user can flip the decision with `include-dev`,
`include <name>`, or `all`.

The rationale is: optimize for the signal-to-noise ratio of `AGENTS.md`, not
for exhaustiveness. A curated list of 8 libraries the AI will actually use
is more valuable than an exhaustive list of 60 where 50 are build plumbing.

## Pipeline

```
parse manifest + lockfile → derive (name, version) list → confirm with user
→ for each: run add-docs steps 1–6.5 (writes config + ask.lock per entry)
parse manifest + lockfile → derive (name, version) list
→ apply default scope (dependencies only) + deny-list filter
→ confirm with user (show included / deny-filtered / devDeps buckets)
→ for each confirmed: run add-docs steps 1–6.5 (writes config + ask.lock per entry)
→ final pass: rebuild AGENTS.md block once → ensure CLAUDE.md @AGENTS.md
```

Expand All @@ -45,42 +70,109 @@ Detect the ecosystem the same way `add-docs` Step 2 does, then parse the relevan
Always prefer the **lockfile** for the resolved version; fall back to the manifest's
declared range only when no lockfile exists.

| Ecosystem | Manifest | Lockfile (preferred for version) |
| Ecosystem | Manifest (default scope: direct runtime deps the project itself declares) | Lockfile (preferred for version) |
|---|---|---|
| npm | `package.json` (`dependencies` + `devDependencies` + `peerDependencies`) | `bun.lock`, `bun.lockb`, `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock` |
| pypi | `pyproject.toml` (`[project.dependencies]` or `[tool.poetry.dependencies]`) or `requirements.txt` | `poetry.lock`, `uv.lock`, pinned `requirements.txt` |
| go | `go.mod` (`require` block) | `go.sum` (versions are already in `go.mod`) |
| crates | `Cargo.toml` (`[dependencies]`) | `Cargo.lock` |
| pub | `pubspec.yaml` (`dependencies`) | `pubspec.lock` |
| hex | `mix.exs` (`deps/0`) | `mix.lock` |
| maven | `pom.xml` or `build.gradle` / `build.gradle.kts` (`dependencies` block) | Maven resolves at build time; no separate lockfile |
| npm | `package.json` `dependencies` only | `bun.lock`, `bun.lockb`, `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock` |
| pypi | `pyproject.toml` `[project.dependencies]` / `[tool.poetry.dependencies]`, or `requirements.txt` (skip `-dev` / `-test` files) | `poetry.lock`, `uv.lock`, pinned `requirements.txt` |
| go | `go.mod` `require` block, direct deps only (skip entries marked `// indirect`) | `go.sum` (versions are already in `go.mod`) |
| crates | `Cargo.toml` `[dependencies]` (skip `[dev-dependencies]` / `[build-dependencies]`) | `Cargo.lock` |
| pub | `pubspec.yaml` `dependencies` (skip `dev_dependencies`) | `pubspec.lock` |
| hex | `mix.exs` `deps/0` where `:only` is not `:dev` or `:test` | `mix.lock` |
| maven | `pom.xml` `dependencies` with `scope != test` / `provided`, or `build.gradle(.kts)` `implementation`/`api` (not `testImplementation`) | Maven resolves at build time; no separate lockfile |

For each dependency, produce a `(ecosystem, name, version)` triple. If the lockfile gives
a precise version (e.g. `3.22.4`), use that. If only a range is available (e.g. `^3.22`),
pass the range to `add-docs` and let the source resolve it.

**Skip**: workspace-internal packages, `link:`/`file:`/`workspace:` deps, and anything
that resolves to a path. These don't have docs to download.
**Always skip**, regardless of user override: workspace-internal packages,
`link:`/`file:`/`workspace:` deps, and anything that resolves to a path.
These don't have docs to download.

**Default-skip (user can override)**: dev/test/build scopes in the table
above **and** npm `peerDependencies` (which are expected to be provided
by the host project, not owned by it). If the user asks for `include-dev`,
re-read the manifest and merge both the dev/test/build scopes **and**
`peerDependencies` into the candidate list before the deny-list filter
runs. Track dev/test/build and peer scopes as separate buckets so Step 2
can list them independently in the confirmation output.

## Step 1.5 — Apply the deny-list

Read [`references/deny-list.md`](./references/deny-list.md) and drop any
candidate whose package name matches a glob pattern listed there. Keep the
dropped entries in a separate bucket so Step 2 can show them to the user.

Matching rules:

- Glob-style (`*` = any chars, `?` = one char), case-sensitive.
- Applied to the package name only (no version, no ecosystem prefix).
- Works across ecosystems — patterns that only make sense for npm
(`@types/*`) simply won't match non-npm names.

If the user says `all`, skip this step entirely.
If the user says `include <name>` (or a comma-separated list), move each
name out of whichever **soft-skip** bucket it currently lives in — the
deny-list bucket, the devDependencies bucket, or the peerDependencies
bucket — and put it back into the keep bucket before Step 2.
`include <name>` is the single-package escape hatch for *soft* skips only;
it does **not** override the always-skip rules in Step 1 (workspace /
`link:` / `file:` / path deps). Those have no downloadable docs, so
forcing them in would just produce errors.

## Step 2 — Show the plan and confirm

Print the derived list to the user as a table or bullet list, grouped by ecosystem if
mixed. Then **stop and ask for confirmation** before fetching anything. Example:
Print the derived buckets so the user can see exactly what's in and
what's out. Show every soft-skip bucket that has at least one entry —
deny-list, devDependencies, and peerDependencies each get their own
line so nothing is silently dropped:

```
About to fetch docs for 14 dependencies:
ASK setup plan for <project> (default scope: direct runtime dependencies)

Will fetch (8):
npm:
- zod@3.22.4
- hono@4.6.2
- drizzle-orm@0.36.0
- @mastra/core@0.5.2
...

Skipped by deny-list (4):
eslint, prettier, typescript, @types/node

Skipped devDependencies (23):
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
husky, lint-staged, turbo, vitest, tsup, ...

Skipped peerDependencies (2):
react, react-dom

Proceed? Options:
- yes fetch the 8 packages above
- include-dev also fetch devDependencies AND peerDependencies
(both still subject to deny-list)
- include <names> force-include specific packages from any soft-skip
bucket (comma-separated)
- all disable deny-list AND include dev + peer deps
- select pick a subset interactively
- cancel
```

npm:
- zod@3.22.4
- hono@4.6.2
- drizzle-orm@0.36.0
...
Omit any bucket whose count is zero — e.g. a Go project won't have a
`peerDependencies` bucket at all. Always-skip entries from Step 1
(workspace / path deps) are never shown as a bucket because they are
not recoverable through any user override.

Proceed? (yes / select subset / cancel)
```
**Stop and wait** for the user's answer. Never start fetching silently —
mass downloads against upstream registries deserve an explicit checkpoint.

If the user says `include-dev` or `all`, recompute the buckets and
re-display before proceeding. If they say `include foo,bar`, move those
names from whichever soft-skip bucket they live in (deny-list,
devDependencies, or peerDependencies) back into the keep bucket and
confirm once more with the updated plan.

If the list is large (>30 entries), warn the user about wall-clock time and offer to
filter (e.g. only `dependencies`, skip `devDependencies`).
If the final keep-bucket is still large (>30 entries after overrides),
warn about wall-clock time before starting.

## Step 3 — Run `add-docs` Steps 1–6.5 for each entry

Expand Down
131 changes: 131 additions & 0 deletions skills/setup-docs/references/deny-list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Default deny-list — packages excluded from `setup-docs` by default

`setup-docs` reads this file and drops any dependency whose name matches one
of the patterns below **before** showing the confirmation plan. The goal is
to keep `AGENTS.md` focused on libraries an AI agent will actually reference
when writing application code — not on the toolchain that builds or lints it.

Matching is glob-style (`*` = any characters, `?` = one character). Matching
is applied to the **package name** (not version), case-sensitive, and works
across ecosystems — entries that are npm-specific (`@types/*`) simply don't
match non-npm names.

Users can always override:

- `include-dev` — also pulls in `devDependencies` and `peerDependencies`
(plus dev-scope equivalents for non-npm ecosystems). Both are still
subject to the deny-list below.
- `include <name>` — force-include a specific package from any
*soft-skip* bucket (deny-list, devDependencies, or peerDependencies).
Comma-separated names are allowed: `include foo,bar`. This does not
override the always-skip rules in SKILL.md Step 1 (workspace / `link:`
/ `file:` / path deps) — those produce no downloadable docs.
- `all` — disable the deny-list **and** include `devDependencies` /
`peerDependencies` in a single shot. Use when you explicitly want the
previous exhaustive behavior.

## Categories

### Type-only packages

AI agents read types from the compiler, not from a separate docs tarball.

- `@types/*`

### Linters / formatters / style

The tool itself rarely needs docs at code-writing time. If the user is
writing an ESLint plugin or a Prettier config, they will ask for
`add-docs eslint` explicitly.

- `eslint`
- `eslint-*`
- `eslint-config-*`
- `eslint-plugin-*`
- `@eslint/*`
- `@typescript-eslint/*`
- `@stylistic/*`
- `@antfu/eslint-config`
- `prettier`
- `prettier-plugin-*`
- `@prettier/*`
- `biome`
- `@biomejs/*`
- `stylelint`
- `stylelint-*`
- `dprint`
- `rome`

### Build tools / bundlers / transpilers

- `vite`
- `@vitejs/*`
- `webpack`
- `webpack-*`
- `rollup`
- `rollup-*`
- `@rollup/*`
- `esbuild`
- `esbuild-*`
- `tsup`
- `unbuild`
- `parcel`
- `@parcel/*`
- `turbo`
- `nx`
- `@nx/*`
- `tsx`
- `ts-node`
- `typescript` (types come from the compiler itself)
- `swc`
- `@swc/*`
- `babel`
- `@babel/*`
- `babel-*`

### Test runners / testing libraries

- `vitest`
- `@vitest/*`
- `jest`
- `@jest/*`
- `jest-*`
- `ts-jest`
- `mocha`
- `ava`
- `tap`
- `node-tap`
- `@testing-library/*`
- `playwright`
- `@playwright/test`
- `cypress`

### Git hooks / commit tooling / release automation

- `husky`
- `lint-staged`
- `commitlint`
- `@commitlint/*`
- `release-please`
- `release-please-*`
- `semantic-release`
- `@semantic-release/*`
- `changeset`
- `@changesets/*`

### Polyfills / runtime shims

- `core-js`
- `core-js-*`
- `regenerator-runtime`
- `tslib`

## How to extend

If you keep hitting the same noisy package in multiple projects, append it
to the relevant category above. Keep the list sorted within each section
alphabetically to make diffs easy to review.

If a user reports that a denied package actually does need docs in their
workflow, don't silently edit the list — tell them to pass `include <name>`
so the decision stays explicit and project-scoped.