Score
Manual surface for the reviewer-gates dispatch pipeline. Implements the Score Command Feature (Phase 1 of the manual-review-and-score-commands Idea).
Producer category: NOT a Producer. This is a signal skill — it produces no canonical artifact under spec/. Read-only on spec/; writes nothing; emits no events. The Feature's REQ: no-file-writes and REQ: no-new-config are load-bearing — do not violate them under any circumstances.
When to Use
- Mid-iteration self-review before re-running a producer skill (which would emit events and update status).
- Audit of an artifact the user did not author.
- Triage across the spec tree to find what needs attention next.
- Pre-PR sanity check on a draft the user paused on.
Do NOT invoke this skill in response to any producer-skill event (feature.specified, idea.approved, plan.approved, etc.). Manual invocation is the only entry point. (REQ: invocation-triggers.)
Anti-Patterns
- Carrying reviewer logic inside this skill. Reviewers come exclusively from
gates.<stage>.reviewersinspecscore.yaml. No hardcoded baseline. No silent injection. No fallback. (REQ: reuse-reviewer-gates-pipeline.) - Writing any file under
spec/,.specscore/, or any other repo path. This skill is read-only. (REQ: no-file-writes.) - Emitting a SpecStudio event, or writing a file, on the default run. The default
/scorerun is ephemeral. Persistence happens only under the explicit opt-in flags--save(report) and--badge(badge injection), and manual runs propose the write and ask before applying it. (REQ: no-file-writes.) - Adding a new key to
specscore.yamlor any new dotfile or environment variable. All reviewer behavior is governed by the existinggates:block defined byreviewer-gates. (REQ: no-new-config.) - Suspending the user's session waiting for a
type: humanreviewer. Skip those silently with a one-line note per artifact. (REQ: human-reviewers-skipped.) - Refusing the invocation when
gates:is absent. This skill is a signal skill, not a gate. Report every artifact as skipped, exit zero with the no-verdict warning. (REQ: missing-gates-block-handling.)
Invocation Shape
specstudio:score [PATHS...] [-r|--recursive] [--against REF] [--verbose] [--yes]
| Argument / Flag | Semantics |
|---|---|
PATHS... (positional, optional) |
Files, directories, or glob patterns relative to repo root (or absolute). Empty list → defaults to spec/. |
-r, --recursive |
Descend into sub-artifacts beneath directory entries. Without this flag, sub-artifacts MUST NOT be included. |
--against REF |
Diff baseline supplied to type: ai reviewers. Default: HEAD. |
--verbose |
In multi-artifact mode, print per-artifact detail in addition to the summary table. No-op in single-artifact mode. |
--yes |
Skip the threshold confirmation prompt regardless of resolved-artifact count. Does NOT skip any other confirmation. |
Any unknown flag (anything starting with - that is not in the table above) MUST cause the skill to refuse to run with a usage error and exit non-zero. Do NOT silently ignore unknown flags. (REQ: invocation-shape.)
Pipeline Overview
- Parse arguments — fail fast on unknown flags.
- Resolve paths — empty →
spec/; directories →README.md; globs → shell expansion;-r→ recursive descent; deduplicate; preserve first-appearance order. - Map each artifact to a stage —
spec/ideas/→ideate;spec/features/<slug>/README.md→specify;spec/plans/→plan; anything else → skipped. - Confirm-at-threshold — if
-rAND resolved count > 10 AND no--yes, prompt for explicit approval before dispatch. - Per-artifact, in resolved order, serially:
- Load
gates.<stage>.reviewersvia the shared loader. - Filter out
type: humanentries; record a one-line skip note per artifact. - Compute the unified diff
git diff <REF> -- <artifact-path>. - Dispatch surviving
type: aientries via the shared runner, passing the artifact body + diff to each. - Collect the verdict (
Approved|Issues Found).
- Load
- Emit output — per-artifact detail (single-artifact) or summary table + footer (multi-artifact, with
--verboseadding per-artifact detail). - Exit — zero if every reviewed artifact returned
Approved, non-zero if any returnedIssues Found, zero with no-verdict warning if every artifact was skipped.
The skill writes nothing in spec/; emits no events. The single user-facing side effect is the terminal output and the exit code (plus the report/badge only under --save / --badge).
Grade dependency. The verdict this skill surfaces is whatever the shared reviewer-gates runner returns. The steps below describe the
Approved | Issues Foundshape thatreviewer-gatesreturns today. Oncereviewer-gatesships the grade as its verdict currency — the findings → A–F aggregation plus the configurable Approve threshold (defaultB) — this skill surfaces that grade and derivesApprovedasgrade ≥ threshold, with no change to path resolution, dispatch, or the no-writes guarantee below.
Step 1 — Parse Arguments
Tokenize the invocation. Distinguish positional PATHS from flags.
- Anything starting with
-is a flag. The recognized flags are exactly-r,--recursive,--against,--verbose,--yes. Unknown flags MUST refuse the invocation. (REQ: invocation-shape.) --againstMUST take a value (--against REFor--against=REF). A bare--againstwithout a value MUST refuse.-rand--recursiveare equivalent.--verbose,--yesare boolean flags.- Everything else is a positional
PATHSentry, in user-supplied order.
If parsing fails, print a one-line usage error and exit non-zero. Do not proceed to path resolution.
Step 2 — Resolve Paths
The output of this step is resolved_paths — an ordered, deduplicated list of artifact paths, each tagged with whether it was skipped (and why) before any reviewer dispatch.
2a — Default empty PATHS
If PATHS is empty, treat it as the single entry spec/. (REQ: empty-paths-default.)
2b — Expand each PATHS entry, in user-supplied order
For each entry p in PATHS:
If
pcontains a glob character (*,?,**): expand using shell-glob semantics against the repo working tree. If the expansion produces zero matches, refuse the entire invocation withError: glob pattern '<p>' matched no files in the working tree; exit non-zero. (REQ: glob-expansion.) Add each expansion result (in lexicographic order) to the working list, marked as a "file" entry — globs do NOT recurse on their own.Else if
presolves to a directory:- If
<p>/README.mdexists, add<p>/README.mdto the working list, marked as a "directory-readme" entry. - If
<p>/README.mddoes NOT exist, record the directory as skipped with reason(no README.md in directory)and continue with remaining entries. (REQ: directory-resolves-to-readme.) - If
-ris supplied, ALSO add (after the README) every reviewable sub-artifact beneath<p>per the patterns inartifact-to-stage-mapping(Step 3), in lexicographic order. (REQ: recursive-descent.) Reviewable patterns are:<p>/ideas/*.md(excluding<p>/ideas/README.md)<p>/ideas/seeds/*.md<p>/features/*/README.md<p>/plans/*.md(excluding<p>/plans/README.md)
- If
Else if
presolves to a regular file: addpto the working list, marked as a "file" entry.-ris a no-op for file entries (a file has nothing beneath to recurse into) — accept silently per the Feature's Not Doing list.Else (
pdoes not resolve): refuse the entire invocation withError: path '<p>' does not exist; exit non-zero. (Globs are handled in step 1; reaching this branch means a literal path that does not resolve.)
2c — Deduplicate, preserving first-appearance order
Walk the working list. Build resolved_paths by appending each entry the first time it appears (key = absolute path of the artifact). Subsequent appearances of the same absolute path MUST be dropped. The order in resolved_paths is the order of FIRST appearance in the working list. (REQ: deduplication-and-order.)
Step 3 — Map Each Artifact to a Stage
For each entry in resolved_paths, compute its stage using the canonical mapping (REQ: artifact-to-stage-mapping):
| Path pattern (relative to repo root) | Stage |
|---|---|
spec/ideas/<slug>.md (not spec/ideas/README.md) |
ideate |
spec/ideas/seeds/<slug>.md (not spec/ideas/seeds/README.md) |
ideate |
spec/features/<slug>/README.md |
specify |
spec/plans/<slug>.md (not spec/plans/README.md) |
plan |
Any other path — including index README.md files at spec/README.md, spec/features/README.md, spec/ideas/README.md, spec/plans/README.md, and anything under spec/research/ — MUST be tagged skipped with reason (no gate configured for this artifact type). Do NOT dispatch any reviewer for skipped artifacts. Continue with remaining entries.
Step 4 — Confirm-at-Threshold
After Step 3, count the artifacts that are NOT yet tagged skipped — call this reviewable_count.
If ALL of the following are true:
-rwas supplied (REQ: confirm-at-threshold).reviewable_count > 10.--yeswas NOT supplied (REQ: yes-flag-skips-threshold).
Then surface a confirmation prompt naming:
- The reviewable count (e.g., "23 artifacts to review").
- The list of distinct stages involved (e.g., "stages: ideate, specify").
- The approximate AI-reviewer dispatch count — sum of
gates.<stage>.reviewerstype: aientry counts across involved stages × number of artifacts in each stage. (For loader errors, fall back to "unknown".)
Wait for the user's response. Use the same approval-phrase recognizer as specstudio:ideate and specstudio:specify:
- Explicit approval phrase (
approve/approved/accept/accepted/lgtmplus direct semantic equivalents in the user's language) → proceed. - Explicit decline or change request → abort the invocation with
Error: user declined the threshold confirmation; exit non-zero. - Vague positive signal → ask one explicit confirmation question and wait. Do NOT proceed on a vague signal alone.
If reviewable_count <= 10, OR -r was not supplied, OR --yes was supplied: do NOT prompt; proceed directly to Step 5.
Step 5 — Per-Artifact Gate Dispatch (serial, in resolved order)
For each entry in resolved_paths in order:
5a — Skip-tagged artifacts
If the entry is tagged skipped (from Step 2 or Step 3), record its skip reason in the per-artifact result and continue to the next entry. Do NOT dispatch any reviewer.
5b — Load the gate config
Resolve the entry's stage <S> from Step 3. Invoke the shared loader at ../shared/reviewer-gates/loader.md with <skill> = <S> (e.g., specify for a Feature artifact). Follow that protocol verbatim.
Loader-outcome handling — diverges from specstudio:specify per REQ: stage-without-gate and REQ: missing-gates-block-handling:
- Loader refuses with "missing or empty
gates.<S>.reviewers" (any of the three states: nogates:, nogates.<S>, emptyreviewers: []): tag this artifact as skipped with reason:(no gates: block in specscore.yaml)if there is no top-levelgates:at all (REQ: missing-gates-block-handling).(no gates.<S> configured)ifgates:exists butgates.<S>does not (REQ: stage-without-gate).(empty gates.<S>.reviewers: [])if the list is present but empty.- Continue to the next artifact. Do NOT refuse the whole invocation. The skill is a signal skill, not a gate.
- Loader refuses for any other reason (entry-shape violation, duplicate name, unknown type, malformed prompt path, missing taxonomy section, etc.): the configuration itself is broken. Surface the loader's error verbatim, tag this artifact as skipped with reason
(gate config error — see loader output), and continue. Do NOT abort the whole invocation — the user may be reviewing artifacts at multiple stages and only one is misconfigured.
5c — Filter type: human entries
From the validated reviewer list returned by the loader, partition into ai_entries and human_entries. (REQ: human-reviewers-skipped.)
- If
human_entriesis non-empty, record a per-artifact note:(skipped <N> type: human entry — manual invocation cannot suspend for human approval). The plural isentriesfor N > 1. - If
ai_entriesis empty (every entry wastype: human), tag this artifact as skipped with reason(every reviewer for stage <S> is type: human; manual invocation has no verdict to compute). Continue to the next artifact. - Otherwise, proceed to Step 5d with
ai_entriesas the dispatch list.
5d — Compute the diff
Run git diff <REF> -- <artifact-path> where <REF> is the value of --against (default HEAD, per REQ: against-default). Capture stdout as diff_text.
- If git exits non-zero because the ref does not resolve (e.g.,
--against does-not-exist): record a per-artifact note(ref '<REF>' could not be resolved — diff is empty). Setdiff_textto empty string. Continue to dispatch (the AI reviewer still receives the artifact body). (REQ: diff-context-supplied-to-ai.) - If git is unavailable or the repo is not a git repository: set
diff_textto empty string with a note(not a git working tree — diff is empty). Continue.
This skill MUST NOT add or read any mode: discriminator field on reviewer entries — that contract belongs to reviewer-gates. (REQ: no-mode-discriminator.)
5e — Dispatch surviving type: ai entries via the shared runner
Invoke the shared runner at ../shared/reviewer-gates/runner.md with the filtered ai_entries list. The runner enforces serial dispatch, AND-composition, and halt-after-first-Issues Found within a pass. (REQ: reuse-reviewer-gates-pipeline.)
The user-facing message passed to each type: ai subagent MUST be the concatenation of:
- The artifact's absolute path (one-line preamble).
- The artifact's current working-tree contents.
- A horizontal rule (
---). - The diff baseline label:
Diff against: <REF>(or the empty-diff note from Step 5d). diff_text(unified-diff format).
This composition is the manifestation of REQ: diff-context-supplied-to-ai — every AI reviewer receives BOTH the snapshot AND the diff. No mode: field selects one or the other.
Each AI subagent's system prompt is the entry's resolved prompt_path file contents (loaded by the runner). Wait for the verdict. The runner enforces the verdict shape (Approved | Issues Found with Blocker / Advisory severities).
Verdict composition for this artifact:
Approved— every entry inai_entriesreturnedApproved.Issues Found— at least one entry inai_entriesreturnedIssues Found. The runner halts within this artifact after the first failure; subsequent entries are NOT dispatched in this pass (perreviewer-gates#req:and-composition).
5f — Record the per-artifact result
Append to the results list:
{
path: <relative path>,
stage: <S>,
verdict: Approved | Issues Found | Skipped,
skip_reason: <string or null>,
ai_verdict_map: { <reviewer-name>: <Approved | Issues Found> },
findings: [ { reviewer, severity, text }, ... ],
notes: [ "(skipped 1 type: human entry — ...)", "(ref '...' could not be resolved — ...)", ... ],
}
5g — Serial-across-artifacts discipline
Do NOT start the next artifact's gate until the current artifact's runner has returned a verdict (or has been tagged skipped). At no point may two artifact gates be concurrently in flight. (REQ: serial-dispatch-across-artifacts.)
This is mandatory — not a performance preference. Parallel dispatch is out of MVP scope per the Feature's ## Not Doing.
Step 6 — Emit Output
Output goes to stdout (not to any file). The shape depends on reviewable_count and the --verbose flag.
6a — Single-artifact mode
When resolved_paths.length == 1 (regardless of --verbose), print the per-artifact section (REQ: output-mode-single-artifact):
<relative path>
Stage: <S>
Verdict: <Approved | Issues Found | Skipped>
<one-line notes, one per line, if any>
<For Issues Found:>
Findings:
Blocker — [<reviewer-name>] <finding text>
Blocker — [<reviewer-name>] <finding text>
Advisory — [<reviewer-name>] <finding text>
<For Skipped:>
Skipped: <skip reason>
Group findings by severity (Blocker first, then Advisory). Include the reviewer's name: per the Feature's REQ: per-artifact-section. Do NOT print a summary footer in single-artifact mode.
6b — Multi-artifact summary mode (default, no --verbose)
When resolved_paths.length >= 2 and --verbose was NOT supplied, print a summary table and footer (REQ: output-mode-multi-artifact):
| Path | Stage | Verdict | Findings |
|---------------------------------|---------|---------------|----------|
| spec/ideas/foo.md | ideate | Approved | — |
| spec/features/bar/README.md | specify | Issues Found | 2B, 1A |
| spec/research/notes.md | — | Skipped | — |
Reviewed: 2 (Approved: 1, Issues Found: 1)
Skipped: 1
- Path column: the relative path from the repo root.
- Stage column: the resolved stage, or
—for skipped artifacts. - Verdict column:
Approved,Issues Found, orSkipped. - Findings column: counts in form
<N>B, <M>A(blockers and advisories),—if none, or—for skipped. - Footer: total reviewed (artifacts that produced a verdict), broken into Approved and Issues Found, and a separate Skipped line. Skipped artifacts MUST NOT contribute to the Approved/Issues Found counts. (
REQ: output-mode-multi-artifact+REQ: skipped-artifacts-in-table.)
6c — Multi-artifact verbose mode (--verbose)
When resolved_paths.length >= 2 and --verbose was supplied, print the full per-artifact section (per 6a) for EVERY artifact, in resolved-paths order, AND the footer from 6b. The summary table MAY be present or omitted at implementation discretion — but the per-artifact detail is mandatory. (REQ: output-mode-multi-artifact + multi-artifact-verbose-mode AC.)
6d — All-skipped warning
If every artifact was tagged skipped (no verdict was computed), print:
Warning: no artifact produced a verdict. Nothing was reviewed. Check your paths and gate configuration.
This warning is mandatory whenever the reviewed count is zero — it prevents a CI integration from silently mistaking "nothing reviewed" for "everything passed". (REQ: exit-code + exit-code-all-skipped AC.)
Step 7 — Exit Code
(REQ: exit-code.)
- Every reviewed (non-skipped) artifact returned
Approved→ exit0. - At least one reviewed (non-skipped) artifact returned
Issues Found→ exit1(non-zero). - Every artifact was skipped → exit
0with the all-skipped warning from Step 6d.
Skipped artifacts MUST NOT affect the success/failure exit code — they affect only the all-skipped warning.
Verification Checklist
Before declaring the implementation complete on any change to this skill, verify (against the Score Command Feature):
- Path resolution preserves first-appearance order (
REQ: deduplication-and-order, ACdeduplication-preserves-first-order). - Index
README.mdfiles (spec/README.md,spec/features/README.md,spec/ideas/README.md,spec/plans/README.md,spec/ideas/seeds/README.md) are always skipped (ACindex-readme-skipped). -
type: humanentries are skipped per-artifact with a one-line note (AChuman-reviewers-silently-omitted). - When every reviewer for a stage is
type: human, the artifact is reported as skipped with a no-verdict note (ACall-human-list-reports-no-verdict). - AI reviewers receive both the artifact body AND the diff against
--against REF(defaultHEAD) — ACdiff-supplied-to-ai-with-default-ref, ACagainst-custom-ref-applied. - Invalid
--againstref falls back to empty diff with a per-artifact note, never refuses (ACinvalid-ref-falls-back-to-empty-diff). - Threshold confirmation prompts at
reviewable_count > 10AND-rAND no--yes(ACconfirm-at-threshold-prompts); silent below 10 (ACconfirm-at-threshold-below-bound); silent under--yes(ACyes-flag-skips-confirm). - Artifact gates run serially (AC
serial-across-artifacts). - Single-artifact output is always detailed (AC
single-artifact-detailed-output); multi-artifact default is summary mode (ACmulti-artifact-default-summary-mode);--verboseadds per-artifact detail (ACmulti-artifact-verbose-mode). - Exit codes match the Feature contract (ACs
exit-code-success,exit-code-failure,exit-code-all-skipped). - No file is written under
spec/,.specscore/, or anywhere else (ACno-files-written). - No new key is added to
specscore.yaml; no new dotfile; no new environment variable (ACno-new-config-keys). - Missing
gates:block is graceful — artifacts skipped, exit zero with no-verdict warning (ACmissing-gates-block-graceful). - Unknown flag refuses the invocation (AC
unknown-flag-refused); unmatched glob refuses (ACglob-unmatched-refused).
Red Flags
- Dispatching any reviewer not present in
gates.<stage>.reviewers. - Carrying a hardcoded baseline reviewer or reviewer-prompt inside this skill.
- Writing to any file under
spec/, including the Feature's own_tests/directory. - Refusing the invocation when
gates:is missing (this skill is signal, not gate). - Reading or writing a
mode:field on reviewer entries. - Running two artifact gates concurrently.
- Suspending the session waiting for a
type: humanreviewer. - Adding a
--parallel,--max-concurrency, or any other concurrency-control flag (out of MVP per## Not Doing). - Computing the grade inside this skill — the grade + Approve threshold come from the shared reviewer-gates layer; this skill only surfaces them.
- Writing a persisted report or injecting a badge without the explicit
--save/--badgeflag (and, for manual runs, user approval). - Emitting any SpecStudio event.
References
- Score Command Feature — the contract this skill implements; every REQ and AC traces from here.
- Reviewer Gates Feature — the contract that owns
gates.<stage>.reviewersschema, reviewer-entry shape, AND-composition, and rerun policy. ../shared/reviewer-gates/loader.md— load-and-validate protocol forgates.<stage>.reviewers. Invoked by Step 5b.../shared/reviewer-gates/runner.md— dispatch and verdict-aggregation protocol. Invoked by Step 5e.manual-review-and-score-commandsIdea — the source Idea. One command (/score); the grade + Approve threshold are single-sourced inreviewer-gates, which this skill consumes.--save/--badgeare opt-in flags on the same command.