diff --git a/skills/ask/SKILL.md b/skills/ask/SKILL.md new file mode 100644 index 0000000..7dfb96e --- /dev/null +++ b/skills/ask/SKILL.md @@ -0,0 +1,106 @@ +--- +name: ask +description: Fetch version-accurate library documentation, source trees, and producer-shipped skills so the agent works against the exact version installed in the project, not training-data guesses. Use this skill whenever the user needs docs for a dependency, wants to read a library's real source, asks "how does X work internally", needs to pin reading to a specific version or ref, mentions ask docs / ask src / ask skills, or any task that would benefit from a library's actual README / source / skill files over recalled knowledge — even when they don't explicitly name the "ask" CLI. Preferred over inferring API shape from memory whenever accuracy matters. +allowed-tools: Bash(ask:*) +--- + +# Version-Accurate Docs & Source with `ask` + +`ask` resolves the version from the project's lockfile +(`bun.lock → package-lock.json → pnpm-lock.yaml → yarn.lock → package.json` +range fallback), fetches docs or source once, caches them globally at +`~/.ask/` (override via `ASK_HOME`), and prints absolute paths to stdout so +the commands compose naturally in shell substitutions. Progress / errors go +to stderr, paths go to stdout — safe for `$(ask …)`. + +## Core Pattern + +```bash +# Docs — one candidate path per line +cat "$(ask docs zod | head -n1)"/README.md +rg "parseAsync" $(ask docs zod) + +# Source — single absolute path to the checkout root +rg "ZodError" $(ask src zod) +fd -e test.ts . $(ask src zod) + +# Producer-shipped skills — one /skills/ dir per line +ls $(ask skills vercel/ai) +``` + +`ask docs` emits candidate documentation directories (publish-time +`dist/docs` first, then any subdirectory whose basename matches `/doc/i` +up to depth 4, falling back to the checkout root when nothing matches). +`ask src` emits exactly one path: the checkout root. Both auto-fetch on +cache miss; pass `--no-fetch` to fail fast (exit 1) on miss instead. + +## Spec Grammar + +``` +zod # bare → npm ecosystem (resolved via lockfile) +npm:next # explicit ecosystem +npm:@mastra/client-js # scoped package +facebook/react # owner/repo → github:facebook/react@main +github:vercel/next.js@v14.2.3 # pinned tag +github:owner/repo@main # pinned branch +``` + +- For `npm:` specs (and bare names), the version comes from the project's + lockfile. Append `@version` to pin explicitly: `zod@3.22.0`, + `npm:next@14.2.3`. +- For `github:` specs, `@` pins a tag or branch. Bare `owner/repo` + (no `@ref`) defaults to `main`. +- Any ref works with these one-shot reading commands — branches, tags, + or mutable refs like `main` / `master` are all accepted, since + nothing is persisted. + +## One-Shot Reading Commands + +| Command | Output | Use when | +|---------|--------|----------| +| `ask docs [--no-fetch]` | Candidate doc dirs, one per line | You want README / guides / handwritten docs at the installed version | +| `ask src [--no-fetch]` | Checkout root, single line | You need to read real source, search all files, follow implementations | +| `ask skills ` (= `ask skills list`) | `/skills/` dirs, one per line | The library ships its own Claude / Cursor / OpenCode skills | + +All three share `ensureCheckout`, so the cached path is reused across +commands — calling `ask docs`, then `ask src`, then `ask skills list` on +the same spec fetches once. + +## When You Need More + +Lazy-load these references only when the situation calls for them: + +- **Managing the cache** — disk pressure, stale entries, `--kind` / + `--older-than` filters, legacy v1 layout cleanup → + [`references/cache.md`](references/cache.md). +- **Project-level declarative workflow** — `ask.json`, `ask install`, + `ask add`, `ask remove`, `ask list`, auto-regenerated `AGENTS.md` and + per-library `.claude/skills/-docs/SKILL.md` → + [`references/declarative-workflow.md`](references/declarative-workflow.md). +- **Vendoring producer skills into this project** — `ask skills install`, + `--force`, `--agent claude,cursor,opencode,codex`, `ask skills remove + --ignore-missing` → + [`references/skills-vendoring.md`](references/skills-vendoring.md). + +## When to Reach for `ask` + +Reach for it when: + +- The installed version matters — otherwise the agent risks fabricating + API shape from an outdated training snapshot. +- The answer lives in source, not types — edge cases, error paths, + internal helpers, behavior that isn't documented anywhere else. +- A library may ship its own skills — `ask skills list ` discovers + producer-side `skills/` directories without touching the project. + +Skip it when TypeScript / LSP / intellisense can answer the question, or +when the user has already pointed at a specific file path. + +## Why This Exists + +Training data ages; lockfiles don't. `ask` bridges the two by pinning +every read to the version the project actually runs, so generated code +reflects reality instead of last year's docs. The `$(ask …)` idiom is +the main ergonomic: it turns a cached path into a first-class argument +to `rg`, `cat`, `fd`, or any tool that accepts a path — no extra API to +learn. diff --git a/skills/ask/references/cache.md b/skills/ask/references/cache.md new file mode 100644 index 0000000..cf82e81 --- /dev/null +++ b/skills/ask/references/cache.md @@ -0,0 +1,93 @@ +# Cache Management (`ask cache`) + +Read this when disk is filling up, a cached entry is stale, or you need +to inspect what `ask` has fetched. The global store lives at `~/.ask/` +by default; set `ASK_HOME` to relocate it. Every `ask install` writes +`/STORE_VERSION` (currently `"2"`) as a layout marker. + +## Store Layout (v2) + +``` +/ +├── npm/@/ # npm tarball extractions +├── github///// # github shallow clones (PM-unified) +├── web// # crawled doc sites +├── llms-txt/@/ # llms.txt imports +├── STORE_VERSION # "2" +└── .quarantine/-/ # corrupt entries, preserved for inspection +``` + +`host` is currently always `github.com` — the segment exists so +`gitlab.com` / `bitbucket.org` can slot in without a migration. + +## `ask cache ls [--kind ]` + +List every entry in the store with its size. + +```bash +ask cache ls # everything +ask cache ls --kind npm # filter: npm | github | web | llms-txt +``` + +Entries print as `/ `. Total size is summarized at +the end. If a legacy (pre-v2) `github/db` or `github/checkouts` +directory exists, its entries are tagged with a `(legacy) ` prefix in +the key — that's the signal to run `ask cache clean --legacy`. + +## `ask cache gc [--dry-run] [--older-than ]` + +Remove entries that no project references anymore. `gc` discovers +references by walking `$HOME` (or each path in `ASK_GC_SCAN_ROOTS`, +colon-separated) for `.ask/resolved.json` files and treating the +entries listed there as roots. + +```bash +ask cache gc # remove all unreferenced +ask cache gc --dry-run # preview only, no deletion +ask cache gc --older-than 30d # also require age > 30 days +ask cache gc --older-than 12h # supports d / h / m / s +ASK_GC_SCAN_ROOTS=/repos:/work ask cache gc # restrict scan +``` + +Always try `--dry-run` first when `ASK_GC_SCAN_ROOTS` is unset — the +default `$HOME` scan may miss project directories stored elsewhere, +which would mark still-used entries as unreferenced. + +## `ask cache clean --legacy` + +Remove the pre-v2 github store layout. The flag is required; calling +`ask cache clean` without it exits with a reminder. + +```bash +ask cache clean --legacy +``` + +Deletes: +- `/github/db/` (bare-clone DB from the old shared layout) +- `/github/checkouts/` (per-ref worktrees from the old layout) + +The v2 layout (`/github/////`) is +untouched. Safe to run anytime; it's a no-op when no legacy dirs exist. + +## Environment Variables + +| Variable | Purpose | Default | +|-----------------------|--------------------------------------------|----------------| +| `ASK_HOME` | Override the global store root | `~/.ask` | +| `ASK_GC_SCAN_ROOTS` | Colon-separated scan roots for `cache gc` | `$HOME` | + +Both are read at invocation time — set them inline or export per shell. + +## Troubleshooting Tips + +- "Corrupted store entry … quarantined to …" — `ask install` moved a + tamper/missing-stamp entry into `.quarantine/`. Re-run the install; + the fresh fetch replaces it. Delete the quarantine dir manually once + you've inspected it. +- `ask docs ` prints nothing — the checkout exists but has no + `/doc/i` subdirs AND the walker can't see the root. Try + `ask src ` and `ls $(ask src )` to confirm the checkout + is populated. +- `gc` removed an entry you still needed — either the project wasn't + inside `ASK_GC_SCAN_ROOTS`, or `.ask/resolved.json` was missing / + out of date. Re-run `ask install` in the project to rebuild both. diff --git a/skills/ask/references/declarative-workflow.md b/skills/ask/references/declarative-workflow.md new file mode 100644 index 0000000..bcd0ed8 --- /dev/null +++ b/skills/ask/references/declarative-workflow.md @@ -0,0 +1,137 @@ +# Declarative Workflow (`ask.json` + `ask install`) + +Read this when the user wants a checked-in list of libraries the +project's agents should reference, or wants `AGENTS.md` auto-regenerated +with lazy `ask src` / `ask docs` pointers. The one-shot reading +commands (`ask docs` / `ask src` / `ask skills list`) don't persist +anything; this flow does. + +`ask install` is **lazy-first**: it resolves versions and writes +`AGENTS.md` plus a per-library `.claude/skills/-docs/SKILL.md` +that tells agents to invoke `ask src ` / `ask docs ` on +demand. No documentation is downloaded during `install` — fetching +happens the first time an agent calls `ask docs` / `ask src`. + +## What Gets Written + +- `ask.json` — the declarative input. Array of spec strings. Checked + into git; edited by `ask add` / `ask remove` (or by hand). +- `AGENTS.md` — block between `` + and `` maintained by `ask`. +- `.claude/skills/-docs/SKILL.md` — one skill per declared + library, each delegating to `ask docs ` / `ask src `. +- Ignore files (`.gitignore`, `.prettierignore`, etc.) — patched via + `# ask:start … # ask:end` marker blocks. Don't hand-edit inside + those markers; `install` / `remove` overwrite them. + +## `ask install` + +Reads `ask.json`, resolves each spec to a concrete version, regenerates +per-library skills, and rewrites the `AGENTS.md` block. + +```bash +ask install +``` + +Per-entry behavior: + +1. **Version resolution** + - `npm:` specs → read lockfile chain + `bun.lock → package-lock.json → pnpm-lock.yaml → yarn.lock → + package.json` (range fallback). Missing from every lockfile ⇒ + entry skipped with a warning. + - `github:` specs → the ref encoded in the spec + (`github:vercel/next.js@v14.2.3`). No lockfile lookup. + - Explicit `@version` on any spec wins over lockfile resolution. +2. **Skill generation** — writes `.claude/skills/-docs/SKILL.md`. +3. **AGENTS.md regeneration** — rewrites the auto-generated block + with every resolved library. + +`postinstall`-friendly: per-entry failures emit a warning; the overall +exit code is always 0, so an unresolvable entry doesn't break +`bun install`. + +## `ask add ` / `ask add` (interactive) + +Appends to `ask.json`, then runs install for just that entry. + +```bash +ask add npm:next +ask add npm:@mastra/client-js +ask add github:vercel/next.js@v14.2.3 +ask add facebook/react # bare owner/repo → github:facebook/react +ask add # interactive picker +``` + +Bare names without `:` or `/` (e.g. `ask add zod`) are rejected with a +hint listing the two valid forms. Note the asymmetry: the one-shot +`ask docs zod` DOES accept bare names — only `ask add` is strict +because the spec is persisted. + +## `ask remove ` + +Removes from `ask.json`, deletes the generated skill, re-runs install +to regenerate `AGENTS.md`. + +```bash +ask remove next +ask remove @mastra/client-js +ask remove npm:next # also accepts the full spec +``` + +The `name` match is tried against the full spec, the spec body, and +the derived library slug — so the unscoped name, the full spec, or the +scoped package name all work. + +## `ask list [--json]` + +Prints declared libraries with their resolved versions. Unresolved +entries (declared in `ask.json` but missing from every lockfile) show +`version: unresolved` so drift is visible. + +```bash +ask list +ask list --json | jq '.entries[] | select(.version == "unresolved")' +``` + +JSON shape (`ListModelSchema`): `{ "entries": [...], "conflicts": +[...], "warnings": [...] }`. Each entry has `name`, `version`, +`format`, `source`, `location`, plus optional `itemCount` / `skills`. + +## `ask.json` Shape + +Strict array of ecosystem-prefixed spec strings — schema is +`z.array(z.string().regex(/^[a-z][a-z0-9+-]*:.+$/))` with `.strict()`. +Object entries are rejected. + +```json +{ + "libraries": [ + "npm:next", + "npm:@mastra/client-js", + "npm:zod@3.22.0", + "github:vercel/next.js@v14.2.3", + "github:vercel/ai@v5.0.0" + ] +} +``` + +- `npm:` specs: append `@` to pin, otherwise the lockfile + decides. +- `github:` specs: append `@` to encode the version inside the + spec string. `github:` without a ref defaults to `latest` in the + generated output. +- No separate `ref`, `source`, or `docsPath` keys. Per-library + `docsPath` comes from the ASK Registry when `ask docs` / `ask src` + actually fetch, not from `ask.json`. + +## Intent-Format and Resolved-Cache Notes + +Prior versions of `ask` had a `.ask/resolved.json` lockfile, a +`` block in `AGENTS.md` for +TanStack-intent packages, and a `--allow-mutable-ref` flag. In the +current lazy-first architecture `ask install` does not write +`.ask/resolved.json` and does not populate intent blocks; the +underlying code modules still exist but are not wired into the +install path. Treat the docs, source, and skills commands as the +supported surface, and `ask.json` as a strict spec-string array. diff --git a/skills/ask/references/skills-vendoring.md b/skills/ask/references/skills-vendoring.md new file mode 100644 index 0000000..502d286 --- /dev/null +++ b/skills/ask/references/skills-vendoring.md @@ -0,0 +1,109 @@ +# Vendoring Producer Skills (`ask skills install` / `remove`) + +Read this when the user wants a library's own skills (the ones the +library *publishes* for consumers) copied into this project and linked +into the agent's skills directory. The one-shot `ask skills list +` only prints paths; the commands here actually vendor and link. + +## The Core Flow + +`ask skills install ` does five things: + +1. Resolves and fetches the library the same way `ask src` does. +2. Collects each direct child of every `skills/` (or `skill/`) parent + directory shipped by the library — matched case-insensitively + against `^skills?$` exactly, so `my-skills/` / `skill-set/` are + ignored. +3. Vendors those children into `.ask/skills///` + in the project. `specKey` is derived from ecosystem + name + + resolved version (e.g. `npm__next__14.2.3`). +4. Symlinks each vendored skill into the detected agent skills + directories (`.claude/skills/`, `.cursor/skills/`, + `.opencode/skills/`, `.codex/skills/`). +5. Records the install in `.ask/skills-lock.json` and patches + `.gitignore` via the `ask` marker block. + +## `ask skills install ` + +```bash +ask skills install vercel/ai +ask skills install npm:@mastra/core +ask skills install github:owner/repo@v1.2.3 --no-fetch # cache hits only +ask skills install vercel/ai --force # overwrite conflicts +ask skills install vercel/ai --agent claude,cursor # explicit targets +``` + +| Flag | Effect | +|-------------------|------------------------------------------------------------------| +| `--no-fetch` | Return cache hit only — exit 1 on miss. | +| `--force` | Overwrite a conflicting `.claude/skills/` symlink. | +| `--agent ` | Explicit targets: `claude,cursor,opencode,codex`. Skips detection. | + +### Agent selection + +Without `--agent`: + +- 0 agents detected → error asking the user to pass `--agent `. +- 1 agent detected → auto-use it. +- 2+ detected → interactive multiselect prompt. + +Detection looks for the agent's directory (`.claude/`, `.cursor/`, +`.opencode/`, `.codex/`). If none exist, pass `--agent` to create the +target directory and force-install. + +### Exit conditions + +- No `skills/` (or `skill/`) dir found in the source → exit 1 with the + message `no skills/ directories found for `. +- No agent selected → exit 1. +- Symlink collision without `--force` → exit from `linkSkill` with a + descriptive error. + +## `ask skills remove ` + +Reverses a prior install using `.ask/skills-lock.json` as the source +of truth. Only removes symlinks that point at the vendored copy — a +user-edited skill in `.claude/skills/` that no longer points at +`.ask/skills///` is left alone. + +```bash +ask skills remove vercel/ai +ask skills remove npm__vercel-ai__5.0.0 # spec-key also accepted +ask skills remove vercel/ai --ignore-missing # exit 0 even if no lock entry +``` + +| Flag | Effect | +|---------------------|-------------------------------------------------------| +| `--ignore-missing` | Exit 0 when the spec has no lock entry. | + +On success, the vendor dir `.ask/skills//` is deleted and the +lock entry removed. + +## Directory Layout After Install + +``` +.ask/ +├── skills/ +│ └── / +│ ├── / +│ │ └── SKILL.md +│ └── / +│ └── … +└── skills-lock.json + +.claude/skills/ +├── -> ../../.ask/skills// +└── -> ../../.ask/skills// +``` + +Each skill is symlinked individually — no wrapper directory — so the +agent discovers them like any other project skill. + +## Docs vs. Intent-Format Distinction + +This command only handles the "skills" channel. Libraries that +distribute docs (not skills) go through `ask install` instead. If the +library is a TanStack-intent package (keywords include +`tanstack-intent`), `ask install` takes the intent path and writes an +`` block in `AGENTS.md`, independent of +the `ask skills install` flow described here.