From sdd
Indexes a repository's ADRs, OpenSpec specs, source code, and tracker issues into qmd collections for hybrid semantic search. Use when asked to make repo content searchable or set up qmd.
How this skill is triggered — by the user, by Claude, or both
Slash command
/sdd:index [add|update|embed|status|remove] [--module <name>] [--foreground|--skip] [--reprobe][add|update|embed|status|remove] [--module <name>] [--foreground|--skip] [--reprobe]This skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
<!-- 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), ADR-0031 (Embed-Session Retry Loop), ADR-0032 (qmd Version-Staleness Check), SPEC-0019 REQ "Issues Collection Layout", SPEC-0019 REQ "Issues Collection Sync via /sdd:index" -->
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. Checks 4 and 5 are non-blocking diagnostics: they emit warnings rendered at the top of the final report (see Report Banner in Step 5) but do not stop the operation. Checks 1–3 are hard preconditions and stop the skill on failure.
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).
qmd version staleness check (non-blocking; Governing: ADR-0032). Read the cache at ~/.cache/sdd-plugin/qmd-version.json. Schema:
{ "latest": "2.1.3", "checked_at": "2026-05-04T10:30:00Z" }
Cache miss or stale (>7 days): refresh via npm view @tobilu/qmd version 2>/dev/null. Write the result back to the cache file (creating ~/.cache/sdd-plugin/ if absent). On any failure (offline, registry timeout, npm not in PATH), silently skip the warning — DO NOT block, DO NOT error. Network failures are common and should never gate indexing.
Cache hit (≤7 days old): use the cached latest value as-is — no npm view call.
Read installed version: qmd --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+'.
If installed < latest (semver compare on major.minor.patch — pure numeric, no prerelease handling needed for qmd's release pattern), set the report banner:
⚠ qmd {installed} installed; {latest} available — `npm install -g @tobilu/qmd` (may include embed-session-timeout fixes)
The banner renders at the top of every report template (see Step 5). If versions match, no banner.
qmd hardware mode (non-blocking; Governing: ADR-0026 embed policy, ADR-0031 retry loop). Determines GPU vs. CPU for embed scheduling. The previous detection technique — scanning qmd status for the "running on CPU" warning — is structurally wrong because the warning emits only when the embedding model loads (during qmd embed), never in qmd status. Replaced with a cached probe of qmd's own emitted output.
Read the cache at ~/.cache/sdd-plugin/qmd-hardware.json. Schema:
{ "qmd_version": "2.1.0", "qmd_path": "/usr/bin/qmd", "hardware": "cpu", "detected_at": "2026-05-04T10:30:00Z" }
qmd --version AND qmd_path matches command -v qmd: use the cached hardware value (gpu or cpu). Skip the probe.--reprobe flag in $ARGUMENTS: defer detection to the first embed run — see Operation: embed Step 1 below. Until then, assume CPU (conservative default; worst case is a backgrounded fast embed for a GPU machine on first run).(qmd_version, qmd_path) so an upgrade or relocation of qmd invalidates it automatically. The cache file is treated as untrusted input — if it fails to parse as JSON or is missing required fields, treat as a cache miss.Surface the resolved hardware as a one-line note in the report header (Hardware: GPU (cached) or Hardware: CPU (cached) or Hardware: assumed CPU (probing on next embed)).
qmd install writability (non-blocking; surfaces as report warning). When qmd was installed via sudo npm install -g, its node-llama-cpp GPU build artifacts cannot be written under /usr/lib/node_modules/... by a non-root user, which silently forces CPU mode even on GPU machines. Detect:
qmd_root=$(npm root -g 2>/dev/null)/@tobilu/qmd/node_modules/node-llama-cpp/llama
if [ -d "$qmd_root" ] && [ ! -w "$qmd_root" ]; then
# surface warning
fi
If unwritable, surface the diagnosis in the report (DO NOT auto-fix — destructive ownership changes need user consent):
⚠ qmd installed at {npm-root-g}/@tobilu/qmd is not writable by $USER.
node-llama-cpp cannot build GPU artifacts under it; embeds will fall back to CPU (~3.4s/chunk).
Fix options:
1. Reinstall under your home: npm config set prefix ~/.npm-global && npm install -g @tobilu/qmd
2. Take ownership: sudo chown -R $USER {npm-root-g}/@tobilu/qmd/node_modules/node-llama-cpp
If npm root -g fails or the path does not exist, skip silently — qmd may be installed via a different package manager (bun, manual build) that doesn't have this problem.
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. Per ADR-0031, embed runs are wrapped in a bounded retry loop because qmd's embed sessions abort at ~30 minutes on CPU.
Resolve hardware mode from the cache populated in Step 2 sub-step 5. Three states:
gpu (cached) — proceed in foreground by defaultcpu (cached) — proceed in background by defaultassumed cpu (probing) — first invocation with no cache. Proceed in background (conservative default), AND tee stderr to the embed log so the post-run probe (Step 4b below) can detect the actual hardware mode and write the cache.Override precedence: explicit --foreground / --skip flags in $ARGUMENTS always win over the cached default.
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 inside a bounded retry loop (Governing: ADR-0031). qmd's embed sessions abort at ~30 minutes with Session expired — skipping N remaining chunks and exit 0 — a partial completion that the previous version of this skill mistook for full success. The retry loop closes that gap.
Single round is one invocation of:
# 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.
Loop control (foreground mode — runs inline in the skill body):
round=1
max_rounds=5
while round <= max_rounds:
run single round, capture stderr to /tmp/qmd-embed-{repo}.log
re-read `qmd status` → record `needsEmbedding`
if needsEmbedding == 0: break (full success)
if NOT (last log contains "Session expired" OR "skipping remaining"): break (genuine error, not a timeout)
round += 1
Cap at 5 rounds. Five 30-minute sessions covers ~2.5 hours of CPU embed wall time, which is enough for any reasonable repo (the user's stumpcloud poly-repo at 1181 chunks finishes in 2-3 rounds). If the cap is hit with needsEmbedding > 0 still pending, surface in the report:
⚠ Embed retry cap hit ({max_rounds} rounds, ~{total_minutes}min wall time). {N} chunks still pending.
This usually means qmd is hitting the same chunks repeatedly without progress — possible chunk-level error.
Inspect /tmp/qmd-embed-{repo}.log for "Failed to embed" lines, then re-run `/sdd:index embed` to continue.
Loop control (background mode — runs inside the backgrounded shell wrapper, NOT the foreground skill body, so the user gets the prompt back immediately):
(
for round in 1 2 3 4 5; do
qmd embed --chunk-strategy auto >> /tmp/qmd-embed-{repo}.log 2>&1
pending=$(qmd status | awk '/Pending:/ {print $2}')
[ "$pending" = "0" ] && break
grep -qE 'Session expired|skipping remaining' /tmp/qmd-embed-{repo}.log || break
done
) &
The harness completion notification fires when the outer subshell exits.
4a. Surface multi-round nature in the report. After the loop completes (foreground) OR is launched (background), include in the report:
Embedded {total_chunks} chunks across {rounds_completed} round(s).
{if rounds_completed > 1: "(qmd embed sessions expire at ~30min on CPU; auto-relaunched per ADR-0031.)"}
This makes the multi-round behavior legible — users running long CPU embeds need to understand why their 1-hour wall time produced multiple log entries.
4b. Hardware probe from stderr (only when Step 1 hardware was assumed cpu (probing)). After at least one round has produced output to /tmp/qmd-embed-{repo}.log, parse that log for the canonical CPU sentinel:
if grep -qiE 'running on cpu|gpu .* not available|cuda .* not found' /tmp/qmd-embed-{repo}.log; then
hardware="cpu"
else
hardware="gpu"
fi
Write the result to ~/.cache/sdd-plugin/qmd-hardware.json per Step 2 sub-step 5's schema, including the current qmd --version and command -v qmd for cache invalidation. On the next invocation, Step 2 sub-step 5 reads this directly without probing.
In background mode, the probe runs inside the same backgrounded wrapper after the retry loop completes, so the cache is populated by the time the next skill invocation starts.
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.Run qmd status and capture the output. Extract needsEmbedding (the index-wide pending count).
Run qmd collection list and filter to rows whose name starts with the repo slug (or {repo}-{module}- in workspace mode).
Compute failed-vs-pending breakdown. qmd does not currently expose a "failed chunks" field in qmd status (and ADR-0024 forbids reading the sqlite index directly). Approximate by parsing /tmp/qmd-embed-{repo}.log for the most recent run's session-expired tail:
if [ -f /tmp/qmd-embed-{repo}.log ]; then
# Count "skipping remaining" mentions in the tail of the most recent embed run.
# Each "Session expired" line is followed by a count: "skipping N remaining chunks".
failed=$(grep -oE 'skipping ([0-9]+) remaining chunks' /tmp/qmd-embed-{repo}.log | tail -1 | grep -oE '[0-9]+')
fi
embedded = Documents.Vectors from qmd statuspending_total = Documents.Pending from qmd statusfailed_prior_run = the count parsed above (0 if no log exists or no "skipping" lines found)pending_never_tried = pending_total - failed_prior_run (clamped to ≥0; if the math goes negative, the log is from a stale run — fall back to 0 failed)Render under a new ### Index Health section in the report. When failed_prior_run > 0, include a remediation hint:
{failed} chunks failed on the prior embed run (session expired).
Re-run `/sdd:index embed` — the retry loop (ADR-0031) will pick them up.
Note: this is a best-effort estimate. The log file is global per repo (not per round), so if multiple embed runs have happened since the last log rotation, the number may be conservative. Surface as "approximate" in the report when the log is older than 24h.
Render the filtered view in the templated output format below. If the most recent embed log contains a "running on CPU" warning, surface that under ### Index Health alongside the Hardware row from Step 2 sub-step 5.
--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).
Report Banner: every report template below begins with a banner section that surfaces non-blocking preflight warnings from Step 2 sub-steps 4–6. The banner is omitted entirely when there are no warnings. Banner format:
{if version-staleness warning from sub-step 4: render that line}
{if install-writability warning from sub-step 6: render that block}
{if Hardware row was "assumed cpu (probing)" and probe just completed: "Detected hardware: {gpu|cpu} (cached for future runs)"}
Render the banner BEFORE the report's ## QMD ... heading so it is the first thing the user sees.
add or default first run{Report Banner — see Step 5}
## 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){Report Banner — see Step 5}
## QMD Embeddings {Generated|Refreshed}
Embedded {N} chunks across {M} collections in {duration} ({rounds} round{s}).
{if rounds > 1: "(qmd embed sessions expire at ~30min on CPU; auto-relaunched per ADR-0031.)"}
### Embedded Collections
| Collection | Documents | Chunks |
|------------|-----------|--------|
| {repo}-adrs | {N} | {C} |
| {repo}-specs | {N} | {C} |
| {repo}-code | {N} | {C} |
| {repo}-issues | {N} | {C} |
{if retry cap was hit with pending > 0: render the retry-cap warning block from Operation: embed Step 4}
Hybrid search (`qmd query`) and vector search (`qmd vsearch`) are now available.
embed (background — embed kicked off, not yet complete){Report Banner — see Step 5}
## QMD Embeddings In Progress for {repo}
Embedding ~{N} chunks in the background (up to 5 rounds, ~30min each on CPU; will auto-stop at 0 pending or 5 rounds).
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{Report Banner — see Step 5}
## 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}) |
### Index Health
- Index: {path to sqlite} ({size})
- Hardware: {GPU | CPU} ({cached|probing})
- Embedded: {V} chunks
- Pending (never tried): {pending_never_tried}
- Failed (prior run): {failed_prior_run} {if approximate: "(approximate — log >24h old)"}
- {if failed > 0: render the remediation hint from Operation: status Step 3}
- {if log contains "running on CPU" and Hardware = CPU: "Confirmed: qmd is running on CPU (per /tmp/qmd-embed-{repo}.log)"}
- {Any other 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~/.cache/sdd-plugin/qmd-hardware.json (Step 2 sub-step 5) — MUST NOT scan qmd status output for the "running on CPU" warning (the warning emits during qmd embed model-load only, never in qmd status; this was a real-world bug). MUST NOT read filesystem markers under ~/.cache/qmd/ to infer hardware — couples to qmd internals (Governing: ADR-0026 embed policy)(qmd --version, command -v qmd) so reinstalling or upgrading qmd invalidates the cache automatically. MUST treat the cache file as untrusted input — JSON parse errors or schema mismatches re-trigger detectionqmd embed in a bounded retry loop (max 5 rounds) that re-reads qmd status after each round and relaunches when Pending > 0 AND the prior round's stderr contained Session expired or skipping remaining (Governing: ADR-0031). MUST surface the round count in the report when rounds > 1 so users understand the multi-round wall timePending > 0 after 5 rounds — the user needs to know it stopped progressing, not that it succeeded silently (Governing: ADR-0031)skipping ([0-9]+) remaining chunks. The failed count is approximate (log granularity is per-repo, not per-round) and MUST be marked as such when the log is older than 24h< cached latest. Cache lives at ~/.cache/sdd-plugin/qmd-version.json with a 7-day refresh interval. MUST silently skip the banner when npm view fails (offline, registry timeout) — version checks NEVER block indexing (Governing: ADR-0032)npm view on every invocation — the 7-day cache is the rate limit. The cache file is keyed by package name only (@tobilu/qmd), so multiple SDD-using projects share the cache without interference$(npm root -g)/@tobilu/qmd/node_modules/node-llama-cpp/llama is not writable by $USER. MUST NOT auto-fix — chown and npm config set prefix are user decisions with downstream effects~/.cache/qmd/index.sqlite) directly — even when qmd doesn't expose the data we want (e.g., per-chunk failure markers), the boundary is "qmd CLI/MCP only" per ADR-0024. Use log-file parsing or wait for qmd to expose the field upstreamnpx claudepluginhub joestump/claude-plugin-sdd --plugin sddConfigures qmd collections for semantic search on project markdown directories. Checks qmd install and MCP, derives project name, scans/selects dirs like docs/, sets collection details and names.
Indexes the current project for optimized search with QMD semantic search and fast file suggestions. Run when entering a new codebase or after significant changes. Saves 60-80% tokens on exploration tasks.
Detects oversized codebases or docs via thresholds like >50 MD files or >200 sources, suggesting qmd for local BM25+vector+LLM semantic search to extend AI context.