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
106 changes: 106 additions & 0 deletions skills/ask/SKILL.md
Original file line number Diff line number Diff line change
@@ -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, `@<ref>` 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.

@cubic-dev-ai cubic-dev-ai Bot Apr 16, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: "nothing is persisted" is misleading: one-shot commands do persist fetched data in the global ASK cache. Clarify that only project declarative state is not persisted.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At skills/ask/SKILL.md, line 55:

<comment>"nothing is persisted" is misleading: one-shot commands do persist fetched data in the global ASK cache. Clarify that only project declarative state is not persisted.</comment>

<file context>
@@ -49,10 +49,10 @@ github:owner/repo@main          # pinned branch
+  (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
</file context>
Fix with Cubic


## One-Shot Reading Commands

| Command | Output | Use when |
|---------|--------|----------|
| `ask docs <spec> [--no-fetch]` | Candidate doc dirs, one per line | You want README / guides / handwritten docs at the installed version |
| `ask src <spec> [--no-fetch]` | Checkout root, single line | You need to read real source, search all files, follow implementations |
| `ask skills <spec>` (= `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/<name>-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 <spec>` 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.
93 changes: 93 additions & 0 deletions skills/ask/references/cache.md
Original file line number Diff line number Diff line change
@@ -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
`<askHome>/STORE_VERSION` (currently `"2"`) as a layout marker.

## Store Layout (v2)

```
<askHome>/
├── npm/<pkg>@<version>/ # npm tarball extractions
├── github/<host>/<owner>/<repo>/<ref>/ # github shallow clones (PM-unified)
├── web/<sha256-of-url>/ # crawled doc sites
├── llms-txt/<sha256>@<version>/ # llms.txt imports
├── STORE_VERSION # "2"
└── .quarantine/<ts>-<uuid>/ # 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 <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 `<kind>/<key> <size>`. 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 <duration>]`

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:
- `<askHome>/github/db/` (bare-clone DB from the old shared layout)
- `<askHome>/github/checkouts/` (per-ref worktrees from the old layout)

The v2 layout (`<askHome>/github/<host>/<owner>/<repo>/<ref>/`) 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 <spec>` prints nothing — the checkout exists but has no
`/doc/i` subdirs AND the walker can't see the root. Try
`ask src <spec>` and `ls $(ask src <spec>)` 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.
137 changes: 137 additions & 0 deletions skills/ask/references/declarative-workflow.md
Original file line number Diff line number Diff line change
@@ -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/<name>-docs/SKILL.md`
that tells agents to invoke `ask src <spec>` / `ask docs <spec>` 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 `<!-- BEGIN:ask-docs-auto-generated -->`
and `<!-- END:ask-docs-auto-generated -->` maintained by `ask`.
- `.claude/skills/<name>-docs/SKILL.md` — one skill per declared
library, each delegating to `ask docs <spec>` / `ask src <spec>`.
- 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/<name>-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 <spec>` / `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 <name>`

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 `@<version>` to pin, otherwise the lockfile
decides.
- `github:` specs: append `@<ref>` 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
`<!-- intent-skills:start -->` 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.
Loading