From sdd
Indexes repo ADRs, OpenSpec specs, source code, and tracker issues into qmd collections for hybrid BM25+vector+reranker search. Use for 'index this repo' or making code/specs searchable.
npx claudepluginhub joestump/claude-plugin-sdd --plugin sddThis skill uses the workspace's default tool permissions.
<!-- Governing: ADR-0015 (Markdown-Native Configuration), ADR-0016 (Workspace Mode), ADR-0024 (qmd hard dependency), ADR-0025 (Tracker Issues as Fourth qmd Collection), ADR-0026 (Tiered Index Freshness), SPEC-0019 REQ "Issues Collection Layout", SPEC-0019 REQ "Issues Collection Sync via /sdd:index" -->
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Guides code writing, review, and refactoring with Karpathy-inspired rules to avoid overcomplication, ensure simplicity, surgical changes, and verifiable success criteria.
Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Share bugs, ideas, or general feedback.
Create per-repository qmd collections so agents and humans can run hybrid search across a repo's ADRs, OpenSpec specs, source code, and tracker issues from a single query plane. Each repository owns four collections ({repo}-adrs, {repo}-specs, {repo}-code, {repo}-issues) so searches can be filtered cleanly with qmd query "..." -c {repo}-adrs. The issues collection is populated by syncing the configured tracker into .sdd/issues/{id}.md files (per ADR-0025 and references/tracker-sync.md). Workspace projects (ADR-0016) get one set of collections per module: {repo}-{module}-{kind}.
references/shared-patterns.md. If $ARGUMENTS contains --module <name>, scope to that module; otherwise, in a workspace, iterate all modules. The resolved ADR directory is {adr-dir} and spec directory is {spec-dir}, both per-module in workspace mode.Read $ARGUMENTS. The first positional token (ignoring --module <name>) selects the operation:
| Token | Operation |
|---|---|
add | Create collections only — do not embed |
update | Re-index existing collections (qmd update) |
embed | Generate or refresh vector embeddings (qmd embed --chunk-strategy auto) |
status | Show qmd status filtered to this repo's collections |
remove | Drop this repo's collections (asks for confirmation) |
| (none) | Default: add (or update if collections exist) → embed |
Before doing anything else, verify the environment. Each check has its own short-circuit message — do not chain them silently.
qmd installed: Run command -v qmd >/dev/null 2>&1. If missing, output and stop:
qmd CLI not found. Install it with `npm install -g @tobilu/qmd` (or `bun install -g @tobilu/qmd`), then re-run this skill. See https://github.com/tobi/qmd for details.
Inside a git repository: Run git rev-parse --show-toplevel. If it fails, output: "Not inside a git repository. /sdd:index derives the collection name from the repo root — cd into a checkout and try again." Then stop.
CLAUDE.md exists with SDD references: Read CLAUDE.md at the project root (or module root if --module is set). If it does not exist or lacks references to an ADR/spec directory, output:
CLAUDE.md does not have SDD plugin references. Run `/sdd:init` first so this skill knows where to find ADRs and specs.
Then stop. The skill needs CLAUDE.md to know the ADR/spec paths and to extract the per-collection context summary (Step 4).
Compute the repo slug:
git rev-parse --show-toplevel | xargs basename | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g'
Build the collection name set (per ADR-0025 / SPEC-0019, the issues collection is the fourth per-repo collection alongside adrs, specs, and code):
--module provided): {repo}-adrs, {repo}-specs, {repo}-code, {repo}-issues. Where {repo} is the slug from step 3.1, plus the module name when --module is provided (e.g., stumpcloud-infra-adrs).--module, multiple modules detected): one quadruple per module — {repo}-{module}-adrs, {repo}-{module}-specs, {repo}-{module}-code, {repo}-{module}-issues. The skill iterates Steps 4–7 once per module.Resolve absolute paths for each collection's source directory:
*-adrs → {module-root}/{adr-dir} (from Step 0)*-specs → {module-root}/{spec-dir} (from Step 0)*-code → {module-root} (the entire repo or module root)*-issues → {module-root}/.sdd/issues/ (the local sync cache populated from the configured tracker — see Step 4 add operation sub-step 6 and the update operation issues sync)Route to one of the operations below based on Step 1's subcommand. Each operation assumes Steps 2–3 already ran.
For each collection in the name set:
Skip if it already exists: Run qmd collection list and check whether the collection name appears. If it does, note it as already-exists and continue to the next collection — qmd collection add is a no-op on an existing name and will NOT refresh the mask or path. To change either, the user must qmd collection remove {name} first (or use /sdd:index remove then /sdd:index add).
Skip if the source directory is missing: For the ADR or spec collection, if its source directory does not exist, warn "Skipping {name}: {path} does not exist." and continue. Do not skip the code collection — the module root always exists.
Build the file mask. The qmd CLI takes only --name and --mask (no --ignore flag). qmd already auto-excludes node_modules/, .git/, .cache/, vendor/, dist/, and build/ at the indexer level (see store.js excludeDirs), so the mask only needs to set what to include:
ADR collection: --mask "ADR-*.md" (matches the MADR filename convention from ADR-0003)
Spec collection: --mask "**/*.md" (catches both spec.md and design.md)
Issues collection: --mask "*.md" (the synced issue files are flat under .sdd/issues/, one per issue, named {id}.md per ADR-0025 sub-decision 1)
Code collection: derive the mask from what is actually in the repo. Rank tracked extensions:
git ls-files | grep -oE '\.[a-zA-Z0-9]+$' | sort | uniq -c | sort -rn
Build a brace-expansion glob covering the dominant languages plus md. For a Go + Markdown repo: --mask "**/*.{go,md}". For a TypeScript + Python repo: --mask "**/*.{ts,tsx,py,md}". Always include md so README files and inline design notes get indexed alongside code.
Create the collection:
qmd collection add "{absolute-path}" --name "{collection-name}" --mask "{mask}"
qmd prints "{N} unique hashes need vectors" after each successful add. These per-add counts are NOT additive across collections because qmd dedupes chunks globally — the same file mentioned in multiple collections only counts once toward the index-wide pending total. Do not sum them in the final report. Instead, after all qmd collection add calls complete, run qmd status (or call the qmd MCP status tool) once to obtain the authoritative index-wide pending count, and use that single number in the report.
Attach a context summary. qmd's reranker uses these strings as a relevance signal — strong context here pays off on every future query against the collection. The single biggest quality variable is information density: a 70-word summary that names actual libraries, file types, decision themes, and content scope produces dramatically better reranker behavior than three vague sentences. Skeletal templates produce skeletal context.
Information checklist (the summary SHOULD cover all four — drop a bullet only if the repo genuinely lacks it):
*-adrs: dominant decision themes and the MADR format; for *-specs: what capabilities the specs govern, OpenSpec spec.md+design.md pairing; for *-code: what's in scope (and what qmd's built-in excludes already strip)Worked example — context strings actually attached to this very repo's collections, written to the checklist above:
*-adrs: "Architecture Decision Records for the claude-plugin-sdd project — a Claude Code plugin for spec-driven development with ADR governance, OpenSpec specs, sprint planning, parallel implementation, and code review. MADR format. Decision themes include workspace mode, drift detection, scrum mode, frontmatter DAG, and qmd integration."*-specs: "OpenSpec specifications for the claude-plugin-sdd project — paired spec.md and design.md files with RFC 2119 requirements governing the SDD plugin's skills (init, prime, check, audit, plan, work, review, index, graph) and the workflows that compose them."*-code: "Source for claude-plugin-sdd — a Claude Code plugin for spec-driven development. Languages: Markdown/MDX (skill bodies, ADRs, specs), TypeScript/TSX/JS (Docusaurus theme), JSON (plugin manifest, eval fixtures), some Python (helper scripts). qmd's built-in excludes already strip node_modules, .git, .cache, vendor, dist, build."Per-collection skeleton — start from these and fill in with the checklist; never ship the skeleton verbatim:
*-adrs: "Architecture Decision Records for the {repo} project — {project domain in plain language}. MADR format. Decision themes: {3-5 dominant themes from scanning ADR titles}."*-specs: "OpenSpec specifications for the {repo} project — paired spec.md and design.md files with RFC 2119 requirements governing {what the specs govern, derived from spec titles}."*-code: "Source for {repo} — {project domain}. Languages: {detected languages with versions}. Named libraries: {top 3-5 from imports/manifests}."Attach via:
qmd context add "qmd://{collection-name}/" "{summary}"
The trailing slash is the canonical form for collection-root context. qmd silently appends one if you omit it, but writing it explicitly keeps the command and stored state in sync.
If CLAUDE.md is too thin to derive a domain description (or the repo lacks one entirely), use AskUserQuestion to ask the user for a one-line project description, then synthesize the rest from git ls-files extension counts and from scanning ADR/spec titles. Don't ship a vague summary just because CLAUDE.md was thin — the reranker quality cost is real.
Issues collection initial sync (per ADR-0025 / SPEC-0019 REQ "Issues Collection Sync via /sdd:index"). The *-issues collection is unique among the four — its source directory .sdd/issues/ is initially empty (no synced issue files exist until the first sync). Before running qmd collection add for the issues collection, run an initial sync via the tracker-sync layer to populate .sdd/issues/:
references/shared-patterns.md. If no tracker is detected, skip the issues collection entirely with a one-line warning ("No tracker configured — skipping {repo}-issues collection. Run /sdd:init to configure a tracker, or use the tasks.md fallback per ADR-0007."). Continue with the other three collections.references/tracker-sync.md § "Per-Tracker Sync" → relevant tracker section. The sync writes one .sdd/issues/{id}.md file per open and recently-closed issue, using the canonical frontmatter schema documented in tracker-sync.md § "Canonical Frontmatter Schema"..sdd/issues/_meta.json per tracker-sync.md § "Cursor Management".qmd collection add for the *-issues collection per the steps above..sdd/issues/ is one issue with frontmatter (id, status, labels, assignees, created/updated, references to specs/ADRs and dependencies) and the verbatim issue body. Open and recently-closed issues both appear; closed issues retain status: closed and closed: {timestamp} fields."On sync failure (rate limit, auth, network), surface the error per tracker-sync.md § "Failure Modes and Degradation" and skip the issues collection — the other three collections still get created.
qmd collection list. If none exist, output: "No qmd collections found for {repo}. Run /sdd:index add first." and stop.qmd update re-scans every collection in the user's index. qmd does not currently expose per-collection filtering on update and there is no shell-level workaround — a per-repo update would have to come from upstream qmd. If qmd collection list shows collections that do not belong to this repo, surface their count so the user understands what is about to happen.qmd update, re-fetch tracker issues into .sdd/issues/ so the qmd file scan picks up new and changed issues. Use the per-tracker fetch+normalize per references/tracker-sync.md, with the cursor from .sdd/issues/_meta.json for incremental sync. Print a one-line note: "Syncing N issues from {tracker}…". On sync failure, surface the error and continue with the existing local cache (per tracker-sync.md § "Failure Modes and Degradation").qmd update.AskUserQuestion whether to re-embed now (the embed operation below). New documents are not searchable via vector/hybrid search until they are embedded.Per ADR-0026's embed policy, this operation runs silently with a hardware-aware default. No AskUserQuestion prompt — prompting CPU users every time produced the same answer 95% of the time and was friction the user did not want.
Detect GPU: Run qmd status and scan for a "running on CPU" warning. The presence of that warning means CPU-only.
Choose mode based on hardware and explicit flags in $ARGUMENTS:
| Hardware | Flag | Mode |
|---|---|---|
| GPU | (default) | Foreground (fast — no reason to background) |
| CPU | (default) | Background (do not block the session — embedding takes ~1s/chunk on CPU) |
| any | --foreground | Foreground (user wants chunk counts inline) |
| any | --skip | No-op — print "Embed skipped per --skip flag" and exit |
Pre-embed scope disclosure (concrete cross-repo accounting). qmd embed operates on the entire qmd index — it generates embeddings for any unembedded chunks across all collections, not just this repo's. Before invoking it, call qmd status (or the qmd MCP status tool) to get the authoritative scope:
needsEmbedding field{slug}-adrs, {slug}-specs, {slug}-code, or {slug}-issues (or {slug}-{module}-{kind} in workspace mode, where {kind} is one of adrs/specs/code/issues). Do NOT use substring match: a slug like myrepo would otherwise spuriously claim collections like not-myrepo-adrs belonging to a sibling repo. Match against the full collection name from collections[]. To estimate this-repo-pending, sum (documents - embedded) across the matched collections (per-collection embedded count comes from qmd ls {collection} or by tracking the diff before/after the most recent qmd update).-{kind} segment to recover the slug) so the user sees concretely whose chunks they are about to embed.Surface this in the report concretely:
"Pending across the index: {total} chunks. Of those, {this} are from {repo}; {other} are from {N} other indexed repos ({sample-repo-slug-1}, {sample-repo-slug-2}, …). Embedding will process all of them in this run."
This replaces the abstract "operates on the entire index" callout. Users with a single indexed repo see "0 from other repos" and the line is short; users with several repos see the cross-repo cost upfront and can choose to skip (--skip) and batch later.
Run the chosen mode:
# Foreground
qmd embed --chunk-strategy auto
# Background
qmd embed --chunk-strategy auto > /tmp/qmd-embed-{repo}.log 2>&1
The --chunk-strategy auto flag uses tree-sitter for Go/TS/JS/Python/Rust files (cleaner chunks at function/class boundaries) and falls back to regex for everything else, including markdown. This produces dramatically better code-search recall than regex chunking and is non-negotiable for code collections.
Background mode reporting: When backgrounded, the report uses the "After embed (background)" template below. The completion notification from the harness lands in the next session turn; the user can also run /sdd:index status to check progress against the log file.
qmd status and capture the output.qmd collection list and filter to rows whose name starts with the repo slug (or {repo}-{module}- in workspace mode).qmd status reports CPU-only mode, surface that warning verbatim under a ### Health section.--module narrows it).AskUserQuestion to confirm with the exact collection list and document counts. Removing a collection drops its index entries and embeddings — this is destructive and not undoable without re-indexing.qmd collection remove {name} for each collection in turn. Report which were removed and which did not exist.Detect the right path automatically:
qmd collection list shows none of this repo's collections → run add then embed.update then offer embed via AskUserQuestion.Tell the user which path was taken at the top of the report so the auto-routing is not surprising.
Use the template that matches the operation. In workspace aggregate mode, render one collection table per module under per-module subheadings (### [api] Collections, ### [worker] Collections).
add or default first run## QMD Index Created for {repo}
{If invoked with no subcommand, include this line:}
Auto-routed: collections did not exist → ran `add` then started `embed` ({foreground|background|skipped}).
### Collections
| Name | Source | Documents | Embedded |
|------|--------|-----------|----------|
| {repo}-adrs | {abs path to ADR dir} | {N} | {yes/no/in-progress} |
| {repo}-specs | {abs path to spec dir} | {N} | {yes/no/in-progress} |
| {repo}-code | {abs path to module root} | {N} | {yes/no/in-progress} |
| {repo}-issues | `.sdd/issues/` (synced from {tracker}) | {N} | {yes/no/in-progress} |
{Optional one-line note: total unique chunks queued for embedding, and a reminder that qmd auto-excludes node_modules/.git/.cache/vendor/dist/build.}
### Search Examples
- ADRs only: `qmd query "{topic from a recent ADR title}" -c {repo}-adrs`
- Code only: `qmd query "{a function or pattern likely in the codebase}" -c {repo}-code`
- Issues only: `qmd query "{topic from an open issue}" -c {repo}-issues`
- Across architecture: `qmd query "..." -c {repo}-adrs -c {repo}-specs`
- Via the qmd MCP server: call its `query` tool with `collections: ["{repo}-adrs"]`
### Next Steps
{If embed is in-progress: include "Background embed running — log: /tmp/qmd-embed-{repo}.log. Re-run `/sdd:index status` to check progress."}
{If skipped: include "Run `/sdd:index embed` — semantic and hybrid search are disabled until vectors exist."}
- After adding new ADRs/specs/code, re-run `/sdd:index update`
- The qmd MCP server (`mcp__plugin_qmd_qmd__*` tools, if you have the qmd plugin loaded) sees new collections immediately — no Claude Code restart required. Verified empirically against running sessions.
- Consider recording this with `/sdd:adr "Adopt qmd for cross-repo semantic search"` — there is no ADR for this yet
update## QMD Index Updated for {repo}
### Indexed
| Collection | Added | Modified | Removed |
|------------|-------|----------|---------|
| {repo}-adrs | {a} | {m} | {r} |
| {repo}-specs | {a} | {m} | {r} |
| {repo}-code | {a} | {m} | {r} |
| {repo}-issues | {a} | {m} | {r} |
{N} documents need re-embedding to become searchable via vector/hybrid query.
### Next Steps
- Run `/sdd:index embed` to refresh vectors for the {N} new/modified documents
embed (foreground)## QMD Embeddings {Generated|Refreshed}
Embedded {N} chunks across {M} collections in {duration}.
### Embedded Collections
| Collection | Documents | Chunks |
|------------|-----------|--------|
| {repo}-adrs | {N} | {C} |
| {repo}-specs | {N} | {C} |
| {repo}-code | {N} | {C} |
| {repo}-issues | {N} | {C} |
Hybrid search (`qmd query`) and vector search (`qmd vsearch`) are now available.
embed (background — embed kicked off, not yet complete)## QMD Embeddings In Progress for {repo}
Embedding ~{N} chunks in the background. Log: `/tmp/qmd-embed-{repo}.log`. Re-run `/sdd:index status` to check progress, or wait for the background completion notification.
While it runs, BM25 keyword search via `qmd search -c {repo}-{...}` is already available; vector and hybrid search will turn on as embeddings land.
status## QMD Status for {repo}
### Collections
| Name | Documents | Vectors | Last Modified | Source |
|------|-----------|---------|---------------|--------|
| {repo}-adrs | {N} | {V} | {timestamp} | {abs path} |
| {repo}-specs | {N} | {V} | {timestamp} | {abs path} |
| {repo}-code | {N} | {V} | {timestamp} | {abs path} |
| {repo}-issues | {N} | {V} | {timestamp} | `.sdd/issues/` (last sync: {iso-timestamp}) |
### Health
- Index: {path to sqlite} ({size})
- GPU: {available|none — running on CPU}
- {Any qmd status warnings, verbatim}
remove## QMD Collections Removed for {repo}
| Name | Status |
|------|--------|
| {repo}-adrs | removed ({N} documents dropped) |
| {repo}-specs | removed ({N} documents dropped) |
| {repo}-issues | removed ({N} documents dropped); `.sdd/issues/` cache preserved (gitignored) |
| {repo}-code | removed ({N} documents dropped) |
To rebuild the index later, run `/sdd:index`.
In aggregate mode, render one section per module before the report's shared sections (Health, Next Steps):
## QMD Index {Created|Updated} for {repo} ({K} modules)
### [api] Collections
| Name | Source | Documents | Embedded |
|------|--------|-----------|----------|
| {repo}-api-adrs | ... | ... | ... |
| {repo}-api-specs | ... | ... | ... |
| {repo}-api-code | ... | ... | ... |
### [worker] Collections
| Name | Source | Documents | Embedded |
|------|--------|-----------|----------|
| {repo}-worker-adrs | ... | ... | ... |
| ...
### Search Examples
- Single module: `qmd query "..." -c {repo}-api-adrs`
- All modules' ADRs: `qmd query "..." -c {repo}-api-adrs -c {repo}-worker-adrs`
### Next Steps
- Scope a future run to one module: `/sdd:index update --module api`
qmd command — partial failures are confusing and hard to undogit rev-parse --show-toplevel rather than the current working directory; users may invoke from a subdirectoryreferences/shared-patterns.md instead of hardcoding docs/adrs/ or docs/openspec/specs/ — repos can override these paths in CLAUDE.md (Governing: ADR-0015)-adrs, -specs, -code, and -issues — collection-level filtering (-c <name>) is the primary mechanism users will use to keep different content types separate. The issues collection MAY be skipped (with a warning) when no tracker is configured per ADR-0007 (Governing: ADR-0025, SPEC-0019 REQ "Issues Collection Layout")references/tracker-sync.md BEFORE running qmd collection add for the issues collection in the add operation — the source directory .sdd/issues/ is initially empty, so the collection has nothing to scan until the first sync writes the markdown files (Governing: SPEC-0019 REQ "Issues Collection Sync via /sdd:index")references/tracker-sync.md BEFORE qmd update in the update operation — fresh issue state must land in .sdd/issues/ before the file scan picks up the changesqmd context add summary to every collection — qmd's reranker uses context strings to disambiguate results, and uncontextualized collections produce noisier hybrid scores--chunk-strategy auto when embedding so code files chunk on AST boundaries (function, class, import) instead of arbitrary character offsets — produces dramatically better code search recallqmd collection list before re-creating; re-running add on an existing collection is a no-op in qmd but will not refresh path or mask, so a stale config can persist silentlyqmd update and qmd embed operate on the entire qmd index (no per-collection filter exists). For embed specifically, MUST do this concretely with the pre-embed scope disclosure in the embed Process step — abstract callouts are easy to gloss over; named other-repo counts force the user to register the costqmd collection add prints — those numbers are pre-dedup and overstating the embed scope confuses users. Use the index-wide count from qmd status (or the qmd MCP status tool) as the authoritative number in the reportAskUserQuestion before qmd embed on a CPU-only machine, with background as the recommended default — embedding the EmbeddingGemma 300M model on CPU runs at roughly 1s per chunk and a foreground embed silently blocks the session for many minutesqmd context add paths with an explicit trailing slash (qmd://{collection-name}/) — qmd auto-appends one when omitted, so writing it explicitly keeps the issued command and stored state in sync and avoids confusion when a user later inspects context with qmd context listnode_modules/, .git/, .cache/, vendor/, dist/, build/) rather than trying to construct exclude patterns — qmd's CLI does not accept an --ignore flag, and the built-in list already covers the common dependency/output directoriesAskUserQuestion before remove — dropping a collection deletes embeddings that take real time and (on CPU) significant compute to regenerate.claude-plugin/plugin.json — skills auto-discover from skills/; touching the manifest causes plugin reload churndocs/adrs/ or docs/openspec/specs/ directories if they are absent — that is the job of /sdd:adr and /sdd:spec. Skip the corresponding collection with a warning instead./sdd:adr in the report — no ADR exists yet for adopting qmd, and the user explicitly noted this is an architectural decision worth documenting after the skill is in use{repo}-{module}-{kind} so the same --module filter works in both /sdd:index and /sdd:check--module is provided, MUST scope to that single module — do not touch sibling modules' collections