Detailed documentation for all Python automation scripts in the scripts/ directory.
- 1. Script System Overview
- 2. uv Runtime
- 3. Script Reference
- 4. Dependencies
- 5. Error Handling
- 6. Cross-References
All scripts in this project share a common design philosophy:
- Python 3.12+ required (uses
Path | Noneunion syntax, modern type hints) - Run via uv -- the universal Python package manager acts as the runtime
- Stateless execution -- each script reads input files, processes them, and writes output; no persistent in-memory state between invocations
- Exit codes --
0= success,1= failure (with structured error details on stderr),2= configuration/usage error (some scripts) - Plugin-root aware -- scripts resolve the plugin root via
Path(__file__).resolve().parent.parent, enabling them to locate schemas, templates, and phase definitions regardless of the caller's working directory - YAML-centric --
state.yaml,profile.yaml, andphase-registry.yamlare the core data files; all scripts use PyYAML for reading and writing
Commands invoke scripts using:
uv run --project ${CLAUDE_PLUGIN_ROOT}/scripts ${CLAUDE_PLUGIN_ROOT}/scripts/<script>.py [args]uv is a fast Python package manager and project tool written in Rust. It replaces pip, pip-tools, virtualenv, and pyenv in a single binary. The SDLC plugin uses uv because:
- It resolves and installs dependencies 10-100x faster than pip
- It creates isolated virtual environments automatically per project
- It ensures reproducible script execution across machines
- Commands can invoke scripts without manually activating a virtualenv
# Using pip (any platform)
pip install uv
# macOS via Homebrew
brew install uv
# Windows via winget
winget install astral-sh.uv
# Standalone installer (Linux/macOS)
curl -LsSf https://astral.sh/uv/install.sh | shBefore first use (or after updating pyproject.toml):
cd ${CLAUDE_PLUGIN_ROOT}/scripts
uv syncThis creates a .venv/ inside scripts/ and installs all dependencies declared in pyproject.toml.
Slash commands (e.g., /sdlc-gate, /sdlc-next) invoke scripts with the --project flag pointing to the scripts directory. This tells uv which pyproject.toml governs the environment:
uv run --project /path/to/claude-code-sdlc/scripts /path/to/claude-code-sdlc/scripts/check_gates.py --state .sdlc/state.yaml --phase 0The --project flag is critical -- without it, uv would look for a pyproject.toml in the caller's working directory instead of the plugin's scripts directory.
Purpose: Validate a company profile YAML file against the canonical schema (profiles/_schema.yaml). This is the first script run during project initialization and acts as a gatekeeper ensuring profile correctness before any SDLC state is created.
CLI:
uv run scripts/validate_profile.py <profile.yaml>Arguments:
| Argument | Required | Description |
|---|---|---|
<profile.yaml> |
Yes | Path to the profile YAML file to validate (positional) |
Input: A single profile YAML file path. The schema is loaded automatically from profiles/_schema.yaml relative to the plugin root.
Validation Process:
- Load schema from
profiles/_schema.yaml(resolved viaSCHEMA_PATH = Path(__file__).resolve().parent.parent / "profiles" / "_schema.yaml") - Load profile YAML from the provided path
- Run validation checks in sequence:
| Check | What It Validates |
|---|---|
| Required fields | Top-level fields declared in schema required array |
version |
Must be a string |
company.name |
Required string |
company.profile_id |
Required string |
stack.backend_language |
Required; enum validation against allowed languages |
stack.backend_framework |
Required string |
stack.frontend_framework |
Optional; enum if present |
stack.cloud_provider |
Required; enum (azure, aws, gcp, on-prem, hybrid) |
stack.ci_cd |
Required string |
quality.coverage_minimum |
Numeric range 0-100 |
quality.max_file_lines |
Numeric, minimum 1 |
quality.max_function_lines |
Numeric, minimum 1 |
compliance.frameworks |
List; each item validated against enum (soc2, hipaa, gdpr, pci-dss, iso27001, fedramp, none) |
compliance.change_approval |
Enum (peer-review, manager-approval, change-board, none) |
conventions |
Type-checked as dict if present |
Helper Functions:
load_yaml(path)-- Safe YAML loading viayaml.safe_loadvalidate_required(data, required, context)-- Checks required field presence, returns error list with field pathsvalidate_type(value, expected_type, context)-- Type checking (string,int,float,list,dict)validate_enum(value, allowed, context)-- Enum membership validationvalidate_range(value, minimum, maximum, context)-- Numeric range validation
Output on success:
PASS -- microsoft-enterprise.yaml is valid
Output on failure:
FAIL -- 3 validation error(s):
x company: missing required field 'profile_id'
x stack.cloud_provider: 'digital-ocean' not in allowed values: azure, aws, gcp, on-prem, hybrid
x quality.coverage_minimum: 150 > maximum 100
Exit codes: 0 (valid), 1 (invalid or file not found)
Purpose: Initialize the .sdlc/ directory structure in a target project. This is the bootstrapping script that creates the entire SDLC workspace, including artifact directories for all 9 phases, the initial state file, and a frozen copy of the selected profile.
CLI:
uv run scripts/init_project.py --profile <profile.yaml> --target <project-dir> [--name <project-name>]Arguments:
| Argument | Required | Description |
|---|---|---|
--profile |
Yes | Path to the company profile YAML |
--target |
Yes | Path to the target project directory |
--name |
No | Project name (defaults to target directory name) |
Process:
- Load and validate profile -- Reads the profile YAML (does not re-run full schema validation; assumes prior
validate_profile.pypass) - Check for existing
.sdlc/-- If.sdlc/already exists, prints a warning and exits without modifying anything (safe re-run behavior) - Create directory structure -- one empty artifact directory per phase (slugs from the registry), plus the context directories:
.sdlc/ artifacts/ 00-discovery/ (empty -- artifacts are authored later, not seeded) 01-requirements/ 02-design/ 03-foundation/ build/ 07-documentation/ 08-deployment/ 09-monitoring/ close/ context/ layers/ (frozen per-phase layers) intake/ (document-intake summaries; opt-in via profile.documentation) state.yaml profile.yaml constitution.md (only if templates/constitution.md exists) - Generate
state.yamlfromtemplates/state-init.yamlwith variable substitution:${PROFILE_ID}-- replaced withprofile.company.profile_id${PROJECT_NAME}-- replaced with--nameargument or target directory name${CREATED_AT}-- replaced with current UTC timestamp in ISO 8601 format
- Copy frozen profile -- Writes the profile to
.sdlc/profile.yaml(viayaml.dump,sort_keys=False) so the project retains its configuration even if the source profile changes - Copy the constitution template -- Copies
templates/constitution.mdto.sdlc/constitution.mdif it exists
Note:
init_project.pydoes not seed phase-artifact templates. The artifact directories are created empty; the files undertemplates/phases/<slug>/are references the phase docs and agents author artifacts from — auto-seeding placeholder files would fail the completeness gate (G2) on every fresh project. The exceptions written at init areconstitution.mdandstate.yaml.
Key Constants:
PLUGIN_ROOT-- Resolved plugin directory (Path(__file__).resolve().parent.parent)TEMPLATES_DIR--templates/under the plugin root (${CLAUDE_PLUGIN_ROOT})PHASE_DIRS-- Ordered list of 9 phase directory names (the phase slugs fromphase-registry.yaml; not derived by zero-padding ints --buildandcloseare non-numeric)
Output on success:
Created .sdlc/ in /path/to/project
Profile: microsoft-enterprise
Artifacts: 9 phase directories
Context: frozen layers + intake directories
State: Phase 0 (Discovery) active
If .sdlc/ already exists, the script prints Warning: <path>/.sdlc already exists. Skipping creation. and exits 0 without modifying anything.
Exit codes: 0 (success), 1 (profile not found or load error)
Purpose: Run the 7-gate validation system against a specific SDLC phase. This is the most complex script (~11KB) and serves as the quality enforcement engine. It reads the phase registry to determine which artifacts are required, then runs each gate check against the artifacts directory.
CLI:
uv run scripts/check_gates.py --state <state.yaml> --phase <N>Arguments:
| Argument | Required | Description |
|---|---|---|
--state |
Yes | Path to .sdlc/state.yaml |
--phase |
Yes | Phase id (0, 1, 2, 3, build, 7, 8, 9, close -- may be a string) to check |
Key Functions:
get_phase_registry()-- Loadsphases/phase-registry.yamlfrom the plugin rootget_compliance_gates(profile)-- Loads framework-specific gates fromprofiles/<profile_id>/compliance/<framework>-gates.yamlcheck_artifact_exists(artifacts_dir, artifact)-- Gate 1: Verifies file/directory existence and non-emptinesscheck_artifact_not_empty(artifacts_dir, artifact)-- Gate 1: Ensures files have content (>0 bytes)check_artifact_complete(artifacts_dir, artifact)-- Gate 2: Scans for placeholder patternscheck_phase_gates(phase_id, state, profile, artifacts_base)-- Main orchestrator; runs all gates and returns results listformat_results(results)-- Formats gate results for human-readable output
Gate Details:
Checks that all required artifacts for the phase exist and are well-formed.
- File existence: Verifies each required artifact file is present in
.sdlc/artifacts/<phase-dir>/ - Non-empty check: Files must have content (>0 bytes)
- Directory children: If the artifact is a directory (e.g.,
section-plans/), verifies it contains at least one child item - Severity: MUST
Scans artifact content for placeholder patterns that indicate incomplete work.
Detected patterns:
TODO-- Unfinished work markersTBD-- "To be determined" placeholders${...}-- Unresolved template variablesPLACEHOLDER-- Explicit placeholder text[INSERT-- Template insertion points (e.g.,[INSERT DESCRIPTION HERE])<!-- REQUIRED:-- HTML comment markers for required sections
For each artifact, the script reads the file content and checks against these patterns. If any match is found, the artifact fails the completeness gate.
Build loop: the gate prints a spec-backlog summary via track_specs.py instead of a section consistency check. It scans <repo>/specs/*.md, reads each spec's frontmatter status (draft/ready/in-flight/merged) and risk (HIGH/MEDIUM/LOW), and reports as INFO:
- Total specs
- Status breakdown (merged / in-flight / ready / draft)
- Risk breakdown (HIGH / MEDIUM / LOW)
- The in-flight list
This is informational and does not block — progress is read directly from the spec files (the unit of work: one spec = one branch = one PR), so it cannot drift from a separately maintained tracker.
Severity: MUST (the completeness gate itself); the spec-backlog summary is INFO
Enforced per-change in the Build loop (coverage, file/function line counts). Checks quantitative thresholds from the profile.
- Coverage minimum:
quality.coverage_minimumfrom profile (0-100 range) - File line limits:
quality.max_file_linesfrom profile - Function line limits:
quality.max_function_linesfrom profile
Severity: applied per change inside the Build loop
Loads compliance-specific gates from the profile's compliance directory.
- Reads
compliance.frameworksfrom the profile (e.g.,["soc2", "hipaa"]) - For each framework, loads
profiles/<profile_id>/compliance/<framework>-gates.yaml - Runs each compliance gate check against the phase artifacts
- Examples: requirements must have priority labels (P0-P3), design decisions need ADR status, test cases must map to requirements
Severity: MUST for compliance-enabled profiles, SHOULD for others
Detects drift in locked metrics across phase transitions (budget, timeline, scope, stakeholder roster, quality thresholds, compliance requirements).
- Compares locked values between successive phases for unexplained changes
- Warns rather than blocks -- divergence is surfaced, not fatal
- Discrepancies are documented via the decision log
Severity: SHOULD (warns, does not block)
Checks review status and traceability.
- Verifies artifacts have been reviewed (review status markers)
- Checks traceability links between artifacts (requirements to tests, designs to implementations)
Severity: MUST for phases 2, 5; SHOULD for others
Output Format:
Results are returned as a list of dictionaries:
[
{
"gate": "G1-integrity",
"artifact": "problem-statement.md",
"passed": true,
"message": "File 'problem-statement.md' exists",
"severity": "MUST"
},
{
"gate": "G2-completeness",
"artifact": "requirements.md",
"passed": false,
"message": "Placeholder pattern found: TODO (line 42)",
"severity": "MUST"
}
]Gate Application by Phase:
| Phase | G1 | G2 | G3 | G4 | G5 Consistency | G6 Quality |
|---|---|---|---|---|---|---|
| 0 Discovery | MUST | MUST | -- | -- | -- | SHOULD |
| 1 Requirements | MUST | MUST | -- | MUST | SHOULD | SHOULD |
| 2 Design | MUST | MUST | -- | MUST | SHOULD | MUST |
| 3 Foundation | MUST | MUST | -- | -- | SHOULD | SHOULD |
| build (Build Loop) | MUST | MUST | SHOULD | SHOULD | SHOULD | SHOULD |
| 7 Documentation | MUST | MUST | -- | -- | SHOULD | SHOULD |
| 8 Deployment | MUST | MUST | -- | MUST | SHOULD | SHOULD |
| 9 Monitoring | MUST | MUST | -- | -- | SHOULD | SHOULD |
| close (Close & Transfer) | MUST | MUST | -- | -- | SHOULD | MUST |
Exit codes: 0 (all MUST gates pass), 1 (any MUST gate fails)
Purpose: Advance the SDLC project to the next phase. Supports a dry-run mode for gate preview and a confirmed mode for actual state transition. This script imports check_phase_gates and format_results directly from check_gates.py to reuse the gate logic.
CLI:
# Dry-run: show gate results without advancing
uv run scripts/advance_phase.py --state .sdlc/state.yaml
# Confirmed: advance after gate checks pass
uv run scripts/advance_phase.py --state .sdlc/state.yaml --confirmedArguments:
| Argument | Required | Description |
|---|---|---|
--state |
Yes | Path to .sdlc/state.yaml |
--confirmed |
No | Human sign-off flag; without it, the script only checks gates (dry-run) |
Process:
- Load state and profile from
.sdlc/state.yamland.sdlc/profile.yaml - Check phase boundary -- if the current phase is terminal (
close), reports completion and exits - Run all gate checks for the current phase via
check_phase_gates() - Print gate results using
format_results()(always shown, even in confirmed mode) - If any MUST gate fails: exit with code
1, print blockers - If dry-run (no
--confirmed): exit with code0, print message that--confirmedis needed - If confirmed and all gates pass: update
state.yamlatomically:- Set current phase status to
completedwithcompleted_attimestamp - Set next phase status to
activewithentered_attimestamp - Set
current_phaseto the next phase by registryorder(viaphase_model.py-- notid+1; ids may be strings) - Update
phase_namestring - Append transition record to
historyarray:- from: <current_phase_id> to: <next_phase_id> at: "2026-03-26T12:00:00+00:00" gate_results: { passed: N, failed: 0, total: N }
- Set current phase status to
- Print next phase guidance:
- Phase display name and description
- Primary and secondary skills
- Required and optional artifacts
- Artifact directory path
- Phase definition file path
Key Functions:
get_phase_registry()/get_phase_def(phase_id)-- Load and look up phase definitionsnow_iso()-- Generate UTC ISO 8601 timestampadvance(state_path, confirmed)-- Main logic; returns0(success),1(gate failure),2(config error)save_yaml(path, data)-- Atomic YAML write withdefault_flow_style=False
Output (confirmed, success):
Gate Results for Phase 0 (Discovery):
[PASS] G1-integrity: problem-statement.md -- File exists
[PASS] G2-completeness: problem-statement.md -- No placeholders found
...
[OK] Advanced: Phase 0 (discovery) -> Phase 1 (requirements)
==================================================
Now entering: Phase 1 -- Requirements
==================================================
Elicit, analyze, and document functional and non-functional requirements.
Primary skills: requirements-analysis, stakeholder-interview
Secondary skills: domain-modeling
Required artifacts:
- requirements.md
- non-functional-requirements.md
- epics.md
Optional artifacts:
- stakeholder-analysis.md
- phase2-handoff.md
Artifact directory: .sdlc/artifacts/phase01/
Phase definition: phases/01-requirements.md
Run /sdlc to see full phase guidance.
(Artifact directories use the phase slug from the registry -- e.g. 03-foundation/, build/, close/ -- never a zero-padded id.)
Exit codes: 0 (success or dry-run pass), 1 (gate failure), 2 (configuration error)
Purpose: Render SDLC phase artifacts as self-contained HTML reports. This is the largest script (~40KB) and produces polished, stakeholder-ready reports that can be opened directly in any browser without a server.
CLI:
# Single phase
uv run scripts/generate_phase_report.py --state .sdlc/state.yaml --phase 0
# Single phase with custom output path
uv run scripts/generate_phase_report.py --state .sdlc/state.yaml --phase 0 --output .sdlc/reports/phase00-report.html
# All phases (generates individual reports + index.html)
uv run scripts/generate_phase_report.py --state .sdlc/state.yaml --allArguments:
| Argument | Required | Description |
|---|---|---|
--state |
Yes | Path to .sdlc/state.yaml |
--phase |
No | Phase number to render (0-9); mutually exclusive with --all |
--all |
No | Generate reports for all phases plus an index page |
--output |
No | Custom output path (defaults to .sdlc/reports/<slug>-report.html, e.g. 00-discovery-report.html, build-report.html) |
Process:
- Load state to determine project metadata and phase statuses
- Resolve artifact directory from the registry slug (
.sdlc/artifacts/<slug>/), falling back to the project root anddocs/when an artifact is not there - For each phase:
- Look up required and optional artifacts from
PHASE_INFOmetadata - Find each artifact file via
find_artifact()(searches.sdlc/artifacts/, project root,docs/) - Convert Markdown content to HTML via
md_to_html() - Embed into the HTML template with navigation and styling
- Look up required and optional artifacts from
- For
--allmode: additionally generatesindex.htmlwith a timeline view of all phases
Key Components:
PHASE_INFOdict -- Maps each phase number to its display name and list of(filename, label)artifact tuplesmd_to_html(text)-- Minimal GitHub-flavored Markdown to HTML converter handling:- Headings (h1-h6)
- Code blocks with language class
- Tables with thead/tbody
- Unordered lists
- Bold, italic, inline code, links
- HTML escaping for security
find_artifact(project_root, phase_num, filename)-- Searches multiple locations for an artifact filebuild_nav_items()/build_gate_items()-- Generate sidebar navigation and gate status indicators
Visual Features:
- Dark theme default (
#0f1117background,#6c8ef7accent,#4ade80green) - Phase timeline navigation bar across the top
- Sidebar table of contents with found/missing status indicators
- Gate status summary showing artifact presence
- Responsive layout (sidebar collapses on mobile)
- All CSS and JS inlined -- no external dependencies
- Missing artifacts appear as labeled placeholder sections (not errors)
Output: Self-contained HTML files in .sdlc/reports/:
.sdlc/reports/
phase00-report.html
phase01-report.html
...
index.html (only with --all)
Exit codes: 0 (success), 1 (state file not found or phase invalid)
Purpose: Generate a text-based status dashboard summarizing SDLC progress. Provides a quick overview of all phases, their completion status, and artifact counts.
CLI:
# Print to stdout
uv run scripts/generate_status.py --state .sdlc/state.yaml
# Write to file
uv run scripts/generate_status.py --state .sdlc/state.yaml --output status.mdArguments:
| Argument | Required | Description |
|---|---|---|
--state |
Yes | Path to .sdlc/state.yaml |
--output |
No | Write dashboard to file instead of stdout |
Process:
- Load state and extract project metadata (
project_name,profile_id,current_phase) - Calculate progress -- count completed phases, compute percentage
- Generate ASCII progress bar -- 20-character bar with
#(filled) and-(empty) - Build phase table -- iterate all 9 phases, show status icon, name, artifact count, and timestamps
- Count artifacts per phase by scanning
.sdlc/artifacts/<phase-dir>/
Status Icons:
| Status | Icon |
|---|---|
completed |
[x] |
active |
[>] |
pending |
[ ] |
skipped |
[-] |
Output Example:
# SDLC Status Dashboard
**Project:** my-app
**Profile:** microsoft-enterprise
**Current Phase:** 2 -- Design
**Progress:** [####------------] 20% (2/9 phases)
## Phases
| # | Phase | Status | Artifacts | Entered | Completed |
|---|----------------|--------|-----------|------------|------------|
| 0 | Discovery | [x] | 3 | 2026-03-01 | 2026-03-05 |
| 1 | Requirements | [x] | 5 | 2026-03-05 | 2026-03-15 |
| 2 | Design | [>] | 2 | 2026-03-15 | -- |
| 3 | Foundation | [ ] | 0 | -- | -- |
| build | Build Loop | [ ] | 0 | -- | -- |
...
| close | Close & Transfer | [ ] | 0 | -- | -- |Exit codes: 0 (success), 1 (state file not found)
Purpose: Analyze gate effectiveness across completed SDLC phases. Identifies gates that are too lenient (always pass), too strict (consistently fail), or frequently overridden. Useful for tuning gate configuration over time and across projects.
CLI:
# Single project audit
uv run scripts/audit_gates.py --state .sdlc/state.yaml
# Compare two projects side-by-side
uv run scripts/audit_gates.py --state .sdlc/state.yaml --compare /other-project/.sdlc/state.yamlArguments:
| Argument | Required | Description |
|---|---|---|
--state |
Yes | Path to .sdlc/state.yaml |
--compare |
No | Path to another state.yaml for cross-project comparison |
Process:
- Extract gate history via
extract_gate_history(state):- Iterates all phases in
state.phases - Collects
gate_resultsfrom each completed phase - Handles both list and dict formats for gate results
- Returns flat list of gate result records with phase attribution
- Iterates all phases in
- Analyze gates via
analyze_gates(results):- Aggregates per-gate statistics using
defaultdict:total-- total number of checkspassed-- count of passed checksfailed-- count of failed checksmanual-- count of manual/overridden checksphases_seen-- set of phases where the gate was evaluatedoverrides-- list of override records with justifications
- Aggregates per-gate statistics using
- Format report via
format_report(gate_stats, state):- Summary section: project name, total gates evaluated, overall pass rate
- Always-Pass Gates: gates with 0 failures and >0 evaluations (candidates for tightening or removal)
- High-Fail Gates: gates with >50% failure rate (possible process issues or overly strict thresholds)
- Override History: table of all manual overrides with gate name, phase, and justification
- Recommendations: actionable suggestions based on the analysis
Output Sections:
# Gate Effectiveness Audit
Project: my-app | Phases analyzed: 5
## Summary
Total gate evaluations: 42
Overall pass rate: 85.7%
## Always-Pass Gates (candidates for tightening or removal)
- G1-integrity: passed 15x across phases [0, 1, 2, 3, 4]
## High-Fail Gates (>50% failure rate -- possible process issues)
- G3-metrics: failed 4/6 (67%)
## Override History
| Gate | Phase | Justification |
|------|-------|---------------|
| G2-completeness | 3 | TDD stubs intentionally contain TODO markers |
## Recommendations
- 1 always-pass gate(s) detected. Consider tightening or removing.
- 1 override(s) recorded. Review whether overridden gates should be relaxed.
Cross-Project Comparison: When --compare is provided, the script runs the full analysis pipeline on both state files and prints both reports separated by a divider. This enables manual comparison of gate configurations across projects.
Exit codes: 0 (success), 1 (state file not found)
Purpose: Synthesize Phase 0 (Discovery) and Phase 1 (Requirements) artifacts into a single consolidated specification file. This spec serves as the primary input for /deep-plan integration, providing a self-contained project description.
CLI:
uv run scripts/synthesize_spec.py --state .sdlc/state.yaml [--output <path>]Arguments:
| Argument | Required | Description |
|---|---|---|
--state |
Yes | Path to .sdlc/state.yaml |
--output |
No | Output file path (defaults to .sdlc/artifacts/spec.md) |
Process:
- Resolve artifacts base from
state.yamlparent directory - Read Phase 0 artifacts (optional enrichment):
00-discovery/problem-statement.md00-discovery/constraints.md00-discovery/success-criteria.md
- Read Phase 1 artifacts (primary content):
01-requirements/requirements.md(REQUIRED -- script exits with error if missing/empty)01-requirements/non-functional-requirements.md01-requirements/epics.md01-requirements/phase2-handoff.md
- Assemble specification by concatenating sections with headers:
# Project Specification(with synthesis timestamp and source path)## Problem Statement(from Phase 0, if available)## Requirements(from Phase 1, always present)## Non-Functional Requirements(if available)## Epics & User Stories(if available)## Constraints(from Phase 0, if available)## Success Criteria(from Phase 0, if available)## Phase 2 Handoff Notes(if available)
- Write output to the specified path
Key Function:
read_artifact(path)-- Returns file content as stripped string, or empty string if file does not exist. Usesencoding="utf-8"witherrors="replace"for robustness.
Output: A single Markdown file combining all early-phase artifacts into a format consumable by /deep-plan.
Exit codes: 0 (success), 1 (requirements.md missing or empty, state file not found)
Purpose: Transform /deep-plan section outputs into SDLC-formatted phase artifacts. This is the bridge between the /deep-plan planning system and the SDLC artifact structure. Supports both Phase 2 (Design) and Phase 3 (Foundation) mapping modes. The script is idempotent and safe to re-run.
CLI:
# Phase 2 mapping (after /deep-plan steps 1-15)
uv run scripts/map_deep_plan_artifacts.py --state .sdlc/state.yaml --phase 2 --planning-dir planning/
# Phase 3 mapping (after /deep-plan steps 16-22)
uv run scripts/map_deep_plan_artifacts.py --state .sdlc/state.yaml --phase 3 --planning-dir planning/Arguments:
| Argument | Required | Description |
|---|---|---|
--state |
Yes | Path to .sdlc/state.yaml |
--phase |
Yes | Target phase (2 or 3) |
--planning-dir |
Yes | Path to the /deep-plan planning directory |
Key Functions:
copy_if_exists(src, dest)-- Copies a file or directory if source exists; creates parent directories as needed; handles both files (shutil.copy2) and directories (shutil.copytreewith overwrite)extract_sections_by_heading(content, level)-- Parses Markdown into a dict of{heading: content}by heading levelparse_section_manifest(index_content)-- Extracts theSECTION_MANIFESTfromsections/index.mdusing a regex match on<!-- SECTION_MANIFEST ... END_MANIFEST -->comment blocksparse_project_config(index_content)-- Extracts project configuration values from the indextransform_section_to_sdlc(section_name, number, content, tdd_content, manifest)-- Converts a/deep-plansection file into the SDLC converged template formatmap_phase_2(planning_dir, artifacts_dir)-- Phase 2 specific mapping (design artifacts)map_phase_3(planning_dir, artifacts_dir)-- Phase 3 specific mapping (section plans)
Phase 3 Mapping Process (primary use case):
- Read section manifest from
planning/sections/index.md - Fallback discovery -- if no manifest found, glob for
section-*.mdfiles directly - Read TDD plan from
planning/claude-plan-tdd.md - For each section in manifest:
- Locate the corresponding
/deep-planfile atplanning/sections/<section-name>.md - Transform to SDLC format via
transform_section_to_sdlc() - Write to
.sdlc/artifacts/03-foundation/section-plans/SECTION-NNN.md(zero-padded 3-digit number)
- Locate the corresponding
- Copy supplementary files:
claude-plan-tdd.md->.sdlc/artifacts/03-foundation/tdd-plan.mdsections/index.md->.sdlc/artifacts/03-foundation/dependency-map.md
Converged Template Format:
The output uses SECTION-template-deep-plan.md which preserves both systems' requirements:
- SDLC structured fields: Goal, Epics/Stories, Entry/Exit Criteria, Dependencies, Interfaces, Test Strategy, Risk
/deep-planprose: Full implementation guidance in a dedicated "Implementation Guidance" section, self-contained enough for/deep-implementto consume
Output Structure:
.sdlc/artifacts/03-foundation/
section-plans/
SECTION-001.md
SECTION-002.md
...
SECTION-NNN.md
tdd-plan.md
dependency-map.md
Exit codes: 0 (success), 1 (state file or planning directory not found)
Purpose: Single source of truth for the risk taxonomy and the checking ladder. A spec's risk tier (HIGH/MEDIUM/LOW, assigned by a human at triage) sets how high its change climbs the checking ladder in the Discern beat. This module encodes that mapping once so check_spec.py and any other consumer resolve review depth the same way. It is a library module (imported), not a CLI — it has no main().
Imported by: check_spec.py, track_specs.py, generate_handoff_report.py (indirectly via track_specs).
What scales by tier: the depth of human review and whether the security pass + named sign-off are mandatory — never whether checking happens at all. Every tier blocks on CI, runs the grader (advises), runs the correctness gate (blocks on a defect), and requires a non-author approval. HIGH adds a blocking security pass and a named human sign-off recorded in the PR. The security pass also fires path-triggered (any PR touching a gated path), independent of tier.
Public API:
| Function | Returns | Description |
|---|---|---|
normalize_tier(tier) |
str | None |
Canonical upper-case tier from any source, or None if invalid |
is_valid_tier(tier) |
bool |
Whether the value names a valid tier |
resolve_ladder(tier, touches_gated_path=False) |
dict | None |
The required ladder booleans for a tier (ci_blocks, grader_runs, correctness_blocks_on_defect, non_author_approval, security_pass_required, named_signoff_required, plus review_depth). touches_gated_path=True forces the security pass |
required_rungs(tier, touches_gated_path=False) |
list[str] |
Human-readable list of gates a tier must clear, in ladder order |
Module constants: RISK_TIERS = ("HIGH", "MEDIUM", "LOW"), TAXONOMY (what lands in each tier and what it triggers — mirrored in CLAUDE.md so agents see it).
Exit codes: N/A (library module).
Purpose: Scaffold a new Build-loop spec (specs/NNNN-name.md) from the spec template. Specs are the durable per-change unit of the Build loop — one spec = one branch = one PR — and live in the target repo's specs/ directory (in the repo, not under .sdlc/), so the agent and grader read them from version control like any source file.
CLI:
# Workflow mode (repo root = .sdlc parent)
uv run scripts/new_spec.py --state .sdlc/state.yaml --name "duplicate claim 409" --risk HIGH
# Standalone mode (any repo, no .sdlc/ required)
uv run scripts/new_spec.py --repo <path> --name "duplicate claim 409" --risk HIGH --source REQ-123Arguments:
| Argument | Required | Description |
|---|---|---|
--state |
One of --state/--repo |
Path to .sdlc/state.yaml (workflow mode) |
--repo |
One of --state/--repo |
Target repo root (standalone; default cwd) |
--name |
Yes | Short descriptive name (becomes the kebab-case slug and frontmatter name) |
--risk |
No | Risk tier HIGH/MEDIUM/LOW (default MEDIUM) — the agent proposes, a human confirms |
--source |
No | Originating story / REQ-id (default —) |
Process:
- Resolve repo root from
--state(.sdlcparent) or--repo - Allocate id — scan
specs/*.md, take the highest 4-digit prefix + 1 (0001when none), so ids stay stable and gap-free across sessions - Render the template (
templates/phases/build/spec.md) — fill frontmatterspec/name/risk/source/created, the title, and sync the body**Tier:**and**Ladder depth:**lines to the chosen risk (both agreements thatcheck_spec.pyenforces) - Write
specs/NNNN-slug.md(errors if it already exists)
Exit codes: 0 (created), 1 (invalid --risk, empty slug, template missing, state file missing, or spec already exists)
Purpose: Enforce the Definition of Ready (DoR) on a Build-loop spec — the "Intent rail": a story becomes buildable only by clearing the DoR. Checks the mechanical floor of that bar (structure, risk tier, scope in/out, no placeholders, field/section agreement, checking-ladder depth) plus an advisory vague-line lint on acceptance checks.
CLI:
# Standalone
uv run scripts/check_spec.py --spec specs/0001-duplicate-claim-409.md
# Workflow (also logs metrics to .sdlc/metrics/spec-log.jsonl)
uv run scripts/check_spec.py --spec specs/0001-duplicate-claim-409.md --state .sdlc/state.yamlArguments:
| Argument | Required | Description |
|---|---|---|
--spec |
Yes | Path to specs/NNNN-name.md |
--state |
No | Path to .sdlc/state.yaml — enables empirical metrics logging |
Checks (MUST blocks, exit 1; SHOULD advises, exit 0 — mirrors check_gates.py severities):
| Severity | Check |
|---|---|
| MUST | Parseable YAML frontmatter present |
| MUST | name set (not the template default) |
| MUST | risk is one of HIGH/MEDIUM/LOW |
| MUST | All required sections present (Goal, Why, Scope, Acceptance Checks, Risk Tier, Delegation Plan, Checking Plan) |
| MUST | Scope > In scope and Scope > Out of scope both non-empty |
| MUST | At least one acceptance check |
| MUST | No unfilled template placeholders (TODO, TBD, NNNN, <title>, …) |
| MUST | Risk Tier section agrees with frontmatter risk |
| MUST | Checking Plan **Ladder depth:** declared and equal to the risk tier |
| SHOULD | harness_context names the ONE existing pattern reused |
| SHOULD | Vague-line lint — acceptance checks carrying vague words or lacking a concrete signal |
| SHOULD | HIGH spec's Checking Plan names the security pass and the named sign-off |
Key functions: parse_frontmatter, extract_section / extract_subsection, list_items, check_spec_text (the orchestrator, importable — reused by track_specs.py), log_spec_metrics, format_results.
Exit codes: 0 (ready, possibly with advisories), 1 (any MUST fails, or spec not found)
Purpose: Track the Build-loop spec backlog from the specs themselves. In the spec-driven Build loop the spec IS the unit of work and the durable source of truth, so backlog progress is derived from each spec's frontmatter status — never from a separate hand-maintained tracker that can drift. This replaced the section-plan progress model (sections-progress.json) in the Build loop.
CLI:
# Workflow
uv run scripts/track_specs.py --state .sdlc/state.yaml [--wip-cap 3] [--json]
# Standalone
uv run scripts/track_specs.py --repo <path>Arguments:
| Argument | Required | Description |
|---|---|---|
--state |
One of --state/--repo |
Path to .sdlc/state.yaml (workflow mode) |
--repo |
One of --state/--repo |
Target repo root (standalone; default cwd) |
--wip-cap |
No | Flag when more than N specs are in-flight (the cap itself lives in cadence-plan.md) |
--json |
No | Emit the summary as JSON |
Output: Total specs; breakdown by status (draft/ready/in-flight/merged) and by risk tier; the in-flight list (one spec = one branch = one PR); WIP-cap warnings. Invoked by check_gates.py to print the Build gate's INFO spec-backlog summary, and by generate_handoff_report.py for the handoff report's spec-backlog section.
Key functions: scan_specs (parses every specs/*.md frontmatter), summarize, wip_warnings — all importable.
Exit codes: 0 (no warnings), 1 (a WIP-cap breach was flagged)
Purpose: Record and report the Build-loop steering scorecard — the numbers that steer the loop, baseline-and-trend. The delivery-standard steers on outcomes, not activity. This tool records loop outcome events to .sdlc/metrics/loop-events.jsonl and reports the scorecard from them. It is deliberately separate from gate-log.jsonl (gate calibration) and spec-log.jsonl (DoR data): those measure the harness; this measures delivery.
CLI:
# Record an outcome event
uv run scripts/scorecard.py record --state .sdlc/state.yaml --type spec_merged --field accepted_as_is=true --field risk=HIGH
# Report the scorecard
uv run scripts/scorecard.py report --state .sdlc/state.yaml [--window-days 14] [--json]Subcommands & arguments:
| Subcommand | Argument | Description |
|---|---|---|
record / report |
--state / --repo |
.sdlc/state.yaml (workflow) or repo root (standalone; default cwd) |
record |
--type |
Event type (see below) |
record |
--field key=value |
Repeatable; bools/ints/floats coerced, else string |
report |
--window-days |
Label only (date filtering is the caller's job) |
report |
--json |
Emit the scorecard as JSON |
Event types: spec_merged (accepted_as_is, risk), spec_reverted, spec_bounced, escaped_bug (which_check), deploy (env, succeeded, lead_time_hours, caused_failure), incident (ttr_hours), review_wait (wait_hours, security).
Reported metrics: accepted-as-is rate, rework/revert rate, bounce-back rate, review-wait median (security-review wait on its own line), the DORA four (deploy count, lead-time median, change-fail rate, time-to-recover median), and escaped bugs. Missing data reads "no data", never a fabricated zero.
Refused by design: recording velocity, story_points, pr_count, lines_of_code, commits exits 2 — the activity metrics the standard never tracks. compute_scorecard, load_events, format_report, _pct, _hrs are importable (reused by generate_handoff_report.py).
Exit codes: 0 (success), 1 (unknown event type, bad --field, or state file missing), 2 (a forbidden activity metric was passed to record)
Purpose: Draft the Phase C (Close & Transfer) final-handoff-report.md from the engagement's own records — the deterministic first pass of close.md Step 4 ("Hand over the record"), run before the Explore agent enriches the narrative. It fills the existing handoff-report template's mechanical sections from real data and marks the judgment sections with [Fill: ...] slots.
CLI:
# Workflow mode
uv run scripts/generate_handoff_report.py --state .sdlc/state.yaml
# Standalone mode (no .sdlc/ — notes the missing context in the report header)
uv run scripts/generate_handoff_report.py --repo <path>
# Custom output / regenerate over a prior draft
uv run scripts/generate_handoff_report.py --state .sdlc/state.yaml --output handoff.md --forceArguments:
| Argument | Required | Description |
|---|---|---|
--state |
One of --state/--repo |
Path to .sdlc/state.yaml (workflow mode) |
--repo |
One of --state/--repo |
Repo root containing .sdlc/ (standalone; defaults to cwd) |
--output |
No | Output path (defaults to .sdlc/artifacts/close/final-handoff-report.md) |
--force |
No | Overwrite an existing report (refuses by default, to protect human edits) |
Sections filled from records:
| Section | Source |
|---|---|
| Phase report index | phase_model.all_phases() + presence of <slug>-report.html in .sdlc/reports/ |
| Engagement record | per-phase gate status, completion date, and approver from state.yaml phases[] |
| Metrics history | scorecard.compute_scorecard() over .sdlc/metrics/loop-events.jsonl |
| Spec backlog | track_specs.summarize(scan_specs()) over specs/ |
Sections left as [Fill: ...] slots (judgment, not data): outcomes against the Phase 0 statement, the technical debt log, open items with owners/dates, and the outcomes-dashboard handover.
Honest by design: missing metrics read "no data", never a fabricated zero (inherited from scorecard.py). A standalone run with no state.yaml adds a "Standalone draft" banner noting the engagement context is absent.
Exit codes: 0 (drafted), 1 (state file not found, or the report exists and --force was not given)
Defined in scripts/pyproject.toml:
[project]
name = "claude-code-sdlc-scripts"
version = "0.1.0"
description = "Automation scripts for claude-code-sdlc plugin"
requires-python = ">=3.12"
dependencies = [
"pyyaml>=6.0",
"jsonschema>=4.20",
]
[project.optional-dependencies]
test = [
"pytest>=7.0",
"pytest-cov>=4.0",
]| Dependency | Version | Purpose |
|---|---|---|
| PyYAML | >=6.0 | YAML parsing and serialization for state.yaml, profile.yaml, phase-registry.yaml, and compliance gate files |
| jsonschema | >=4.20 | JSON Schema validation (available for structured validation beyond YAML) |
| pytest | >=7.0 (dev) | Test runner for script unit tests |
| pytest-cov | >=4.0 (dev) | Coverage reporting for pytest |
| Python | >=3.12 | Required for modern type hint syntax (Path | None, list[str]) |
Build system: Hatchling (hatchling.build)
Test configuration: Tests live in scripts/tests/, run with uv run pytest or uv run pytest --cov.
All scripts follow consistent error handling conventions:
- Errors to stderr: Critical errors are written to
sys.stderr(some scripts useprint(..., file=sys.stderr)) - Structured messages: Error messages include file paths and field names for easy debugging:
Error: State file not found: /path/to/.sdlc/state.yaml Error: requirements.md not found or empty in 01-requirements/ company: missing required field 'profile_id' - Non-zero exit codes: All failures produce exit code
1(or2for configuration errors inadvance_phase.py), propagated to the calling command - Graceful degradation: Where possible, scripts continue with warnings rather than hard failures:
init_project.pywarns and skips if.sdlc/existssynthesize_spec.pytreats Phase 0 artifacts as optionalmap_deep_plan_artifacts.pywarns on missing section files and skips themgenerate_phase_report.pyshows placeholder sections for missing artifacts
- YAML round-trip safety: State updates use
yaml.dump()withdefault_flow_style=Falseandallow_unicode=Trueto preserve human readability
| Document | Relationship |
|---|---|
| commands.md | Documents which slash commands invoke which scripts |
| gate-system.md | Detailed gate semantics and severity rules |
| architecture.md | Overall plugin architecture and data flow |
| profiles.md | Profile YAML structure and schema reference |
phases/phase-registry.yaml |
Phase definitions consumed by check_gates.py and advance_phase.py |
scripts/phase_model.py |
Single source of truth for phase ids, order, and slugs (reads phase-registry.yaml) |
profiles/_schema.yaml |
Schema consumed by validate_profile.py |
templates/state-init.yaml |
State template consumed by init_project.py |
templates/phases/03-foundation/section-plans/SECTION-template-deep-plan.md |
Converged template consumed by map_deep_plan_artifacts.py |