--- name: discover-specs description: "Partial Skill: invoke by name only — corpus/discovery's frontmatter-scanning engine across the SDD spec locations — used by the sdd gateway to scan statuses and by start-mission to locate the project spec, not triggered by users directly." user-invocable: false metadata: internal: true --- # Discover Specs The concrete engine for SDD **spec discovery**. It locates the project specs in a repo and returns each one's frontmatter — **without reading any spec body** — so a consumer (the **gateway**, corpus tooling) can route on `status` / `project-path` / `approval` cheaply. It carries a self-contained `.mjs` script (compiled from `src/`; no runtime deps, Node ≥22.11). ## Recognition — location-bounded and shape-confirmed A `spec.md` is a spec only when **both** hold (ADR-0017, narrowed; extra anchors per ADR-0019 — `sdd:lifecycle-governance`): - **Location** — it sits at one of the three fixed SDD spec locations, **or** at an extra anchor the project declared in `.agents/sdd/spec-anchors.toml`: 1. `.agents/spec/spec.md` — repo-root single-project 2. `.agents/specs//spec.md` — repo-root multi-project 3. `/.agents/spec/spec.md` — a nested project (the `**` is the project-path, any depth) 4. **extra anchors** — each config entry, a repo-relative pattern (`*` globs one segment, `**` globs zero or more segments at any depth, `` globs and captures a name); **opt-in and additive** (absent config ⇒ only 1–3, so today's behavior is unchanged). Curated via the `manage-spec-anchors` skill. - **Shape** — its frontmatter `status` is in the lifecycle enum (`draft | approved | implemented | deprecated`). A `spec.md` at any recognized location with **no** lifecycle `status` is skipped (so the scan never grabs a stray file by accident); a status-bearing `spec.md` at neither a fixed convention nor a declared extra anchor is not discovered. An **unreadable or malformed** `spec-anchors.toml` is ignored (warn + fall back to the fixed conventions), so the scan never crashes on a corrupt config. ## Run the scan ```bash node "/scripts/discover-specs.mjs" [--root .] [--format toon|json] [--resolve ] ``` - Default `--root` is the current directory; default `--format` is **TOON** (the token-efficient tabular form the gateway scans). - Emits one row per spec, sorted by folder slug, with columns `path,name,nameSource,status,projectPath,approvals` — `path` is the spec's root-relative folder slug; `name` is the project name and `nameSource` is `declared | derived | guessed` (below); `approvals` is the gate verdicts as `:` pairs joined by `;`. - `--resolve ` filters to the **exact (case-insensitive) name matches** — 0 rows = none, 1 = resolved, >1 = ambiguous (the consumer disambiguates with the user). - `--format json` emits the same records as a flat JSON array for non-LLM consumers. Example (TOON): ``` specs[2]{path,name,nameSource,status,projectPath,approvals}: .agents/specs/aced,aced,derived,implemented,plugins/aced,spec:approve;impl:approve .agents/specs/sdd,sdd,derived,approved,plugins/sdd,spec:approve ``` **`name-source`** flags how trustworthy the name is: **`declared`** (frontmatter `name`, authoritative), **`derived`** (repo-root single-project → `repo`; a `.agents/specs/` folder names itself), **`guessed`** (a nested project's folder basename — confirm with the user before relying on it). When `node` is absent, an agent performs the same derivation by hand: scan the three spec locations, keep each `spec.md` whose frontmatter `status` is in the enum, read its frontmatter only, and derive the name per the rules above. ## Boundaries Frontmatter only — the script reads the whole file but its **output** carries no body content, so a consuming agent never spends tokens on a body (`digest` reads bodies; this never does). It owns no lifecycle state and writes nothing. The script resolves a name **deterministically** (exact match → spec, or the candidate set); **disambiguating with the user** is the consumer's agentic step, not the script's.