agentleFS
MCKRUZMCKRUZ/shepherd-sdlc/CLAUDE.md

shepherd-sdlc

CLAUDE.md9 starsChanged 27 days agoMITView on GitHub

The file

We split this CLAUDE.md into the rules it gives an agent

2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Every agent and command is a library part, not just a pipeline stage.

    line 28
  2. Very specific:

    When adding a new agent or command, document both modes in its file.

    line 32

≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
CLAUDE.md

Shepherd — Development Instructions

What This Is

Shepherd is a Claude Code plugin that orchestrates the full SDLC lifecycle using company-configurable profiles. It makes the AI-SDLC methodology executable within Claude Code.

Architecture

  • plugin.json — Plugin manifest (entry point for Claude Code)
  • SKILL.md — Main skill definition (loaded when plugin activates)
  • commands/ — 33 slash commands (/sprd, /sprd-setup, /sprd-status, /sprd-next, /sprd-gate, /sprd-enhance, /sprd-coach, /sprd-review, /sprd-intake, /sprd-brief, /sprd-spec, /sprd-spike, /sprd-sprint, /sprd-refine, /sprd-handoff, /sprd-spec-status, /sprd-audit, /sprd-audit-artifacts, /sprd-channel, /sprd-data, /sprd-doctor, /sprd-evals, /sprd-experience, /sprd-feature, /sprd-harness, /sprd-phase-report, /sprd-refresh, /sprd-retro, /sprd-revise, /sprd-rules, /sprd-upgrade, /sprd-version, /sprd-ado)
  • agents/ — 14 agents (orchestrator, requirements-analyst, compliance-checker, section-evaluator, narrative-enhancer, gate-repair, multi-reviewer, discovery-analyst, document-summarizer, feature-architect, visual-designer, conversation-designer, data-analyst, bizreq-analyst)
  • channels/ — Cross-profile channel descriptor library (_schema.yaml, ag-ui.yaml, voice.yaml, chat.yaml) — the delivery-surface vocabulary the channel layer reads
  • profiles/ — Company/stack YAML configs with compliance gates
  • phases/ — Phase definitions (9 phases: 0, 1, 2, 3 Foundation, build loop, 7, 8, 9, close). The 4/5/6 gap is intentional — the batch Implementation/Quality/Testing phases are replaced by the continuous Build loop. Phase identity/ordering is owned by scripts/phase_model.py reading phases/phase-registry.yaml.
  • references/ — Progressive disclosure docs (loaded on-demand)
  • templates/ — Artifact templates copied to target projects
  • scripts/ — Python automation (uv runtime)
  • scripts/code_host.py + ado_import.py (with ado_map.py / ado_transport.py / ado_pipelines.py), ado_outcomes.py, import_outcomes.py, host_report.py, ado_sync.py (with ado_schema.py / ado_wit.py / ado_sync_model.py) — the code-host provider layer: which host a repository is on (github / azure-devops / none), the az twin of the gh reads and writes returning the same dict shapes, the Azure DevOps scorecard import, the top-level host block every host-touching --json carries, and the Azure Boards mirror driven by /sprd-ado. references/code-host-providers.md is the contract; references/ado-boards.md is the mirror's
  • hooks/ — PowerShell hooks for session/phase context injection
  • studio/ — the Shepherd desktop app, this plugin's optional Electron front end (Node/TypeScript, its own package.json and test suite). The plugin is fully usable without it, and nothing outside this folder depends on it. The Shepherd desktop app never reimplements plugin logic — it runs the scripts above the same way a person would. Its tests locate the plugin as their parent directory, so they always exercise the checkout they ship in. Every claude call the Shepherd desktop app makes uses flags declared in studio/shared/claudeContract.ts and probed once against claude --help, so an older Claude Code is reported ("lacks <flags> — update") rather than failing silently. Brand assets (mark, tiles, palette, brandbook) live in docs/brand/shepherd/ — no wordmark file ships until its typeface is chosen (brand 4b); the team guides in docs/guide/. Upgrade round 2 (docs/proposals/studio-upgrade-2.md, 2026-10-06) added the Depth mark at hero size and the packaged app icon, readable dark-theme text tokens, one PageHeader/Disclosure kit for every screen, a measured fix for the ghost strip and the Board graph's fit, lit 3D bodies with a viewing reticle and ledger plates, a keyboard scope for the graphs, and sign-off / hand-off / project-open ceremonies that quieten with familiarity — no new dependency, nothing under studio/electron/**, the plugin's protected core (scripts/, phases/, templates/, agents/, commands/, hooks/, profiles/, references/) byte-for-byte untouched — pytest proves it. The command center (docs/proposals/togo-command-center.md, 2026-10-06) reorganised the app around the Build loop — a top band with an omnibar and an SVG lifecycle strip replace the sidebar; a sprint home (lanes · Today · In the room · Refining), planning, an in-place spec card, a close screen and a read-only steering mode draw one main-process CommandCenter read model with per-block provenance, and every write goes through a closed sprint.py argv table with the signed-in person as --by — alongside additive plugin verbs (sprint.py list|log|carry|edit, spec_transition.py confirm-tier|assign, handoff.py --check, spec_readiness.py --all + ladder[], record_findings.py report --json findings[]) that leave every existing verb's text and exit codes byte-identical. See studio/README.md.

Conventions

  • Profile YAML validated against profiles/_schema.yaml
  • Phase definitions use RFC 2119 keywords (MUST/SHOULD/MAY)
  • Scripts use Python 3.12+ via uv
  • State tracked in target project's .sdlc/state.yaml

Design Rule: Standalone or Workflow

Every agent and command is a library part, not just a pipeline stage. Each one MUST be runnable two ways: (1) composed into the phase workflow (reading .sdlc/state.yaml and the intake catalog), and (2) standalone, pointed at arbitrary inputs with no .sdlc/ present (degrade gracefully: provisional IDs, explicit output paths, note the missing context in output headers). When adding a new agent or command, document both modes in its file. discovery-analyst / /sprd-brief --docs <path> is the reference implementation of this pattern.

Key Features

  • Frozen layers — Token-efficient phase summaries in .sdlc/context/layers/ for cross-phase continuity
  • 3-tier context — Foundation (always loaded), frozen layers (per-phase), references (on-demand)
  • 7-gate validation — Integrity, completeness, metrics, compliance, consistency, quality, exit criteria
  • Narrative enhancement — /sprd-enhance generates stakeholder-friendly .narrative.md companions
  • Conversational coaching — /sprd-coach for adaptive guided dialogue through phases
  • Artifact checksums — SHA-256 dirty tracking; gate checks skip unchanged artifacts
  • Smart repair — gate-repair agent auto-fixes structural gate failures (missing templates, placeholders)
  • Dependency enforcement — check_dependencies.py validates section DAG and implementation order
  • Multi-perspective review — /sprd-review with adversarial, edge-case, and council modes
  • Brownfield detection — Auto workspace scan in Phase 0 for existing codebases
  • Structured error specs — Accepts/Returns/Errors required for P0/P1 requirements
  • Cross-artifact validation — File reference checking between markdown artifacts
  • Question-to-file — HITL open questions persisted to open-questions.md for audit trail
  • Test expansion — Healer/Expander/bug-to-test patterns in the Build loop
  • Parallel review resolution — Batch review findings by file in the Build loop
  • Session health check — Opt-in pre-flight build verification at the start of Build-loop sessions (agent-executed, cross-platform)
  • Visual regression testing — Optional screenshot capture during Build-loop E2E with baseline comparison (requires orchestrator integration)
  • Single-change guardrail — Intentionally redundant constraints preventing concurrent spec work in the Build loop
  • Phase-scoped evaluation criteria — evaluation_criteria with optional phases array for non-code artifact quality rubrics
  • Empirical metrics — JSONL logging in check_gates.py, validate_frozen_layer.py, and section-evaluator agent to .sdlc/metrics/
  • Document intake — Opt-in corpus analysis in Phase 0: scans external docs (RFPs, API specs, vendor docs), generates per-doc summaries with DOC-NNN IDs, creates token-budgeted intake index (Tier 1.5), enables Phase 1 requirement-to-source traceability
  • Spec authoring & Definition-of-Ready enforcement — /sprd-spec is the Build-loop Intent beat: new_spec.py scaffolds specs/NNNN-name.md (auto-allocated id, risk tier a first-class frontmatter field) and check_spec.py enforces the DoR — required sections, valid risk tier, scope in and out, no placeholders — plus a vague-line lint that flags acceptance checks likely to fail the vague-line test (advisory). The agent proposes the risk tier; a human confirms it. Runs standalone or in-workflow (logs to .sdlc/metrics/spec-log.jsonl)
  • One-command hand-off (the Delegate beat's tool) — /sprd-handoff / handoff.py turns branch + frontmatter + code-host assignment + starting the agent into one step. It refuses (repository untouched) unless the spec passes the DoR, the developer is in the roster and isn't also the spec's own checker, and the team is under its WIP limit (spec 0003's cadence-plan limits) unless --over-limit "<reason>" is given. Branch creation, the status/developer frontmatter commit, and the push all happen over plain git in a throwaway worktree — the caller's own checkout, dirty or not, is never touched — so the local half still works with no code-host access; only opening the draft PR and assigning/requesting review needs the repository's code-host CLI (gh on GitHub, az on Azure DevOps), and its failure is reported, not fatal. Idempotency is read from the spec's own branch on origin, not the caller's local checkout, since the frontmatter change never reaches a checkout still sitting on main. --open starts Claude Code on the branch in plan mode; without it, the exact command is printed
  • Spec status read from its pull request — /sprd-spec-status / spec_status.py answers "where is this change, and who is it waiting on" straight from the code host: checks, whether the grader ran and its verdict, whether a required security review passed, approvals, and merge — never a status field someone forgot to update. harness/profile/rubrics/grader.md has the grader append a machine-readable ## Acceptance Check Verdicts block to its PR comment (one row per acceptance check, covered/not-covered; the block wins over its own prose on disagreement) — the same ## <Heading> + table convention as /sprd-review's ## Gate Results, now a third producer/consumer pair. waiting_on names the first unmet rung of the checking ladder in one line ("waiting for a non-author approval; requested from @priya-n 2 days ago"). No PR yet reports cleanly, not an error; no code-host access reports what was read locally and says the rest is unavailable — never "not started". The one write it makes, status: merged, lands once the PR merges as a commit straight onto the default branch, built from git plumbing (no checkout — the caller's own working copy is almost always already sitting on that same branch) and idempotent; a protected branch rejecting the push is reported, not fatal
  • Risk tier drives gate depth — risk_model.py is the single source of truth for the risk taxonomy and the checking ladder (every tier blocks on CI + grader + correctness + non-author approval; HIGH adds a security pass and named sign-off; a gated path forces the security pass regardless of tier). check_spec.py enforces a spec's Checking Plan depth against its risk tier, so the tier sets the climb mechanically rather than as a label
  • Spec-backlog tracking (specs are the Build unit) — the Build loop is spec-driven (one spec = one branch = one PR); track_specs.py derives backlog progress (status/risk breakdown, in-flight list, WIP-cap breaches) straight from spec frontmatter, so there is no separate tracker to drift. The section-plan/sections-progress.json model was retired from the Build loop; check_dependencies.py + /deep-plan sections are now Phase-2 design aids that feed the spec backlog
  • Steering scorecard (outcomes, not activity) — scorecard.py records loop outcomes to .sdlc/metrics/loop-events.jsonl and reports the standard's numbers (accepted-as-is, review-wait median, rework/revert, bounce-back, escaped bugs, the DORA four; security-review wait on its own line). Reads "no data" rather than a fabricated zero, and refuses to record the forbidden activity metrics (velocity, story points, PR count, LOC). steering-scorecard.md is the client-facing artifact. scorecard.py import --since <date> builds the same events straight from GitHub's own history via the gh CLI (no stored credential — reuses the machine's existing sign-in): merged PRs, review-wait, deployments and closed incident-labelled issues, deduplicated on GitHub's own ids so re-running an overlapping window is a no-op. github_import.py is the pure mapping layer (gh-shaped dict in, event dict out), kept fully unit-testable with no live calls; hand-recording with record still works unchanged
  • Code-host providers — GitHub (gh) or Azure DevOps (az), chosen by the repository — the code lives on both hosts, so every PR-facing read and write (spec status, hand-off, connection report, branch policies, pipeline evidence, gate credentials, scorecard import) works on either, and no doc or screen may claim gh is required to open a project or read a PR. Two axes, never merged: the code host (PRs, identity, policies) comes from the origin remote; the CI platform (pipeline dirs, runs, secrets) comes from the harness manifest, as doctor.py already reads it — GitHub + Azure Pipelines is legitimate and a mismatch is reported, never resolved silently. Override precedence: --host flag > SHEPHERD_CODE_HOST env (or SDLC_CODE_HOST, the pre-2.0 name, still read) > .sdlc/code-host.yaml (written by set_setting.py code-host, travels with the clone) > the parsed remote > the manifest as a tie-breaker when there is no usable remote > none, which falls through to gh exactly as before. Identity: the roster key stays @handle; people[].email (optional, unique) is the only way an Azure DevOps sign-in (UPN) resolves to a handle — never a guess from a display name. Honesty: cli_state reads unknown when a probe could not run, a value Azure DevOps does not record is null (updatedAt, vote time, request time) or the key is omitted (wait_hours), an unreadable category reads "not imported", never a zero; "unavailable" rides the top-level host block ({name, source, cli, cli_state, detail}) and is never a false no. The Shepherd desktop app's typed-actor rule: when the host cannot say who is signed in (PAT-only az), a typed name is allowed only in that state and every ledger line records accountSource: typed. Protected core untouched: scorecard.py, github_import.py, doctor.py, check_gates.py, check_spec.py, advance_phase.py, phase_model.py, new_spec.py, harness/** are byte-identical; existing GitHub argv is pinned by test_gh_argv_golden.py and no existing test was edited. Fixtures carry provenance: scripts/tests/fixtures/code_host/azure_devops/captured/*.json is real az output captured 2026-10-05 and anonymised (CAPTURE-NOTES.md lists every fact it settled); the hand-written documents above it stay marked hand-written (unverified) and test_fixture_provenance.py lists what is still unverified. See references/code-host-providers.md and docs/proposals/code-host-providers.md
  • Azure Boards mirror (/sprd-ado) — a provenance-carrying projection of the repository onto the client's Azure Boards, driven by ado_sync.py (ado_transport.py is the one az spawner: argv lists only, --detect false always, never a shell). The repository stays the source of truth and .sdlc/ado.yaml is the config. Setup: config init|check; discover saves the board's process to .sdlc/ado/schema.json (null for what was never read, never an empty list); validate prints the fit table — each kind lands as a work item, an area path or a tag, and the plugin recommends process additions and never makes them. Reads: plan (what the board would need, with a 12-hex plan hash), status / pull / drift (ownership-classified drift against an append-only ledger, no data before the first pull, never a fabricated 0), link / adopt, and show with the discussion read as observed content (every quoted line behind a | gutter; --for-agent withholds instruction-shaped lines — the agent quotes, never obeys). Writes (Fold B3), only as a named human: apply --confirm <hash> --by sends the plan a person read — creates in tree order, test /rev-guarded updates, drafted comments — as whoever az is signed in as, and only under posture: assisted (the default is plan-only); an AI or agent --by, a plan-only clone, an effort field, or an az identity that is not --by refuses the whole plan (exit 2, nothing sent). link-pr, iteration bind, posture and comment (which drafts a one-row plan; sending is always apply) share the same --by rule. Settling drift (Fold B4), by a named human: reconcile <ref> --field F --take ado|sdlc|ack|not-drift --by records one disposition on a drift row through the design's state machine (ado_sync_model.DISPOSITION_MACHINE; ack needs --owner, not-drift a --reason, a take the row's class has no word for is exit 2). --take ado only starts the verb that owns the field (spec_transition.py assign, sprint.py slate|unslate, track_decisions.py decide) as an argv list from the per-verb CHILD_ARGV table in ado_sync_reconcile.py (the only subprocess importer besides ado_transport.py; ready/defer take no --by; a child's nonzero exit is exit 1, never 2), and a spec's State is never taken. A disposition clears a row only when it carries what it claims (a child exit 0, an owner, a reason), so a failed child leaves the row open. pull --propose-specs writes one batch file of new_spec/link argument lists for board items nobody mapped and runs nothing. A findings source (ado_sync_sources_df.py, imported by ado_sync.py) projects each review finding in findings-log.jsonl as a work item — CRITICAL/HIGH on the process's BugCategory, title [<category>] <fingerprint>, never finding text in a field — as the opt-in finding kind (plan --kind finding, or finding in kinds:). A decision closed from the board takes the resolution a person typed (--reason); --from-comment <id> only prints a cached comment as a suggestion to type, never fills it in, and never copies it into the decision log. Breadth (Fold B5), each kind opt-in: five more kinds ride the same plan → apply --confirm handshake — nfr (an NFR-xxx row of non-functional-requirements.md, under the Domain whose heading it sits beneath, else no parent), rule (a BR-NN row of business-rules.md, under the one Feature whose brief names it; a (pending DL-NN) outcome names that decision as its predecessor), question (a Q-NN entry of question-list.md; whether it was answered is read, the answer's words are not), lifecycle (one item for the phase, gate and sign-off table of state.yaml; a phase with no gate reads no gate recorded) and measurement (a threshold row of a success-criteria.md dimension, under its Outcome, only on a type types.measurement pins). ado_sync_sources_p1.py registers them into SOURCES at import, none is in the starter kinds: (plan --kind nfr, or the kind in kinds:), and each one's tag is its ref: sdlc:nfr:<id>, sdlc:rule:<NN>, sdlc:question:<NN>, sdlc:lifecycle:project, sdlc:measurement:<n>-<slug>. boards_contract.KINDS has 15 members ending nfr (a leaf, so TREE_ORDER is unchanged). ado_wit_feed.py (read-only) pages the reporting/workItemRevisions feed (WIT_ROUTES["workitemrevisions"], verified: False until the B0 capture) from an explicit watermark whose kind the string names — token:<continuation token> or since:<whole UTC time>, never inferred from its shape — kept in .sdlc/ado/local/watermark by save_watermark once the caller has used the rows; a watermark never saved reads None, never 0 (the read then starts at FEED_START, named, not omitted); the first call names whichever the watermark is, every later call only the previous page's continuationToken, never both; and pull does not read through it yet. reconcile --take ado on a board edit to a co-owned text field writes <artifact>.proposed beside the artifact (design D32) — the board's words, or with --from-comment <id> that cached comment's, spliced into the field's own byte span through document_shape.write_document with the line endings kept — and never writes the artifact; a named person lands it through /sprd-revise, and reconcile --json names the draft as proposed / proposed_sha. The shipped targets (ado_sync_reconcile.TEXT_TARGETS) are a requirement's Description and AcceptanceCriteria and an epic's AcceptanceCriteria; every other text field — a title, an NFR's or a rule's text among them — is refused by name (exit 2). A .proposed file can hold a board comment's words, so it belongs in the project's .gitignore, never in a commit. A typed --reason on an ado or sdlc take is ledgered as <text> plus a 12-hex reason_sha, never the words (not-drift and ack, which require the words, keep them as typed). /sprd-retro gains an Azure Boards section (retro_report.py, additive): comment lines that read as instructions per work item against the comments read, refused comment drafts by reason, and the drift-disposition rollup — its boards key and text block appear only when .sdlc/metrics/ado-sync-log.jsonl holds an event the mirror wrote, a block nobody measured reads no data / null, and nothing is keyed by a person. Mentions stay text only until the B0 mention_probe capture (B5-P5 waits for it). Desktop app (Shepherd): a closed AdoVerbRequest of eight verbs (studio/shared/adoVerbArgv.ts, pure and golden-tested) is the only way a renderer reaches ado_sync.py — main builds the argv from that table, --json on every verb, --by <actor> (main's resolved person) last on the four writes, a comment's text as one --message=<text> element, one spawn per verb, never az; the work-item chip, apply dialog and Today rows read status --json through the command center, the renderer never reads .sdlc/ado/**, and no timer mutates a ledger. Fold B5 adds, in the renderer only (nothing under studio/electron/** changed): AdoReconcileDialog — one choice per drifted field (leave open, take the board's, keep the repository's, acknowledge, not drift) beside its diff, a preview of the exact reconcile argument line per field, and one reconcile per chosen field on one Confirm, stopping at the first the plugin did not settle; a result carrying proposed names that file and says the artifact was not changed — and PullNow (Pull now runs pull, Pull discussion pull --discussion, neither on a timer; an exit 0 carrying an unavailable board says so in the plugin's words, never "Done"). The spec card gains a Boards row (Pull now, Plan changes, Settle drift once a pull read drift for that spec) and mounts the Discussion block below its facts rail. Fold B6 — the catch-up, and a plan from the desktop: sync is the catch-up in one read — the fields pull, drift classified from that pull's own rows (the board is not read a second time), the discussion pull only on --discussion (sync has no --full and no --by), then the plan — exit 0 on every path but a pull-stage ledger DRIFT (1), and ado_sync_model.SYNC_NODATA (counts and next null, never a 0) when nothing could be read; comment, plan and sync end with the one footer ado_sync_model.apply_next_line(<hash>) wherever an apply is offered (plan --json carries it as next beside apply_offered; a plan-only clone prints none). The catch-up is ask-first: /sprd-refine and /sprd-sprint print the ledger's Board as of <as_of> and run sync only on a person's yes, adding --discussion only on their own ask; /sprd-sprint and /sprd-handoff print the board's pending count after a write and never run apply. The closed AdoVerbRequest table is now eight verbs, the eighth (plan) a read with no --by, and no later Fold widens it without a build-kit amendment (K28): Plan changes runs plan for that spec on the person's click and offers Review plan → the apply dialog → Confirm (apply) on exactly the plan it printed; Catch up in the Today column is pull on a click and Pull discussion stays its own click; the Boards nudge after a person's own write spawns nothing until clicked. Nothing reads the board on project open: the open path and the command center spawn status only (gate G18, studio/test/unit/noOpenAdoSpawn.test.ts). Steering mode draws the chip and the read-only discussion rows and none of these controls. Nothing is silenced, bypassed or deleted, no effort field is written, nothing under specs/ or state.yaml changes, and the committed ledger never holds a UPN, e-mail, GUID, display name read from the board or comment text — only roster @handles (by, executed_as; a --by off the roster is kept as typed) and SHA-256 prefixes. The protected core (check_spec.py, check_gates.py, scorecard.py, sprint.py, sprint_model.py, spec_transition.py, ado_map.py, ado_import.py, code_host.py, host_report.py, phase-registry.yaml) is byte-for-byte unchanged. See references/ado-boards.md and docs/proposals/ado-boards-mirror.md
  • Finding memory & disposition tracking (Context Repair, Increment A) — /sprd-review now writes a machine-readable ## Gate Results block (id/category/severity/target/disposition/detail) that record_findings.py parses into an append-only ledger .sdlc/metrics/findings-log.jsonl, so a finding survives the report being overwritten and its disposition is tracked across rounds. findings_model.py is the single source of truth for the severity↔gate mapping and the disposition state machine (FIXED/SPLIT/ACCEPTED_RISK/POSTPONED/OPEN) with honest counting — a mislabeled disposition (SPLIT without id+owner, an AI signing ACCEPTED_RISK) still counts as debt. The review stays advisory (the grader advises, never blocks); only the FIXED-claim check may block (report --strict exits 2) — a finding marked FIXED whose target file never changed is a factual false claim, not a judgment. The ledger is what later makes a recurring finding promotable into a permanent check (Phase C: "findings become new checks"). Runs standalone (--repo) or in-workflow (--state). See docs/proposals/context-repair-loop.md
  • Multi-discipline channel layer — an additive layer giving Data, Design, and Bizreq first-class seats and organizing a feature around its customer channel of use. channels/ holds schema-guarded YAML descriptors (ag-ui, voice, chat) whose acceptance dimensions ride a spec's existing ## Acceptance Checks; validate_channel.py guards the library and check_channel.py is an advisory (exit 0) DoR lint that never changes a ready/not-ready verdict. Six commands (/sprd-feature, /sprd-experience, /sprd-data, /sprd-rules, /sprd-channel, /sprd-evals, /sprd-harness, /sprd-upgrade) drive five interview-driven discipline agents (feature-architect, visual-designer, conversation-designer, data-analyst, bizreq-analyst) that also serve as /sprd-review council lenses (7 viewpoints). A spec's optional channel: frontmatter field binds the surface; a phase-spanning .sdlc/decision-log.md (owner + 2-business-day clock, surfaced in /sprd-status via track_decisions.py) and optional discipline sign-offs on the state sign-off record round it out. The protected core (check_spec.py, check_gates.py, section-evaluator, harness/**, phase_model.py, phase-registry.yaml, /sprd-coach, /sprd-spec) is byte-for-byte unchanged. See references/channel-model.md and references/team-model.md
  • Close handoff-report generation — Phase C Step 4 ("Hand over the record") is a two-pass draft: generate_handoff_report.py does the deterministic assembly first — phase report index, per-phase gate/sign-off table (from state.yaml), metrics history (reusing scorecard.py), spec backlog (reusing track_specs.py) — filling the existing final-handoff-report.md template and marking the judgment sections (outcomes vs the Phase 0 statement, debt log, open items, dashboard handover) with [Fill: ...] slots for the Explore agent to enrich. Honest by design (missing data reads "no data", never a fabricated zero) and refuses to clobber a human-edited report without --force. Runs standalone (--repo) or in-workflow (--state)
  • Artifact update & audit — an additive, advisory layer for changing a pre-Build artifact after the fact and auditing the trail. artifact_model.py owns the change-ledger entry shape and the staleness disposition state machine (OPEN → REFRESHED | ACKNOWLEDGED | NOT_AFFECTED) with honest counting (ACKNOWLEDGED needs an owner, NOT_AFFECTED a reason, or it still counts as debt); artifact_lineage.py harvests declared upstream → downstream edges (frozen-layer source_artifacts, id references, explicit markdown paths) with a labeled coarse phase-order fallback and cycle-safe traversal. audit_artifacts.py (record / impact / report, exit 0 always) records changes to an append-only .sdlc/metrics/artifact-log.jsonl — its own JSONL, never inside gate_results, so /sprd-audit output is byte-identical with or without it — and flags a downstream as a stale candidate when an upstream changed after it last did. /sprd-revise <id|file#section> is the PM-facing write path (discipline agent proposes, named human decides; records the why to the ledger and a linked DL-NN decision-log item; re-gates; shows impact to disposition). /sprd-audit-artifacts is the read-only sibling to /sprd-audit (freshness dashboard, --impact, --history). A record --scan step in /sprd-next captures direct edits at advance time; /sprd-status surfaces a stale rollup. The protected core (incl. advance_phase.py) is byte-for-byte unchanged. See references/artifact-lifecycle.md
  • Artifact versioning & draft+confirm auto-refresh — an additive, advisory package built on the artifact-update-audit layer that (1) gives every pre-Build artifact a content history (diff/rollback) and (2) back-propagates a merged spec's shipped reality up into requirements.md/epics.md/feature-brief.md/business-rules.md. One new pure module version_model.py derives the ordinal-keyed version list v1..vN straight from the change-ledger's existing 16-hex SHA-256 hashes — the content-addressed object store (.sdlc/versions/objects/<xx>/<16hex>) is just those hashes rehydrated to bytes, so there is no second index to drift (a dup-hash rollback is its own ordinal, rendered "restored from vX"; a pre-existing file with no ledger entry synthesizes a v1 baseline, never a crash). The version + refresh verbs fold into audit_artifacts.py (the sanctioned extend target; its existing record/impact/report output stays byte-identical) so one best-effort capture() seam keeps content↔hash lockstep. /sprd-version {list,show,diff,rollback,gc} is the read/undo path (rollback is preview→named-human-confirm, append-only, --ack-signoff for signed-off artifacts, refuses on an uncaptured version; gc is cross-ledger refcounted and protects unknown sign-offs). /sprd-refresh {detect,scan,draft,apply,reject,status} is the reverse-propagation path: review-first / divergence-aware (a faithful spec drafts nothing without --draft), the discipline agent edits only a .proposed, a named human echoes the reviewed diffhash to apply (One Rule), and the refresh lands as a rollback-able refreshed version attributed to the spec via a source_spec rider key (artifact_model.py unchanged). Both write nothing to state.yaml (re-gating stays the command layer's /sprd-gate job) and exit 0 on every path; the object store and .proposed drafts are gitignored local safety nets (a fresh clone/CI degrades to "content not captured", documented override to commit). The protected core is byte-for-byte unchanged. See references/artifact-versioning.md
  • Cross-ledger retro roll-up — retro_report.py (surfaced as /sprd-retro) is the read-only report that turns the accumulated ledgers into retro input: recurring findings grouped by category+target across review rounds (candidates for a permanent check — the "findings become new checks" feed), repeat-stale artifacts, the refresh funnel per merged spec and per upstream stem (detected → drifted → refreshed → rejected → open — the tuning signal for the divergence heuristic), and a combined disposition-debt rollup naming each source ledger. Same discipline as the scorecard: "no data" over fabricated zeros, patterns keyed by category/artifact/stem and never by actor (no ranking flag exists), refuses activity metrics, writes nothing, exit 0 on every path, dual-mode --repo/--state
  • Command contract lint — scripts/tests/test_command_contracts.py is the static half of the command-prose safety harness: it extracts every uv run … scripts/<name>.py invocation from fenced blocks in commands/*.md and validates script existence, subcommand chains, and every --flag against live --help output (cached subprocesses), plus agent-name and references//templates/ path cross-references. Conservative by design (ambiguous → skipped, never a false violation; intentional exceptions live in an explained allowlist) with self-tests proving each detection class fires. Live "does-the-model-follow-the-doc" evals remain out of scope
  • Template shapes — documents read and written without losing anything — a machine-readable <name>.shape.yaml beside a template (templates/_shape-schema.yaml is the format; scripts/validate_shape.py enforces it, every error naming its line) declares that template's sections and fields (label, a fixed type, required, guidance). scripts/document_shape.py reads and writes real documents against a shape as exact byte spans into the original text — never a parse-and-regenerate model — so a document round-trips byte-for-byte when nothing changed and a real edit touches only the bytes it targets; nothing recognized is ever silently dropped (every byte lands in some block, proven by a tiling test). A document reads as free text only when NO section is recognized; one that lacks a required section still reads as sections, with the gap in warnings (read contract 3), and a section whose fields are all optional may be absent silently, and a ## section a person added that the shape doesn't declare reads as an editable custom section of its own (one whole-body field) rather than raw text. scripts/tests/test_shapes_cover_templates.py fails when a template gains a section its shape doesn't declare. A repeating block (requirements.md's ### FR-001, ### FR-002, ...) gets its next number by scanning the whole document, including free text, so a number is never reused. scripts/check_document_completeness.py is a new, separate, always-exit-0 advisory check that reports a required field absent or empty — including a required section deleted outright, which the existing string-only Gate 2 scan can't see. check_gates.py and the rest of the protected core are byte-for-byte untouched; whether this new check ever gates anything is a later, deliberate decision. Shapes ship for the 28 templates that already carry a <!-- REQUIRED: --> marker, plus foundation-report and build-handoff — the two Foundation documents authored through Shepherd's chat, where a missing shape meant a proposed write could never land ("no usable shape"); the rest are unaffected. Shepherd starts a not-yet-created document from its template on the first accepted proposal (ensureDocumentFromTemplate). See docs/templates-artifacts.md §1
  • document_shape_cli.py — a subprocess surface over the shape library (Shepherd spec 0009 prerequisite) — document_shape.py is a pure library with no CLI; Shepherd (the desktop app) is a separate TypeScript process that can only call it as a program. The wrapper adds no judgment of its own, only serialization: read --doc --shape emits the library's block model as JSON, and write --doc --updates applies [start, end, new_text] span replacements — both converting between the library's Python-string (code-point) offsets and the UTF-8 byte offsets a Node caller actually needs to slice a decoded string correctly (these templates are full of em dashes and curly quotes, so the two diverge in practice, not just in theory). A byte offset that doesn't land on an exact character boundary is refused with a clear error rather than silently misresolving. write takes no --shape — span replacement doesn't consult it. Both commands open files with newline="", preserving the library's byte-identical round-trip guarantee. Exit 0/1 (not the rest of this system's advisory always-0), since this is a mechanical serialization layer, not a judgment call. add-row (spec 0020) appends ONE row to a markdown table, as a single exact span through write_document() so every other byte (line endings included) is untouched: addressed by shape (--shape --section --field, a table-typed field, its first table) or by position with no shape at all (--table-index N, code fences skipped). The second form exists because business-rules.md, golden-scenarios.md and decision-log.md are a preamble plus one table with no ## heading — a shape can only anchor to a ## heading, and real projects' copies have none either — so they cannot be shaped and are appended to by position instead. --cells (or --cells-file, to avoid shell quoting) maps column header → value; --id-column/--id-pattern "BR-%02d" allocates the next free id by scanning the whole document (prose included, so an id is never reused) and a width is a minimum (BR-%02d reads BR-100 whole — pattern_to_regex once read it as BR-10 and issued BR-100 twice). It removes nothing by default: --replace-placeholders clears template stand-in rows (more than half the non-empty cells hold a [bracketed] or <angle> span; links and HTML tags are content) and only from a table made entirely of them — a table with any real row loses nothing, because deleting a person's row on a heuristic is the failure this verb must not have (the first version did, caught by the correctness review of its PR); ids in rows that were not removed still count as used. Every refusal exits 1 with the document unchanged. Spec 0020 also adds shapes for 14 discipline templates (workshop brief, contradiction/question lists, document registry/summary, feature brief, user stories, the data and experience documents, spike) and a shared templates/review-report.md (its ## Gate Results table starts empty, so add-row builds a report finding by finding and record_findings.py ingests it); test_shapes_cover_templates.py and test_shaped_templates_roundtrip.py cover them automatically.
  • stage_readiness.py — "what does this stage still need", in one read-only call (spec 0010) — composes the stage's required artifacts and its plain-language exit conditions (via phase_model), completeness findings (via check_document_completeness's pure check_document), and sign-off state read straight from state.yaml. Two deliberate choices: it never calls check_gates.py, which appends to gate-log.jsonl on every invocation with no suppression flag — a UI polling readiness would turn the gate log into a record of how often someone had a window open (a test pins that this script writes nothing); and it resolves shapes by path (.sdlc/artifacts/<phase>/<name>.md ↔ templates/phases/<phase>/<name>.shape.yaml) rather than by the template stamp, because nothing in the pipeline ever writes a stamp, so a stamp-based scan silently reports a clean bill of health for a document full of holes. Always exit 0, dual --repo/--state, --json
  • record_draft.py — the AI-draft audit ledger (spec 0010's resolved Decision List) — a discarded draft is still recorded, with its outcome (accepted / edited / discarded), to its own append-only .sdlc/metrics/draft-log.jsonl. Deliberately NOT the document's version history: that history records what the document actually says, and filling it with rejected text would make it unreadable for its real purpose. report answers "how much was drafted, and how much of it did people keep" — "no data" over a fabricated zero, and never keyed by actor
  • sign_off_confirmations.py / confirmation_hints.py — ticking the sign-off questions, with a pre-check beside each — an exit gate's plain-language questions ("Scope boundaries are unambiguous") are a person's to answer. sign_off_confirmations.py (confirm / withdraw / status) records each tick — who, when — to its own append-only .sdlc/metrics/confirmation-log.jsonl, never into state.yaml (advance_phase.py owns that and is unchanged). The registry gives questions no ids, so an id is a hash of the phase and the wording: reword a question and its old ticks stop counting. confirmation_hints.py computes what a check can see — out-of-scope items present, success dimensions with thresholds and a named source, personas with every column filled, project_type recorded, AQ-NN items, open decisions missing an owner or due date — as looks_met / not_yet / judgement. A hint is a pre-check, never a verdict: it never ticks a box, and every question without an evaluator says "needs your judgement". A test requires each evaluator's matching phrase to hit exactly one real registry question, so a reworded question fails loudly. stage_readiness.py --json carries the result as judgement; /sprd-next reads the record and will not ask for sign-off while a question is unconfirmed (enforced in the command's instructions, pinned by a test — not in the protected advance script)
  • approval_settings.py — a minimal per-stage approval toggle (shipped early, on spec 0010's behalf) — Shepherd's repository-sync spec (0009) needs to know whether a stage's documents require a named person's approval before merging; that setting properly belongs to Shepherd's document-editor spec (0010), which doesn't exist yet. This is the small, additive piece both will share: .sdlc/approval-settings.yaml (stages: [{stage, approval_required, approver}]), validated the same way validate_team.py validates a roster — every error names its stage index, a missing file (or a stage absent from it) means "approval not required," and a named approver is cross-checked against the real team roster (.sdlc/team.yaml) when one exists.
  • spec_readiness.py — "is this spec ready to start", grouped for a reader (Shepherd spec 0011) — a thin read-only wrapper around the protected check_spec.check_spec_text; it owns no judgement of its own, only the grouping into blocking / advisory / already-passed that a screen needs. The Definition of Ready stays in check_spec.py, which means a spec edited by hand outside Shepherd is held to exactly the same bar. Always exit 0, --json
  • spec_transition.py — the three state changes a spec can make (Shepherd spec 0011) — ready (draft → ready, refused while check_spec.py still blocks), risk (re-tier, a raise needs nothing, a lower needs a named person — --authorised-by), and defer (with a reason the script refuses to accept empty or token). Writes through a targeted frontmatter edit (set_frontmatter_field), never a regenerate, so comments and hand-written ordering survive. _tier_rank explicitly inverts risk_model.RISK_TIERS, which is declared most-risky-first — a plain index() ranks it backwards, and that bug shipped once and was caught by running it rather than reading it
  • spec_status.py --all — the whole backlog in one pass (Shepherd spec 0011) — the per-spec path stays as it was; --all adds a bulk mode that fetches every pull request in a single code-host call (gh or az, by the repository's remote) rather than one per spec (measured 77–89 ms on a 200-spec backlog against N sequential round-trips), and unlike the per-spec path it never writes. waiting_on_handle(pr) returns a structured handle from the review data, never a prose match on a rendered status line
  • project_settings.py / set_setting.py — the settings a project actually has, and the only supported way to change them — project_settings.py composes the roster, the per-team work-in-progress limits and the per-stage approval settings into one read, each section carrying the file path it came from so a person can always go edit the real thing; its FIXED_RULES list names the rule and the script that enforced_by it, because a rule shown as a setting implies it can be turned off. set_setting.py (person / limit / approval) makes targeted text edits — an early version regenerated the roster YAML and destroyed all nine of its comments, so the tests now assert every original line survives an edit
  • connection_report.py / gate_inventory.py — is this project actually wired up — six three-valued checks (yes / no / unknown — the third value is the point: "could not determine" is never reported as "no"), with expected_workflows() read from harness/workflows/*.yml rather than hard-coded, and a NOT_UNIVERSALLY_EXPECTED map carrying a reason for each thing whose absence is not a fault. gate_inventory.py parses the gate table out of .github/RAILS.md (a project's own copy wins over the playbook's) and reports each gate as installed / missing / not_a_pipeline
  • foundation_summary.py — what the pre-Build phases decided, for somebody arriving late — reads the Phase 3 artifact set through phase_model + the artifacts model (never a hard-coded path list) and pulls each document's top-level sections via the shape library's find_heading_spans, so it degrades to headings rather than failing on a document that has drifted from its template
  • declare_complete.py — may Build be declared finished (Shepherd spec 0014) — assess() / declare(), reporting every blocker at once rather than the first, each one carrying its items: undecided specs (naming who is building each), deferrals with no reason recorded (its own blocker — the decision was made, the why was lost), specs with no team, and teams that have not confirmed their own list. "unassigned" is explicitly not a team, or the flow would request a confirmation from nobody. It deliberately does not advance the phase — advance_phase.py is protected core and already owns that transition with its own gate checks and sign-off recording
  • pipeline_proof.py / pipeline_proof_model.py — which delivery rails have actually fired (Foundation Step 6; Shepherd's "Gather pipeline evidence" button) — a rail that has only ever been green has been assumed, not tested, and Foundation's exit asks whether each was proven by forced failure. pipeline_proof.py reads GitHub's own history through gh (the live branch ruleset against the checked-in one, merged PRs split at the moment the ruleset went live, every rail's workflow runs) and writes .sdlc/artifacts/03-foundation/pipeline-proof.md; pipeline_proof_model.py owns the pure classification: PROVEN (a red run on a PR that did not merge red — fixed later or closed unmerged), RAN_UNPROVEN, NEVER_FIRED, BROKEN (the grader is built to always conclude successfully, so a red run means it failed), and NO_DATA (a local hook like the Stop gate, or a history that could not be read — never a fabricated zero or a "never fired"). Read-only by construction: a test pins the exact set of gh calls (repo view, pr list/view, run list/view, api GET on rulesets); the forced failures that would prove a rail each open a real PR, so they are only ever listed under "Proofs still needed". Re-running rewrites only the gathered sections; the person's Forced-failure proofs section is never touched, and CRLF is preserved. Reuses gate_inventory.inventory() for the rail list and spec_status's verdict parser; github_import.run_gh now decodes gh output as UTF-8 (found by running this against a real repository: cp1252 crashed on an emoji in a PR comment). Exit 0 on every path (ok: false + error), dual --repo/--state, --json, --write
  • Script --json modes, decision verbs and channel binding (spec 0021) — Shepherd's buttons run a script and show its result, which needs the result as data and, for two commands, a script where there was only prose. Every --json mode prints exactly ONE JSON document and nothing else, keeps the script's exit codes, and leaves output WITHOUT the flag byte-identical (pinned by golden captures from the unmodified scripts in scripts/tests/fixtures/golden/, taken via scripts/tests/golden_support.py, which forces UTF-8 because several of these print an em dash and the Windows pipe default is cp1252). doctor.py --json → checks[{name,status PASS|FAIL|WARN,detail,fix}], summary, harness_installed, and skipped (what --offline left out, so a missing check is not read as a clean one). audit_gates.py gains --repo (dual mode) and --json (enough_data is false below MIN_PHASES, and with no gate results the list is empty, never zeros; --compare with --json is refused). upgrade_harness.py --json is dry-run only — --json with --apply exits 2, because applying rewrites CI workflows and hooks Shepherd can neither commit nor review. check_channel.py --json reports per-dimension coverage; --interaction-spec PATH [--channel C] instead asks whether a channel-interaction-spec.md has a real (non-placeholder) contract row for every dimension of its channel. new_spec.py/new_spike.py --json return the new file's path and id. track_decisions.py open|decide own the decision clock: open allocates the next DL-NN (the whole log scanned, so an id is never reused), writes opened = today and due = two business days on via the script's own add_business_days, and creates the log from the template when absent; decide sets the status to decided and records the decider and resolution in the row — both targeted edits, every other byte and the line endings untouched; with no verb the script behaves exactly as before. intake_documents.py gains --repo, --docs (standalone: with no .sdlc/ every id is provisional and nothing is written), --json, --skip, --priority and --lock; a locked catalog refuses --rescan/--skip/--priority so DOC-NNN ids stay stable (--registry, which writes the registry and token-budgeted index, is deliberately left to the intake spec). bind_channel.py is the mechanical half of /sprd-channel: sets the spec's channel:, seeds harness_context from the descriptor only when empty, and appends one (channel: <dimension>) line per uncovered dimension to ## Acceptance Checks (the interaction spec's acceptance-check cell when it has a real one, else the descriptor's example_check); edits nothing else, is idempotent, refuses a different channel than the one bound, and only REPORTS the channel's risk floor — raising the tier stays spec_transition.py risk after a person confirms
  • phases/activities.yaml / activities_model.py — what a person can do in each stage (spec 0023) — a declaration, beyond the documents the registry requires, of the commands a stage offers (run, create, check, draft, talk), so Shepherd (the desktop app) draws the list from the plugin instead of keeping its own and a command added here appears there with no Shepherd change. It lives in its own file because phase-registry.yaml and phase_model.py are protected core. Status is computed, never stored, with one fixed precedence: done if its done_when holds (even if a requirement has since become unmet), else blocked with a plain-language reason, else available; an activity with no done_when is never done. Activities are optional and never gate a phase. validate() names the phase, activity and rule for every error (unknown command, creates with no template, after/requires naming nothing); evaluation only looks at files and the profile — it never runs a script or a model
  • stage_readiness.py --json gains activities and definition; generate_status.py --json gains capabilities (spec 0023) — activities is the stage's list with each status, definition is the stage's definition file. A broken declaration degrades to no activities plus a warnings entry rather than failing the report (the documents and sign-off are still true); a healthy run has no warnings key. capabilities.py is a declared list of what this plugin can do (add-row, bind-channel, pipeline-proof, ...), so a newer Shepherd desktop app can tell an older plugin from a current one and hide a button it cannot honour. Declared rather than probed because probing would run --help on a dozen scripts on every dashboard load; test_capabilities.py is what keeps the list honest by running each script's real --help and requiring every listed flag. A capability whose script is absent is omitted, never reported
  • Discipline scripts — the deterministic halves of four commands (spec 0022) — each does only the mechanical part of a command and leaves the judgement to the agent and a named person. All four run standalone (--repo/--doc) or in-workflow (--state), are advisory (exit 0; a usage error is exit 2), and read "no data" rather than a fabricated zero. --json prints exactly one document.
    • workshop_brief.py build (/sprd-brief) — assembles workshop-brief.md from the chosen contradictions, questions, decisions, claims and load-bearing documents. Refuses (exit 1) on a missing required input, a missing registry or placeholders left unfilled; caps the page at 5 contradictions and 12 questions; pre-workshop questions go to emailed_instead and a question routed interview is refused. total is null, never 0, when a source document is absent
    • rules_check.py (/sprd-rules) — checks business-rules.md against golden-scenarios.md: a rule with no scenario, a scenario naming no rule, a missing source or approver, a pending rule with no decision-log item. Findings are SHOULD severity and never block; logs to .sdlc/metrics/rules-log.jsonl in workflow mode
    • data_contract.py summary (/sprd-data) — counts the Fields table's PII classification. yes and customer-linked (the template's indirectly identifying value) both count as PII, with indirect_fields naming the second kind, so a contract is never reassured that it holds less personal data than it does. When any field is PII it quotes the personal-or-client-data line of risk_model.TAXONOMY, because PII is a risk-tier driver. Columns are found by header text, never position
    • narrative_status.py (/sprd-enhance) — which artifacts have a .narrative.md companion and whether it is stale (git commit time of the artifact against its companion). --phase and --all-phases together is exit 2; coverage is always present and reads {0,0} with has_data: false
  • Shepherd draws the plugin's activities (spec 0024) — the Workflow tab gains an "Also in this stage" list built from stage_readiness.py --json's activities, so a command added to phases/activities.yaml appears with no Shepherd list to update. The plugin declares what an activity is; Shepherd gives each kind one control: create starts every file the activity declares from its template through ensureDocumentFromTemplate (extended to nested paths and the shared review-report.md; it never overwrites, and says which files were created and which already existed), check runs one of two scripts and renders plain-language rows (rules-check → rules_check.py --repo --json, data-check → data_contract.py summary --repo --json; a table in studio/shared/activityControls.ts because the plugin doesn't say which script produces a check's result), talk sends one [Studio]-prefixed turn through the existing stage chat (buildSystemPrompt gains one sentence; CHAT_TOOLS is untouched). Run and draft activities are not drawn yet: a row with no control is a promise the tab cannot keep, so they appear only in the new Guide tab, which renders the stage's phase definition file and names every declared activity by label and slash command. A check that had nothing to read says "Nothing to check yet", never "No problems found" or a zero count. An older plugin that emits no activities key leaves the tab exactly as it was; a control whose capability the plugin does not list (generate_status.py --json's capabilities) is disabled with a reason instead of failing. New IPC lives in studio/electron/main/activities.ts, not index.ts
  • generate_phase_report.py --json and the --all repair (spec 0025) — the phase report prints its result as one JSON document (phase, phase_name, output, found, missing, total, exit_criteria, artifacts; --all prints {reports, index}; paths forward-slashed) so Shepherd's "Export this stage's report" can say what was written. Text output is pinned by golden captures from the unmodified script. --all used to crash at phase 7 (the registry declares some artifacts as {path, root, required_for} mappings and the report read every entry as a filename); phase_artifacts now reads the registry through phase_model.required_artifacts. A type-specific artifact is listed, not dropped, since no project type is known there
  • Shepherd's four model-free panels (spec 0026) — phase-report, intake, enhance and review are run/draft activities whose deterministic part Shepherd can show, so they are keyed by activity id in studio/shared/activityControls.ts (PANEL_CONTROLS, each naming the plugin capability it needs) rather than by kind. A blocked row shows the plugin's reason and no panel; a done row keeps its panel (a locked intake still shows its frozen catalogue). Phase report runs generate_phase_report.py --json (single stage or --all), says "Report written: 3 of 5 documents present" with the missing names — never "complete" while any are missing — and opens the file only from inside <project>/.sdlc/reports/ (path-checked, symlink-checked, .html only); reports stay local, .sdlc/reports/ is not on the sync allowlist. Intake has an explicit Catalogue the documents button because intake_documents.py writes catalog.json the first time it runs, so reading on open would create a file by looking; skip is additive and final (the script has no un-skip), priority sends the full order, and Lock these ids asks first and then freezes every control. .sdlc/context/intake/ joins the sync allowlist (the catalogue and summaries other people need). Enhance (narrative_status.py) and Review (record_findings.py report --json, plus --strict, where exit 2 is a result — a finding marked fixed whose file never changed — not an error) are read-only until the model-run buttons arrive. Every call is fixed argv; ids from the renderer are validated before they reach it. The stock profiles ship documentation: commented out, so intake stays blocked until a project sets intake_path
  • Shepherd's model runner and Keep / Discard step (spec 0027, HIGH) — the first Shepherd desktop-app feature that starts a model run which READS the project, so studio/electron/main/agentRun.ts is the single place that says what Claude may do. Documents can carry text from a vendor or a customer, so four rules each stand alone: read-only tools (--tools/--allowedTools Read,Grep,Glob; no Bash, Edit, Write, Task, web tool or MCP server; --tools is intersected with the agent's own list, so the summary agent runs with Read and Glob), an isolated working directory (Shepherd's empty one — project settings and hooks are discovered from the cwd; the project is reached only through --add-dir), a fixed agent table (enhance → narrative-enhancer, review → multi-reviewer; the renderer sends a kind, never an agent name), and no document text in the prompt (paths and an instruction only; the model reads the files itself). -p -- <prompt> must stay LAST in the argument list: a flag after -- is silently ignored (reproduced twice). The result comes from the CLI's own final result line (is_error, result, total_cost_usd); an absent cost is omitted, never shown as $0.00. One job at a time; Cancel kills the process tree (taskkill /T /F on Windows) through a new signal on runCommand. Nothing is written until Keep, which writes the candidate through an audited whole-document write (allowlist + resolveProjectDocument + temp-file-and-rename), first capturing any file it replaces in the version history and refusing to replace one whose old text it cannot confirm captured; records created/revised (audit_artifacts.py has no drafted event) and accepted in the draft ledger; a review Keep also runs record_findings.py record. Discard writes nothing and records discarded. Summaries never mention velocity, story points, PR counts, lines of code or hours spent. No automated test calls a live model
  • intake_documents.py --registry — the registry and the session-start index (spec 0028) — step 6 of /sprd-intake is bookkeeping once the catalogue and summaries exist, so it is a script call (scripts/intake_registry.py holds the logic). It reads the catalogue (never writes it) and the DOC-NNN-*.md summaries, then writes document-registry.md and .sdlc/context/intake/index.md. Registry: corpus metrics from the catalogue, one row per document in priority-then-id order with a summary link and Key Topics taken from the summary's Scope line (80 characters, no cleverer); a missing summary — or one still holding ${...} placeholders — reads "(not yet summarised)" and is listed under missing_summaries; skipped documents stay in the registry marked "(skipped)" and leave the index. An existing registry is edited in place: only the two gathered sections and the Generated: line change, every other byte (hand-written Topic Clusters, the Cross-Reference Map, the file's LF/CRLF) is kept, and a lost heading is a warning, not a failure. Topic clusters and the cross-reference map are judgment and are never generated. The index fits index_budget_tokens by the command's own order — clusters dropped first, then one-line descriptions shortened to 120/80/40 characters, then removed — and never loses a document id: if the ids alone are over budget it is written in full and index_within_budget is false. --registry cannot be combined with --skip, --priority, --lock, --rescan or --docs (exit 2); with no catalogue it exits 1. --json prints exactly one document
  • Shepherd's batch model jobs for the reference documents (spec 0029, HIGH) — extends spec 0027's runner from one result to many, on exactly the same controls (read-only Read,Grep,Glob, isolated cwd, fixed agent table, no document text in a prompt, one audited write path). Summarise runs document-summarizer once per catalogued, non-skipped document without a filled summary, sequentially in priority_order then id order, for a locked catalogue only; the ids are read from catalog.json by the main process (never sent by the renderer) and the prompt carries a DOC id and the catalogue's path, never a filename, so a vendor's file name cannot carry instructions. Analyse runs discovery-analyst once and must return both lists between exact marker lines (=== FILE: contradiction-list.md ===, === FILE: question-list.md ===, === END ===, once each, in order, no extra === FILE: … ===); prose outside them is discarded (the real agent opens with a sentence of its own — found by running it, which the stand-in tests could not show). A batch asks first (the documents listed, "Claude runs once for each of these N"), shows progress and the sum of the costs the runs themselves reported (never $0.00), and Cancel keeps the candidates already finished. A failed run fails only its document. Keep widens to exactly .sdlc/context/intake/DOC-NNN-<slug>.md and the two analysis files; Keep all / Keep selected / Discard all go through the spec 0027 per-candidate write, and the analysis pair stops after a failed first write and says which was written. State is per project, as in 0027. Write the registry and index runs intake_documents.py --registry --json and uses no model
  • workshop_brief.py candidates — what the brief is chosen from (spec 0031) — the read-only verb beside build (logic in scripts/brief_candidates.py) that reports exactly what a person curates from: every contradiction (id, title, severity, question, both sources with document reference and quoted passage, and recommended), every question (id, question, block, route), the registry's documents, the page's limits (read from the build's own constants: 5 contradictions, 12 questions, 3-5 decisions, 3-5 load-bearing documents), standing_decisions already in the template, existing_brief (so a form asks before overwriting) and provisional_ids. It reuses build's own parsers, so a selection form and the build cannot disagree about what an entry is. recommended is the /sprd-brief curation step's own rule and nothing more (blocks-outcome and shapes-design are pre-ticked); a missing or empty input is has_data: false with a note saying what to run next, never an error or a zero. Writes nothing; exit 0 (usage error 2); build is unchanged
  • Shepherd's workshop brief form (spec 0032) — the Discovery brief activity draws a form instead of a bare Create, keyed by activity id with the brief-candidates capability. It reads workshop_brief.py candidates --json (spec 0031: what the build's own parsers see) and writes through workshop_brief.py build --json; no model runs. Recommended contradictions (blocks-outcome, shapes-design) start ticked and counters show the page's limits (5 / 12 / 3-5 documents / 3-5 decisions counting the template's standing ones); a pre-workshop question is listed under "Email these before the workshop" and cannot go on the page (the main process still passes it so the build reports it as emailed instead), an interview one cannot be ticked ("neither in the room nor emailed"). A claim needs a document from the registry; attendees are free text with an optional roster shortcut (most are the client's people). The renderer is untrusted: buildBrief re-reads the candidates, re-validates every selection (id forms, existence, routes, limits, single-line text, 500/200-character caps, at most 15 claims and 30 attendees so the JSON arguments stay inside a Windows command line) and runs a fixed argument list where selection text travels only inside the --decisions-json / --logistics-json / --claims-json values (verified to round-trip quotes, backslashes, &, % and Unicode on Windows with no shell). A refusal starts no build; replacing an existing brief needs an explicit tick before --force is ever passed. The typed form lives in memory per project so switching tabs does not lose it, and is cleared after a successful build
  • Sprint team layer (/sprd-sprint + /sprd-refine) — an additive, advisory layer that gives the spec backlog a two-week commitment window without a second backlog, a reordering, or a gate. sprint_model.py is the pure single source of truth (mirrors findings_model.py): sprint states planning → ready → closed, parse_mix (by risk tier, "HIGH:1,MEDIUM:2,LOW:3", sum ≤ target — a count, never a size), propose_slate (fills the mix in spec-id order, never exceeds target), the conjunctive ready rule (ready_gaps: DoR READY and status: ready and eng_review: accepted and data_review: accepted|n-a, plus no dependency cycle and no depends_on pointing outside the slate at an unmerged spec — reusing check_dependencies.detect_cycles/topological_sort), the advisory build order (build_order: topological, then unblocks-most, then HIGH→MEDIUM→LOW, then id) and next_up under the WIP cap, outcomes, and the frontmatter writer's refusal rules (set_frontmatter writes ONLY the five keys sprint, next_owner, eng_review, data_review, depends_on; refuses #, quotes, newlines, placeholder tokens; can never write status, which stays hand-moved). sprint.py is the I/O CLI (new slate unslate status handoff ack verdict ready plan close; dual-mode --state|--repo; sprint record .sdlc/sprints/SNN.md from templates/phases/build/sprint.md; append-only ledger .sdlc/metrics/sprint-log.jsonl, frontmatter first then the ledger line, DRIFT + exit 1 if the append fails; reads exit 0 always; writes 0 ok / 1 illegal or gap / 2 refused; only touches ^\d{4}- spec files so the installed specs/spec-template.md is never listed or written). generate_sprint_report.py renders the self-contained sprint-planning page (sprint-SNN-planning.html, written at ready and on demand via plan) and review page (-review.html, at close) with generate_phase_report.md_to_html, splicing an idempotent <!-- sprints:start/end --> block into index.html. track_specs.py gains by_sprint + --sprint SNN (legacy output byte-identical), retro_report.py gains carry-over recurrence (per spec, ≥2 sprints — the only cross-sprint number) and bounce causes, both session-start hook twins print [SHEPHERD-SPRINT] and retire the stale session-handoff.json block. Honest by design: "no data" over fabricated zeros, no per-person aggregation in any JSON, FORBIDDEN_FIELDS = scorecard.FORBIDDEN_TYPES ∪ {points, estimate, effort, hours, capacity} refused with exit 2 and scorecard's wording, an AI --by refused as labelling-not-enforcement. Never writes state.yaml; never checks G1–G7. The protected core (check_spec.py, check_gates.py, advance_phase.py, phase_model.py, phase-registry.yaml, section-evaluator, harness/**, /sprd-coach, /sprd-spec, new_spec.py, scorecard.py, generate_status.py, templates/state-init.yaml) is byte-for-byte unchanged. Shepherd (the desktop app) surfaces the layer read-only: phases/activities.yaml declares a "build": block (sprint run, refine talk, phase-report, coach; refine also under "3"), capabilities.py gains sprint-status / sprint-plan / sprint-report, sprint.py status --json rows carry rel_path and a bad --sprint still prints one JSON document (has_data: false), and the Build › Sprint view (studio/electron/main/sprint.ts, SprintBoard.tsx) renders status --json as-is and opens the planning/review page via generate_sprint_report.py --json — never a write verb, never a 0 for "no data". See references/sprint-model.md, docs/proposals/sprint-team-layer.md and docs/proposals/studio-improvements.md
  • Data specs — a linked spec the data team works to live data (spec 0039) — a data spec is an ordinary spec (one spec = one branch = one PR) whose work is data: marked type: data (the existing type field, never kind:, which already means three other things) plus serves: "<parent id>", owned by the Data discipline, worked through /sprd-data, and done only when the data really flows. data_spec_model.py is the pure source of truth (imports no subprocess, argparse or branch_scan): mark_data, link_parent, cited_refs, render_context, replace_section_body, cycle_after_link, status_row, context_coverage, ledger_event, and set_scalar_line, a targeted line edit that keeps a trailing # comment and CRLF and refuses a newline, # or stray quote. data_spec.py is the I/O CLI (new target status check; dual-mode --state|--repo, --json one document, --help touches no filesystem; reads exit 0 always; writes 0 ok / 1 illegal (missing parent, two files for one id, ledger DRIFT) / 2 refused — a self-link, a data-spec parent, a depends_on cycle, a conflicting serves — with nothing written). new_spec.py is imported (next_spec_id, render_spec, slugify, SPEC_FILE_RE), never edited, so its --json stays exactly {path,id,name,risk}. One call links both ends: the parent's depends_on gains the data id (so sprint_model.build_order builds the data first with no new logic) and its data_review becomes pending from ""/n-a/returned; accepted is kept and the result carries data_review_note — a tool never writes accepted, which stays sprint.py verdict --lane data --verdict accepted --by <named human> (the verb already refuses an AI --by). Exactly those two parent lines change, each keeping its comment. ## Context — one section field in data-spec.shape.yaml, rewritten in place by byte span — lists the parent, a dated read-only copy of its acceptance checks, and every BR-NN/DL-NN/ADR-NNN/DOC-NNN the parent's body cites, each resolved to the Markdown file that defines it (a heading or table row starting with the id) under .sdlc/artifacts, .sdlc, docs, specs — a DOC-NNN looks in .sdlc/context/intake first and a DL-NN in the decision log first, so a summary row never outranks the defining file; one that cannot be found reads not found in this repository, never a guessed path. Done means the data flows: six pre-seeded Acceptance Checks (the pipeline runs end to end; the serving object returns at least one real row; a dictionary row per input citing its profile query; a source → transform → serve path per serving object; smoke tests linked from the handoff; a DL-NN with an owner per readiness gap), each passing check_spec's vague-line lint, so the grader grades them like any acceptance check. check is the early look (context-coverage, readiness-gaps-owned, handoff-present) — advisory, exit 0, passed: null with not measured when its source file is absent, never a pass or a fail; status --json with no data spec is exactly {"has_data": false, "specs": []}, and acceptance_due flags a merged data spec whose parent's data_review is not yet accepted. Three new documents under templates/phases/02-design/data/, each with a shape and a round-trip fixture: data-dictionary.md (a cell nobody measured reads not measured, never 0), decision-memo.md (constraints, options with evidence, a scorecard, a decision a named human makes) and data-handoff.md (what is live, how to call it, degraded results, smoke tests, what is left). Branch-safe ids — branch_scan.py is the only new subprocess importer (argv lists, never a shell, never fetch): track_decisions.py open --all-branches and data_spec.py new --all-branches take the highest DL-NN or spec id on every local ref (refs/heads, refs/remotes) plus one, so two branches cannot both name a decision DL-07; refs are read as of the last fetch (the help says "run git fetch first"), a ref that could not be read is named in unread and never counted, outside a git repository the call proceeds locally with refs_scanned: [], and without the flag open's output is byte-identical (pinned by the goldens). In --state mode one data_spec_linked line (spec ids only, no name) is appended to .sdlc/metrics/spec-log.jsonl; --repo mode appends nothing. /sprd-data loads ## Context before any interview question (step 1a) and the data-analyst agent keeps its tools (Read, Write, Edit, Grep, Glob): it has no shell and queries no platform, so --live and platform packs are a written design only (docs/proposals/data-specs.md §Deferred) — a shell against a client's data platform is the owner's security decision, not a package's. phases/activities.yaml gains a data-spec run activity under build (Phase 2's data activity still creates its original three documents; the new three are a data spec's deliverables); capabilities.py gains data-spec, data-spec-status and decision-open-all-branches. The protected core (new_spec.py, check_spec.py, check_gates.py, advance_phase.py, phase_model.py, sprint.py, sprint_model.py, spec_transition.py, scorecard.py, generate_status.py, handoff.py, spec_status.py, track_specs.py, check_dependencies.py, document_shape.py, data_contract.py, phase-registry.yaml, templates/state-init.yaml, spec.md and its shape, section-evaluator, /sprd-coach, /sprd-spec, harness/**) is byte-for-byte unchanged, and the one existing test edited is the build-phase activities pin in test_activities_model.py, which gained data-spec. See references/data-specs.md and docs/proposals/data-specs.md

Testing

uv run scripts/validate_profile.py profiles/microsoft-enterprise/profile.yaml
uv run scripts/validate_team.py .sdlc/team.yaml
uv run scripts/init_project.py --profile profiles/microsoft-enterprise/profile.yaml --target /tmp/test
uv run scripts/check_gates.py --state /tmp/test/.sdlc/state.yaml --phase 0
uv run scripts/validate_frozen_layer.py --state /tmp/test/.sdlc/state.yaml --phase 0
uv run scripts/track_artifacts.py --state /tmp/test/.sdlc/state.yaml --snapshot
uv run scripts/check_dependencies.py --state /tmp/test/.sdlc/state.yaml
uv run scripts/intake_documents.py --state /tmp/test/.sdlc/state.yaml
uv run scripts/new_spec.py --repo /tmp/test --name "duplicate claim 409" --risk HIGH
uv run scripts/check_spec.py --spec /tmp/test/specs/0001-duplicate-claim-409.md
uv run scripts/track_specs.py --state /tmp/test/.sdlc/state.yaml
uv run scripts/scorecard.py record --state /tmp/test/.sdlc/state.yaml --type spec_merged --field accepted_as_is=true
uv run scripts/scorecard.py report --state /tmp/test/.sdlc/state.yaml --window-days 14
uv run scripts/scorecard.py import --repo /tmp/test --since 2026-09-01
uv run scripts/code_host.py --repo /tmp/test --json
uv run scripts/import_outcomes.py --repo /tmp/test --since 2026-09-01
uv run scripts/spec_status.py --repo /tmp/test --all --host azure-devops
uv run scripts/set_setting.py --repo /tmp/test code-host --host azure-devops
uv run scripts/handoff.py --repo /tmp/test --spec /tmp/test/specs/0007-name.md --developer @sam-k
uv run scripts/spec_status.py --repo /tmp/test --spec /tmp/test/specs/0007-name.md
uv run scripts/generate_handoff_report.py --state /tmp/test/.sdlc/state.yaml
uv run scripts/record_findings.py record --report /tmp/test/.sdlc/artifacts/02-design/review-report.md --repo /tmp/test
uv run scripts/record_findings.py report --repo /tmp/test --strict
uv run scripts/audit_artifacts.py record --scan --repo /tmp/test
uv run scripts/audit_artifacts.py impact FR-012 --repo /tmp/test
uv run scripts/audit_artifacts.py report --repo /tmp/test --json
uv run scripts/audit_artifacts.py version list requirements.md --repo /tmp/test
uv run scripts/audit_artifacts.py version diff requirements.md prev latest --repo /tmp/test
uv run scripts/audit_artifacts.py version rollback requirements.md prev --repo /tmp/test
uv run scripts/audit_artifacts.py refresh detect --spec specs/0001-duplicate-claim-409.md --repo /tmp/test
uv run scripts/audit_artifacts.py refresh scan --repo /tmp/test --json
uv run scripts/audit_artifacts.py refresh status --spec specs/0001-duplicate-claim-409.md --repo /tmp/test
uv run scripts/retro_report.py --state /tmp/test/.sdlc/state.yaml --json
uv run scripts/retro_report.py --repo /tmp/test --window-days 30
uv run scripts/validate_shape.py templates/phases/01-requirements/requirements.shape.yaml
uv run scripts/check_document_completeness.py --doc /tmp/test/doc.md --shape templates/phases/01-requirements/requirements.shape.yaml
uv run scripts/check_document_completeness.py --repo /tmp/test
uv run scripts/document_shape_cli.py read --doc /tmp/test/doc.md --shape templates/phases/01-requirements/requirements.shape.yaml
uv run scripts/document_shape_cli.py write --doc /tmp/test/doc.md --updates /tmp/test/updates.json
uv run scripts/document_shape_cli.py next-number --doc /tmp/test/doc.md --shape templates/phases/01-requirements/requirements.shape.yaml
uv run scripts/document_shape_cli.py add-instance --doc /tmp/test/doc.md --shape templates/phases/01-requirements/requirements.shape.yaml --title "New requirement"
uv run scripts/approval_settings.py /tmp/test
uv run scripts/stage_readiness.py --repo /tmp/test --phase 1 --json
uv run scripts/record_draft.py record --repo /tmp/test --artifact .sdlc/artifacts/01-requirements/requirements.md --field Rationale --outcome discarded --actor matt
uv run scripts/record_draft.py report --repo /tmp/test --json
uv run scripts/spec_readiness.py --spec /tmp/test/specs/0001-duplicate-claim-409.md --json
uv run scripts/spec_transition.py --spec /tmp/test/specs/0001-duplicate-claim-409.md ready
uv run scripts/spec_transition.py --spec /tmp/test/specs/0001-x.md risk LOW --authorised-by "Matt K"
uv run scripts/spec_transition.py --spec /tmp/test/specs/0001-x.md defer --reason "the upstream service slipped a quarter"
uv run scripts/project_settings.py --repo /tmp/test --json
uv run scripts/set_setting.py --repo /tmp/test person "@sam-k" --name "Sam K" --team core --roles developer
uv run scripts/set_setting.py --repo /tmp/test limit core 3
uv run scripts/set_setting.py --repo /tmp/test approval requirements --on --approver "@priya-n"
uv run scripts/connection_report.py --repo /tmp/test --json
uv run scripts/gate_inventory.py --repo /tmp/test --json
uv run scripts/foundation_summary.py --repo /tmp/test --json
uv run scripts/declare_complete.py --repo /tmp/test check --confirmed "core=@priya-n"
uv run scripts/declare_complete.py --repo /tmp/test declare --declared-by "Matt K" --confirmed "core=@priya-n"
uv run scripts/stage_readiness.py --repo /tmp/test --phase 2 --json
uv run scripts/generate_status.py --state /tmp/test/.sdlc/state.yaml --json
uv run scripts/workshop_brief.py build --repo /tmp/test --contradictions CON-01,CON-02 --questions Q-01,Q-02 --logistics-json logistics.json --json
uv run scripts/rules_check.py --repo /tmp/test --json
uv run scripts/workshop_brief.py candidates --repo /tmp/test --json
uv run scripts/data_contract.py summary --repo /tmp/test --json
uv run scripts/narrative_status.py --repo /tmp/test --all-phases --json
uv run scripts/pipeline_proof.py --repo /tmp/test --json
uv run scripts/generate_phase_report.py --state /tmp/test/.sdlc/state.yaml --phase 0 --json
uv run scripts/generate_phase_report.py --state /tmp/test/.sdlc/state.yaml --all --json
uv run scripts/pipeline_proof.py --repo /tmp/test --write
uv run scripts/doctor.py --repo /tmp/test --offline --json
uv run scripts/audit_gates.py --repo /tmp/test --json
uv run scripts/check_channel.py --spec /tmp/test/specs/0007-name.md --json
uv run scripts/check_channel.py --interaction-spec /tmp/test/.sdlc/artifacts/02-design/experience/channel-interaction-spec.md --json
uv run scripts/bind_channel.py --spec /tmp/test/specs/0007-name.md --channel voice --repo /tmp/test --json
uv run scripts/track_decisions.py open --decision "Fail open or closed?" --owner "@priya-n" --repo /tmp/test
uv run scripts/track_decisions.py decide --id DL-01 --by "@priya-n" --resolution "Fail closed" --repo /tmp/test
uv run scripts/intake_documents.py --repo /tmp/test --json --skip DOC-003 --priority DOC-001,DOC-004
uv run scripts/intake_documents.py --repo /tmp/test --lock
uv run scripts/intake_documents.py --repo /tmp/test --registry --json
uv run scripts/sprint.py new --repo /tmp/test --sprint S07 --goal "Ship the duplicate-claim rail" --start 2026-09-28 --target 6 --mix "HIGH:1,MEDIUM:2,LOW:3" --by "Pod Lead"
uv run scripts/sprint.py slate --repo /tmp/test --sprint S07 --by "Pod Lead" --spec 0001
uv run scripts/sprint.py verdict --repo /tmp/test --spec 0001 --lane eng --verdict accepted --by "Eng Lead"
uv run scripts/sprint.py status --state /tmp/test/.sdlc/state.yaml --json
uv run scripts/sprint.py ready --repo /tmp/test --sprint S07 --by "Pod Lead"
uv run scripts/sprint.py close --repo /tmp/test --sprint S07 --by "Pod Lead" --carry-to S08 --carry "0001=blocked on DL-03"
uv run scripts/generate_sprint_report.py --repo /tmp/test --sprint S07 --kind planning
uv run scripts/generate_sprint_report.py --repo /tmp/test --sprint S07 --kind planning --json
uv run scripts/sprint.py plan --repo /tmp/test --sprint S07 --json
uv run scripts/track_specs.py --repo /tmp/test --sprint S07
uv run scripts/data_spec.py new --repo /tmp/test --serves 0001 --name "claims profile" --owner @sam-k --json
uv run scripts/data_spec.py target --repo /tmp/test --spec 0003 --serves 0001
uv run scripts/data_spec.py status --repo /tmp/test --json
uv run scripts/data_spec.py check --repo /tmp/test --spec 0002 --json
uv run scripts/track_decisions.py open --repo /tmp/test --decision "Fail open or closed?" --owner "@priya-n" --all-branches --json
uv run scripts/ado_sync.py config init --repo /tmp/test
uv run scripts/ado_sync.py config check --repo /tmp/test --json
uv run scripts/ado_sync.py discover --repo /tmp/test --json
uv run scripts/ado_sync.py validate --repo /tmp/test --json
uv run scripts/ado_sync.py plan --repo /tmp/test --json
uv run scripts/ado_sync.py status --repo /tmp/test --json
uv run scripts/ado_sync.py drift --repo /tmp/test --json
uv run scripts/ado_sync.py show sdlc:spec:0004 --json --for-agent --repo /tmp/test
uv run scripts/ado_sync.py discussion gc --repo /tmp/test --dry-run --json
uv run scripts/ado_sync.py plan --repo /tmp/test --no-comments --json
uv run scripts/ado_sync.py posture assisted --repo /tmp/test --by "Pod Lead"
uv run scripts/ado_sync.py iteration bind S07 --repo /tmp/test --path 'Claims\Sprint 7' --by "Pod Lead"
uv run scripts/ado_sync.py comment sdlc:spec:0004 --repo /tmp/test --message='Carried to S08: waiting on DL-04' --by "Pod Lead"
uv run scripts/ado_sync.py apply --repo /tmp/test --confirm 3f9c1a07b2de --by "Pod Lead" --limit 20
uv run scripts/ado_sync.py link-pr --repo /tmp/test --spec 0004 --pr 12 --by "Pod Lead"
uv run scripts/ado_sync.py reconcile sdlc:spec:0004 --repo /tmp/test --field System.AssignedTo --take ado --by "Pod Lead"
uv run scripts/ado_sync.py pull --repo /tmp/test --all --propose-specs --json
uv run scripts/ado_sync.py plan --repo /tmp/test --kind nfr --json
uv run scripts/ado_sync.py plan --repo /tmp/test --kind lifecycle --kind rule --kind question --kind measurement --json
uv run scripts/ado_sync.py reconcile sdlc:fr:001 --repo /tmp/test --field System.Description --take ado --from-comment 7 --by "Pod Lead"
uv run scripts/ado_sync.py reconcile sdlc:epic:001 --repo /tmp/test --field Microsoft.VSTS.Common.AcceptanceCriteria --take ado --by "Pod Lead"
uv run scripts/ado_sync.py sync --repo /tmp/test --kind spec --id 0004 --json
uv run scripts/retro_report.py --repo /tmp/test --json
uv run --project scripts python -m pytest scripts/tests/ -q

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.