Skip to content
Back to skills

Spec Shippability Review

ASecurity

Dev-only check that a drafted spec for THIS baseline repo won't ship dev-tree references to consumer installs. Catches three failure modes — shipped SKILL.md prose that references paths under `src/`, `tests/`, `scripts/`, `obj/` as runtime invocations (in ```bash fences``` OR `inline backticks`, plus shipped `.mjs`/`.js`/`.sh`/`.py` helper-file imports); new Python helpers added under `.claude/skills/<slug>/` (shipped helpers must be `.sh` or `.mjs`/`.js` going forward); and imports of module...

  • 14 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 4, 2026
ai-agentspythongoshellbashnodedocumentation

Works with

  • cli

Security analysis

A100/100

Pro scans all 17 files and shows the line behind each finding

Scanned October 4, 2026

npx -y skills add friedbotstudio/baseline --skill spec-shippability-review --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Spec Shippability Review?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Spec Shippability Review
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/friedbotstudio-spec-shippability-review/badge)](https://www.skillsdirectory.com/skills/friedbotstudio-spec-shippability-review)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: spec-shippability-review
description: Dev-only check that a drafted spec for THIS baseline repo won't ship dev-tree references to consumer installs. Catches three failure modes — shipped SKILL.md prose that references paths under `src/`, `tests/`, `scripts/`, `obj/` as runtime invocations (in ```bash fences``` OR `inline backticks`, plus shipped `.mjs`/`.js`/`.sh`/`.py` helper-file imports); new Python helpers added under `.claude/skills/<slug>/` (shipped helpers must be `.sh` or `.mjs`/`.js` going forward); and imports of modules that aren't in `obj/template/.claude/manifest.json` (consumer won't have the file). The aggregate scanner (`scan-shipped-skills.mjs`) walks only baseline-owned skill dirs (via `owner: baseline` frontmatter) at top level — `references/` and other subdirs are documentation, not runtime. BLOCKER findings hard-block implementation entry; ADVISORY surfaces but doesn't block. Read-only — surfaces a punch list; maintainer edits the spec and re-runs until CLEAN.
---

<!-- character:begin -->

## Character

- **Soul.** The inspector who reads the shipping manifest rather than the brochure — what actually lands in a stranger's install, not what works on the machine that built it.
- **Motivation.** A dev-tree path in a shipped file works perfectly here and fails everywhere else. Catching it costs a line now and a release later.
- **Mantra.** "It works locally" is where my check begins, never where it ends.
- **Temperament.** The customs inspector's literalism. Opens the crate rather than reading the label, and extends no benefit of the doubt to a path that resolves only on the machine that built it.
- **Voice.** Cites the exact line and the exact reason it breaks elsewhere. No hedging and no severity inflation — a finding is a BLOCKER or it is not.
- **Resolve.** The install that breaks is on a machine I will never see. This is the only place I can stand for that person.

<!-- character:end -->

<!-- DEV-ONLY SKILL — this file lives in `.claude/skills/spec-shippability-review/`
     of the baseline repository for the maintainer's own workflow. It is excluded
     from the shipped template by `scripts/build-template.sh` Stage 1.5 (no
     `owner: baseline` frontmatter → pruned from `obj/template/`). End-user
     projects never see this skill. -->

# /spec-shippability-review — catch dev-tree references that would break in consumer installs

You are running a consumer-install lens over a drafted spec for THIS baseline repo (`@friedbotstudio/create-baseline`). The bug this skill catches is the v0.8.1 marker-import: a SKILL.md procedure said `node -e "import('./src/cli/reconciliation-marker.js')..."` and shipped to npm. Consumer installs don't receive `src/`, so every `/upgrade-project` run errored with `ERR_MODULE_NOT_FOUND` on the marker write. The existing spec validators (`spec-lint`, `spec-diagram-review`, `spec-traceability-review`) didn't catch it because none of them know what lands in a consumer install.

This skill is **dev-only** — it lives in the baseline dev tree and is pruned from `obj/template/` by `scripts/build-template.sh` Stage 1.5 (no `owner: baseline` frontmatter). Consumer projects don't receive it and don't need it: the "dev spills to prod" failure mode is unique to meta-tools that ship their own code.

## When to use

- The harness runs this as a workflow phase between `/spec` and `/approve-direction` (the node is wired into `intake-full` and `spec-entry` tracks in `.claude/workflows.jsonl`; `tdd-quickfix` and `chore` skip it because they have no spec phase).
- Ad-hoc: `/spec-shippability-review <slug>` when iterating on a spec draft.
- `scripts/build-template.sh` invokes the aggregate `scan-shipped-skills.mjs` entry point at Stage 1.6 (after Stage 1.5 prunes dev-only skills, before Stage 3 stamps the manifest). This re-validates the actual shipped `SKILL.md` content at every build so a baseline-owned skill that references dev-tree paths or unshipped modules cannot reach npm — even if it slipped past the per-spec check earlier.

## Prereq

`docs/specs/<slug>.md` exists. The spec must have a `## Design` section, a write_set described in the Test plan or Contracts, and any SKILL.md prose it proposes to add or modify embedded as code fences.

## Procedure

1. **Run the analyzer.** Invoke the JS helper:
   ```
   node .claude/skills/spec-shippability-review/check.mjs <slug>
   ```
   The helper reads the spec, extracts write_set + SKILL.md code fences + path mentions, runs the three checks below, writes a punch list to `.claude/state/spec-shippability/<slug>.json`, and exits:
   - `0` — CLEAN (zero findings)
   - `1` — NEEDS_REVIEW (one or more ADVISORY findings, no BLOCKER)
   - `2` — BLOCKED (one or more BLOCKER findings)

2. **Surface the punch list** to the user verbatim. The helper prints to stdout in a human-readable format (file:line citations + suggested fix per finding). The same content is in the JSON state file for `direction_approval_guard` to consume.

3. **Verdict-based next-action**:
   - **CLEAN** → tell the user: "Shippability review CLEAN. Run `/approve-direction docs/specs/<slug>.md` when ready."
   - **NEEDS_REVIEW** → tell the user: "N ADVISORY finding(s). Review and address or accept; ADVISORY does not block `/approve-direction`. Re-run `/spec-shippability-review <slug>` after edits to confirm."
   - **BLOCKED** → tell the user: "N BLOCKER finding(s). implementation entry will be refused by the pre-implementation checkpoint until these are fixed. Edit the spec and re-run."

## The three checks

### C1 — DEV_TREE_RUNTIME_REF (BLOCKER)

Scan code fences within SKILL.md content blocks that the spec proposes to add or modify. Flag any runtime invocation that references a path under a dev-only prefix:

| Prefix | Why dev-only |
|---|---|
| `src/` | CLI source code; not in shipped manifest |
| `tests/` | test files; not shipped |
| `scripts/` | build scripts; not shipped |
| `obj/` | build output; not shipped |
| `docs/` (except `docs/init/seed.md`) | development documentation; consumer gets only seed.md |

Runtime invocation = command-line patterns inside ` ```bash` / ` ```sh` / ` ```shell` fences that would execute against the consumer's filesystem: `node ./path/...`, `node -e "import('./path/...')"`, `python3 ./path/...`, `bash ./path/...`, `<exec>` referencing a relative path.

Suggested fix in the punch list: "Move the logic into a shipped helper under `.claude/skills/<slug>/<helper>.mjs`, OR inline the implementation into the `node -e \"...\"` command body."

### C2 — DEV_HELPER_EXTENSION (BLOCKER for new `.py`, ADVISORY for modifications)

Scan the spec's write_set for paths under `.claude/skills/<slug>/`. For each path with a `.py` extension:

- If the path does NOT exist on disk yet → **BLOCKER** (new Python helper in shipped skill). Suggested fix: rewrite as `.mjs` (Node ESM, stdlib only, no third-party deps for parity with the existing JS-side baseline).
- If the path DOES exist on disk → **ADVISORY** (modification to grandfathered Python helper). The grandfathering carve-out exists for legacy `.py` helpers; the previous grandfathered set (`sweep.py` and `drift_check.py`) has been ported to `.mjs` and no grandfathered files remain on disk today. The branch still fires only if a previously-shipped `.py` is re-introduced — recommend porting to JS in the same workflow.

### C3 — UNSHIPPED_MODULE_IMPORT (BLOCKER)

For every relative import / require / node invocation found in C1's scan that has a sub-`.claude/` path (e.g., `.claude/skills/foo/helper.mjs`, `.claude/scripts/something.js`), resolve it against the shipped manifest at `obj/template/.claude/manifest.json → files`. If the resolved path is NOT a key in the manifest's `files` map, the consumer won't have that file at install time.

Suggested fix in the punch list: "Add the file to a baseline-owned skill directory so the recursive cp copies it into `obj/template/`, OR change the invocation to use a file that IS shipped."

Note: C3 is a stricter superset of C1. C1 catches the obvious `src/...` case; C3 catches the subtler case of a `.claude/`-prefixed path that exists in the dev tree but isn't shipped (e.g., a helper in a skill that lacks `owner: baseline`).

## Punch-list shape (the contract `direction_approval_guard` reads)

`.claude/state/spec-shippability/<slug>.json`:

```json
{
  "slug": "<workflow slug>",
  "spec_path": "docs/specs/<slug>.md",
  "verdict": "CLEAN" | "NEEDS_REVIEW" | "BLOCKED",
  "generated_at": "<ISO-8601 UTC>",
  "findings": [
    {
      "severity": "BLOCKER" | "ADVISORY",
      "check": "DEV_TREE_RUNTIME_REF" | "DEV_HELPER_EXTENSION" | "UNSHIPPED_MODULE_IMPORT",
      "file": "<path within the repo, e.g. docs/specs/<slug>.md or the SKILL.md the spec proposes to modify>",
      "line": <integer or null>,
      "evidence": "<short snippet that triggered the finding>",
      "message": "<one-line description of why this fails the consumer-install lens>",
      "suggested_fix": "<concrete fix the maintainer can apply>"
    }
  ]
}
```

`direction_approval_guard.sh` reads `verdict`. If `BLOCKED`, it denies the approval-token write with an error message that includes the count of findings and the first three messages.

## Constraints

- **Read-only.** This skill does NOT modify the spec. The maintainer edits and re-runs.
- **Dev-only.** No `owner: baseline` frontmatter — pruned from shipped template. Consumer projects don't run this check and don't need it.
- **JS-only helper.** `check.mjs` is Node ESM, stdlib only. Eats its own dogfood per C2 (no new Python in shipped helpers).
- **Idempotent.** Re-running on an unchanged spec produces the same punch list.
- **Workflow integration.** When invoked as a workflow phase by the harness, success (exit 0 or 1) appends `"spec-shippability-review"` to `workflow.json → completed` and continues; failure (exit 2 = BLOCKED) yields with the punch list as the reason.

Files in this skill

  • SKILL.md9.8 KB
  • analyzer.mjs8.7 KB
  • check.mjs6.3 KB
  • scan-shipped-skills.mjs11.4 KB
  • tests/expected/advisory-mixed.json213 B
  • tests/expected/blocker-dev-import.json418 B
  • tests/expected/blocker-python.json206 B
  • tests/expected/clean.json81 B
  • tests/fixtures/advisory-mixed.md314 B
  • tests/fixtures/blocker-dev-import.md540 B
  • tests/fixtures/blocker-helper-import.mjs613 B
  • tests/fixtures/blocker-inline-backtick.md1 KB
  • tests/fixtures/blocker-python.md333 B
  • tests/fixtures/clean-helper.mjs518 B
  • tests/fixtures/clean.md401 B
  • tests/fixtures/shipped-skill-blocker/planted/SKILL.md743 B
  • tests/fixtures/shipped-skill-unshipped/planted/SKILL.md558 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.