shepherd-sdlc
CLAUDE.md9 starsChanged 27 days agoMITView on GitHub
In this repository2 other files it gives its agents.
- docs/agents.mdAGENTS.md
- shepherdSkill
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
- Very specific: line 28
Every agent and command is a library part, not just a pipeline stage.
- Very specific: line 32
When adding a new agent or command, document both modes in its file.
≈ “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.
Files
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 readsprofiles/— Company/stack YAML configs with compliance gatesphases/— 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 byscripts/phase_model.pyreadingphases/phase-registry.yaml.references/— Progressive disclosure docs (loaded on-demand)templates/— Artifact templates copied to target projectsscripts/— Python automation (uv runtime)scripts/code_host.py+ado_import.py(withado_map.py/ado_transport.py/ado_pipelines.py),ado_outcomes.py,import_outcomes.py,host_report.py,ado_sync.py(withado_schema.py/ado_wit.py/ado_sync_model.py) — the code-host provider layer: which host a repository is on (github/azure-devops/none), theaztwin of theghreads and writes returning the same dict shapes, the Azure DevOps scorecard import, the top-levelhostblock every host-touching--jsoncarries, and the Azure Boards mirror driven by/sprd-ado.references/code-host-providers.mdis the contract;references/ado-boards.mdis the mirror'shooks/— PowerShell hooks for session/phase context injectionstudio/— the Shepherd desktop app, this plugin's optional Electron front end (Node/TypeScript, its ownpackage.jsonand 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. Everyclaudecall the Shepherd desktop app makes uses flags declared instudio/shared/claudeContract.tsand probed once againstclaude --help, so an older Claude Code is reported ("lacks<flags>— update") rather than failing silently. Brand assets (mark, tiles, palette, brandbook) live indocs/brand/shepherd/— no wordmark file ships until its typeface is chosen (brand 4b); the team guides indocs/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, onePageHeader/Disclosurekit 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 understudio/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-processCommandCenterread model with per-block provenance, and every write goes through a closedsprint.pyargv 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. Seestudio/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-enhancegenerates stakeholder-friendly.narrative.mdcompanions - Conversational coaching —
/sprd-coachfor adaptive guided dialogue through phases - Artifact checksums — SHA-256 dirty tracking; gate checks skip unchanged artifacts
- Smart repair —
gate-repairagent auto-fixes structural gate failures (missing templates, placeholders) - Dependency enforcement —
check_dependencies.pyvalidates section DAG and implementation order - Multi-perspective review —
/sprd-reviewwith 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.mdfor 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_criteriawith optionalphasesarray for non-code artifact quality rubrics - Empirical metrics — JSONL logging in
check_gates.py,validate_frozen_layer.py, andsection-evaluatoragent 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-specis the Build-loop Intent beat:new_spec.pyscaffoldsspecs/NNNN-name.md(auto-allocated id, risk tier a first-class frontmatter field) andcheck_spec.pyenforces 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.pyturns 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, thestatus/developerfrontmatter commit, and the push all happen over plaingitin 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 (ghon GitHub,azon Azure DevOps), and its failure is reported, not fatal. Idempotency is read from the spec's own branch onorigin, not the caller's local checkout, since the frontmatter change never reaches a checkout still sitting on main.--openstarts 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.pyanswers "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.mdhas the grader append a machine-readable## Acceptance Check Verdictsblock 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_onnames 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.pyis 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.pyenforces 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.pyderives 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.jsonmodel was retired from the Build loop;check_dependencies.py+/deep-plansections are now Phase-2 design aids that feed the spec backlog - Steering scorecard (outcomes, not activity) —
scorecard.pyrecords loop outcomes to.sdlc/metrics/loop-events.jsonland 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.mdis the client-facing artifact.scorecard.py import --since <date>builds the same events straight from GitHub's own history via theghCLI (no stored credential — reuses the machine's existing sign-in): merged PRs, review-wait, deployments and closedincident-labelled issues, deduplicated on GitHub's own ids so re-running an overlapping window is a no-op.github_import.pyis the pure mapping layer (gh-shaped dict in, event dict out), kept fully unit-testable with no live calls; hand-recording withrecordstill 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 claimghis required to open a project or read a PR. Two axes, never merged: the code host (PRs, identity, policies) comes from theoriginremote; the CI platform (pipeline dirs, runs, secrets) comes from the harness manifest, asdoctor.pyalready reads it — GitHub + Azure Pipelines is legitimate and a mismatch is reported, never resolved silently. Override precedence:--hostflag >SHEPHERD_CODE_HOSTenv (orSDLC_CODE_HOST, the pre-2.0 name, still read) >.sdlc/code-host.yaml(written byset_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 toghexactly 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_statereadsunknownwhen a probe could not run, a value Azure DevOps does not record isnull(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-levelhostblock ({name, source, cli, cli_state, detail}) and is never a falseno. The Shepherd desktop app's typed-actor rule: when the host cannot say who is signed in (PAT-onlyaz), a typed name is allowed only in that state and every ledger line recordsaccountSource: 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 bytest_gh_argv_golden.pyand no existing test was edited. Fixtures carry provenance:scripts/tests/fixtures/code_host/azure_devops/captured/*.jsonis realazoutput captured 2026-10-05 and anonymised (CAPTURE-NOTES.mdlists every fact it settled); the hand-written documents above it stay markedhand-written (unverified)andtest_fixture_provenance.pylists what is still unverified. Seereferences/code-host-providers.mdanddocs/proposals/code-host-providers.md - Azure Boards mirror (
/sprd-ado) — a provenance-carrying projection of the repository onto the client's Azure Boards, driven byado_sync.py(ado_transport.pyis the oneazspawner: argv lists only,--detect falsealways, never a shell). The repository stays the source of truth and.sdlc/ado.yamlis the config. Setup:config init|check;discoversaves the board's process to.sdlc/ado/schema.json(nullfor what was never read, never an empty list);validateprints 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 databefore the first pull, never a fabricated 0),link/adopt, andshowwith the discussion read as observed content (every quoted line behind a|gutter;--for-agentwithholds instruction-shaped lines — the agent quotes, never obeys). Writes (Fold B3), only as a named human:apply --confirm <hash> --bysends the plan a person read — creates in tree order,test /rev-guarded updates, drafted comments — as whoeverazis signed in as, and only underposture: assisted(the default isplan-only); an AI or agent--by, a plan-only clone, an effort field, or anazidentity that is not--byrefuses the whole plan (exit 2, nothing sent).link-pr,iteration bind,postureandcomment(which drafts a one-row plan; sending is alwaysapply) share the same--byrule. Settling drift (Fold B4), by a named human:reconcile <ref> --field F --take ado|sdlc|ack|not-drift --byrecords one disposition on a drift row through the design's state machine (ado_sync_model.DISPOSITION_MACHINE;ackneeds--owner,not-drifta--reason, a take the row's class has no word for is exit 2).--take adoonly 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-verbCHILD_ARGVtable inado_sync_reconcile.py(the onlysubprocessimporter besidesado_transport.py;ready/defertake 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-specswrites one batch file ofnew_spec/linkargument lists for board items nobody mapped and runs nothing. A findings source (ado_sync_sources_df.py, imported byado_sync.py) projects each review finding infindings-log.jsonlas a work item — CRITICAL/HIGH on the process'sBugCategory, title[<category>] <fingerprint>, never finding text in a field — as the opt-infindingkind (plan --kind finding, orfindinginkinds:). 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 sameplan→apply --confirmhandshake —nfr(anNFR-xxxrow ofnon-functional-requirements.md, under the Domain whose heading it sits beneath, elseno parent),rule(aBR-NNrow ofbusiness-rules.md, under the one Feature whose brief names it; a(pending DL-NN)outcome names that decision as its predecessor),question(aQ-NNentry ofquestion-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 ofstate.yaml; a phase with no gate readsno gate recorded) andmeasurement(a threshold row of asuccess-criteria.mddimension, under its Outcome, only on a typetypes.measurementpins).ado_sync_sources_p1.pyregisters them intoSOURCESat import, none is in the starterkinds:(plan --kind nfr, or the kind inkinds:), 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.KINDShas 15 members endingnfr(a leaf, soTREE_ORDERis unchanged).ado_wit_feed.py(read-only) pages thereporting/workItemRevisionsfeed (WIT_ROUTES["workitemrevisions"],verified: Falseuntil the B0 capture) from an explicit watermark whose kind the string names —token:<continuation token>orsince:<whole UTC time>, never inferred from its shape — kept in.sdlc/ado/local/watermarkbysave_watermarkonce the caller has used the rows; a watermark never saved readsNone, never 0 (the read then starts atFEED_START, named, not omitted); the first call names whichever the watermark is, every later call only the previous page'scontinuationToken, never both; andpulldoes not read through it yet.reconcile --take adoon a board edit to a co-owned text field writes<artifact>.proposedbeside 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 throughdocument_shape.write_documentwith the line endings kept — and never writes the artifact; a named person lands it through/sprd-revise, andreconcile --jsonnames the draft asproposed/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.proposedfile can hold a board comment's words, so it belongs in the project's.gitignore, never in a commit. A typed--reasonon anadoorsdlctake is ledgered as<text>plus a 12-hexreason_sha, never the words (not-driftandack, which require the words, keep them as typed)./sprd-retrogains 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 — itsboardskey and text block appear only when.sdlc/metrics/ado-sync-log.jsonlholds an event the mirror wrote, a block nobody measured readsno data/null, and nothing is keyed by a person. Mentions stay text only until the B0mention_probecapture (B5-P5 waits for it). Desktop app (Shepherd): a closedAdoVerbRequestof eight verbs (studio/shared/adoVerbArgv.ts, pure and golden-tested) is the only way a renderer reachesado_sync.py— main builds the argv from that table,--jsonon 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, neveraz; the work-item chip, apply dialog and Today rows readstatus --jsonthrough the command center, the renderer never reads.sdlc/ado/**, and no timer mutates a ledger. Fold B5 adds, in the renderer only (nothing understudio/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 exactreconcileargument line per field, and onereconcileper chosen field on one Confirm, stopping at the first the plugin did not settle; a result carryingproposednames that file and says the artifact was not changed — andPullNow(Pull now runspull, Pull discussionpull --discussion, neither on a timer; an exit 0 carrying anunavailableboard 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:syncis 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(synchas no--fulland no--by), then the plan — exit 0 on every path but a pull-stage ledgerDRIFT(1), andado_sync_model.SYNC_NODATA(countsandnextnull, never a 0) when nothing could be read;comment,planandsyncend with the one footerado_sync_model.apply_next_line(<hash>)wherever an apply is offered (plan --jsoncarries it asnextbesideapply_offered; a plan-only clone prints none). The catch-up is ask-first:/sprd-refineand/sprd-sprintprint the ledger'sBoard as of <as_of>and runsynconly on a person's yes, adding--discussiononly on their own ask;/sprd-sprintand/sprd-handoffprint the board's pending count after a write and never runapply. The closedAdoVerbRequesttable 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 runsplanfor 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 ispullon 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 spawnstatusonly (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 underspecs/orstate.yamlchanges, 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--byoff 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. Seereferences/ado-boards.mdanddocs/proposals/ado-boards-mirror.md - Finding memory & disposition tracking (Context Repair, Increment A) —
/sprd-reviewnow writes a machine-readable## Gate Resultsblock (id/category/severity/target/disposition/detail) thatrecord_findings.pyparses 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.pyis 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 --strictexits 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). Seedocs/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.pyguards the library andcheck_channel.pyis 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-reviewcouncil lenses (7 viewpoints). A spec's optionalchannel:frontmatter field binds the surface; a phase-spanning.sdlc/decision-log.md(owner + 2-business-day clock, surfaced in/sprd-statusviatrack_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. Seereferences/channel-model.mdandreferences/team-model.md - Close handoff-report generation — Phase C Step 4 ("Hand over the record") is a two-pass draft:
generate_handoff_report.pydoes the deterministic assembly first — phase report index, per-phase gate/sign-off table (fromstate.yaml), metrics history (reusingscorecard.py), spec backlog (reusingtrack_specs.py) — filling the existingfinal-handoff-report.mdtemplate 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.pyowns 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.pyharvests declaredupstream → downstreamedges (frozen-layersource_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 insidegate_results, so/sprd-auditoutput 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 linkedDL-NNdecision-log item; re-gates; shows impact to disposition)./sprd-audit-artifactsis the read-only sibling to/sprd-audit(freshness dashboard,--impact,--history). Arecord --scanstep in/sprd-nextcaptures direct edits at advance time;/sprd-statussurfaces a stale rollup. The protected core (incl.advance_phase.py) is byte-for-byte unchanged. Seereferences/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 moduleversion_model.pyderives the ordinal-keyed version listv1..vNstraight 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 av1baseline, never a crash). Theversion+refreshverbs fold intoaudit_artifacts.py(the sanctioned extend target; its existingrecord/impact/reportoutput stays byte-identical) so one best-effortcapture()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-signofffor signed-off artifacts, refuses on an uncaptured version;gcis 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 toapply(One Rule), and the refresh lands as a rollback-ablerefreshedversion attributed to the spec via asource_specrider key (artifact_model.pyunchanged). Both write nothing tostate.yaml(re-gating stays the command layer's/sprd-gatejob) and exit 0 on every path; the object store and.proposeddrafts 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. Seereferences/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.pyis the static half of the command-prose safety harness: it extracts everyuv run … scripts/<name>.pyinvocation from fenced blocks incommands/*.mdand validates script existence, subcommand chains, and every--flagagainst live--helpoutput (cached subprocesses), plus agent-name andreferences//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.yamlbeside a template (templates/_shape-schema.yamlis the format;scripts/validate_shape.pyenforces it, every error naming its line) declares that template's sections and fields (label, a fixedtype,required, guidance).scripts/document_shape.pyreads 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 inwarnings(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 editablecustomsection of its own (one whole-body field) rather than raw text.scripts/tests/test_shapes_cover_templates.pyfails 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.pyis 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.pyand 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, plusfoundation-reportandbuild-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). Seedocs/templates-artifacts.md§1 document_shape_cli.py— a subprocess surface over the shape library (Shepherd spec 0009 prerequisite) —document_shape.pyis 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 --shapeemits the library's block model as JSON, andwrite --doc --updatesapplies[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.writetakes no--shape— span replacement doesn't consult it. Both commands open files withnewline="", 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 throughwrite_document()so every other byte (line endings included) is untouched: addressed by shape (--shape --section --field, atable-typed field, its first table) or by position with no shape at all (--table-index N, code fences skipped). The second form exists becausebusiness-rules.md,golden-scenarios.mdanddecision-log.mdare 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-%02dreadsBR-100whole —pattern_to_regexonce read it asBR-10and issuedBR-100twice). It removes nothing by default:--replace-placeholdersclears 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 sharedtemplates/review-report.md(its## Gate Resultstable starts empty, soadd-rowbuilds a report finding by finding andrecord_findings.pyingests it);test_shapes_cover_templates.pyandtest_shaped_templates_roundtrip.pycover 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 (viaphase_model), completeness findings (viacheck_document_completeness's purecheck_document), and sign-off state read straight fromstate.yaml. Two deliberate choices: it never callscheck_gates.py, which appends togate-log.jsonlon 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,--jsonrecord_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.reportanswers "how much was drafted, and how much of it did people keep" — "no data" over a fabricated zero, and never keyed by actorsign_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 intostate.yaml(advance_phase.pyowns 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.pycomputes what a check can see — out-of-scope items present, success dimensions with thresholds and a named source, personas with every column filled,project_typerecorded, AQ-NN items, open decisions missing an owner or due date — aslooks_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 --jsoncarries the result asjudgement;/sprd-nextreads 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 wayvalidate_team.pyvalidates 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 protectedcheck_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 incheck_spec.py, which means a spec edited by hand outside Shepherd is held to exactly the same bar. Always exit 0,--jsonspec_transition.py— the three state changes a spec can make (Shepherd spec 0011) —ready(draft → ready, refused whilecheck_spec.pystill blocks),risk(re-tier, a raise needs nothing, a lower needs a named person —--authorised-by), anddefer(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_rankexplicitly invertsrisk_model.RISK_TIERS, which is declared most-risky-first — a plainindex()ranks it backwards, and that bug shipped once and was caught by running it rather than reading itspec_status.py --all— the whole backlog in one pass (Shepherd spec 0011) — the per-spec path stays as it was;--alladds a bulk mode that fetches every pull request in a single code-host call (ghoraz, 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 lineproject_settings.py/set_setting.py— the settings a project actually has, and the only supported way to change them —project_settings.pycomposes the roster, the per-team work-in-progress limits and the per-stage approval settings into one read, each section carrying thefilepath it came from so a person can always go edit the real thing; itsFIXED_RULESlist names the rule and the script thatenforced_byit, 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 editconnection_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"), withexpected_workflows()read fromharness/workflows/*.ymlrather than hard-coded, and aNOT_UNIVERSALLY_EXPECTEDmap carrying a reason for each thing whose absence is not a fault.gate_inventory.pyparses the gate table out of.github/RAILS.md(a project's own copy wins over the playbook's) and reports each gate asinstalled/missing/not_a_pipelinefoundation_summary.py— what the pre-Build phases decided, for somebody arriving late — reads the Phase 3 artifact set throughphase_model+ the artifacts model (never a hard-coded path list) and pulls each document's top-level sections via the shape library'sfind_heading_spans, so it degrades to headings rather than failing on a document that has drifted from its templatedeclare_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.pyis protected core and already owns that transition with its own gate checks and sign-off recordingpipeline_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.pyreads GitHub's own history throughgh(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.pyowns 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 ofghcalls (repo view,pr list/view,run list/view,apiGET 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. Reusesgate_inventory.inventory()for the rail list andspec_status's verdict parser;github_import.run_ghnow decodesghoutput 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
--jsonmodes, 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--jsonmode 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 inscripts/tests/fixtures/golden/, taken viascripts/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, andskipped(what--offlineleft out, so a missing check is not read as a clean one).audit_gates.pygains--repo(dual mode) and--json(enough_datais false belowMIN_PHASES, and with no gate results the list is empty, never zeros;--comparewith--jsonis refused).upgrade_harness.py --jsonis dry-run only —--jsonwith--applyexits 2, because applying rewrites CI workflows and hooks Shepherd can neither commit nor review.check_channel.py --jsonreports per-dimension coverage;--interaction-spec PATH [--channel C]instead asks whether achannel-interaction-spec.mdhas a real (non-placeholder) contract row for every dimension of its channel.new_spec.py/new_spike.py --jsonreturn the new file's path and id.track_decisions.py open|decideown the decision clock:openallocates the nextDL-NN(the whole log scanned, so an id is never reused), writesopened= today anddue= two business days on via the script's ownadd_business_days, and creates the log from the template when absent;decidesets the status todecidedand 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.pygains--repo,--docs(standalone: with no.sdlc/every id isprovisionaland nothing is written),--json,--skip,--priorityand--lock; a locked catalog refuses--rescan/--skip/--prioritysoDOC-NNNids stay stable (--registry, which writes the registry and token-budgeted index, is deliberately left to the intake spec).bind_channel.pyis the mechanical half of/sprd-channel: sets the spec'schannel:, seedsharness_contextfrom 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'sexample_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 staysspec_transition.py riskafter 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 becausephase-registry.yamlandphase_model.pyare protected core. Status is computed, never stored, with one fixed precedence:doneif itsdone_whenholds (even if a requirement has since become unmet), elseblockedwith a plain-language reason, elseavailable; an activity with nodone_whenis neverdone. Activities are optional and never gate a phase.validate()names the phase, activity and rule for every error (unknown command,createswith no template,after/requiresnaming nothing); evaluation only looks at files and the profile — it never runs a script or a modelstage_readiness.py --jsongainsactivitiesanddefinition;generate_status.py --jsongainscapabilities(spec 0023) —activitiesis the stage's list with each status,definitionis the stage's definition file. A broken declaration degrades to no activities plus awarningsentry rather than failing the report (the documents and sign-off are still true); a healthy run has nowarningskey.capabilities.pyis 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--helpon a dozen scripts on every dashboard load;test_capabilities.pyis what keeps the list honest by running each script's real--helpand 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.--jsonprints exactly one document.workshop_brief.py build(/sprd-brief) — assemblesworkshop-brief.mdfrom 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 toemailed_insteadand a question routedinterviewis refused.totalisnull, never 0, when a source document is absentrules_check.py(/sprd-rules) — checksbusiness-rules.mdagainstgolden-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 areSHOULDseverity and never block; logs to.sdlc/metrics/rules-log.jsonlin workflow modedata_contract.py summary(/sprd-data) — counts the Fields table's PII classification.yesandcustomer-linked(the template's indirectly identifying value) both count as PII, withindirect_fieldsnaming 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 ofrisk_model.TAXONOMY, because PII is a risk-tier driver. Columns are found by header text, never positionnarrative_status.py(/sprd-enhance) — which artifacts have a.narrative.mdcompanion and whether it is stale (git commit time of the artifact against its companion).--phaseand--all-phasestogether is exit 2;coverageis always present and reads{0,0}withhas_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'sactivities, so a command added tophases/activities.yamlappears 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 throughensureDocumentFromTemplate(extended to nested paths and the sharedreview-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 instudio/shared/activityControls.tsbecause the plugin doesn't say which script produces a check's result), talk sends one[Studio]-prefixed turn through the existing stage chat (buildSystemPromptgains one sentence;CHAT_TOOLSis 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 noactivitieskey leaves the tab exactly as it was; a control whose capability the plugin does not list (generate_status.py --json'scapabilities) is disabled with a reason instead of failing. New IPC lives instudio/electron/main/activities.ts, notindex.ts generate_phase_report.py --jsonand the--allrepair (spec 0025) — the phase report prints its result as one JSON document (phase,phase_name,output,found,missing,total,exit_criteria,artifacts;--allprints{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.--allused 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_artifactsnow reads the registry throughphase_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,enhanceandreviewarerun/draftactivities whose deterministic part Shepherd can show, so they are keyed by activity id instudio/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; adonerow keeps its panel (a locked intake still shows its frozen catalogue). Phase report runsgenerate_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,.htmlonly); reports stay local,.sdlc/reports/is not on the sync allowlist. Intake has an explicit Catalogue the documents button becauseintake_documents.pywritescatalog.jsonthe 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 shipdocumentation:commented out, so intake stays blocked until a project setsintake_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.tsis 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;--toolsis 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 finalresultline (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 /Fon Windows) through a newsignalonrunCommand. 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; recordscreated/revised(audit_artifacts.pyhas nodraftedevent) andacceptedin the draft ledger; a review Keep also runsrecord_findings.py record. Discard writes nothing and recordsdiscarded. 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-intakeis bookkeeping once the catalogue and summaries exist, so it is a script call (scripts/intake_registry.pyholds the logic). It reads the catalogue (never writes it) and theDOC-NNN-*.mdsummaries, then writesdocument-registry.mdand.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 undermissing_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 theGenerated: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 fitsindex_budget_tokensby 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 andindex_within_budgetis false.--registrycannot be combined with--skip,--priority,--lock,--rescanor--docs(exit 2); with no catalogue it exits 1.--jsonprints 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 runsdocument-summarizeronce per catalogued, non-skipped document without a filled summary, sequentially inpriority_orderthen id order, for a locked catalogue only; the ids are read fromcatalog.jsonby 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 runsdiscovery-analystonce 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>.mdand 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 runsintake_documents.py --registry --jsonand uses no model workshop_brief.py candidates— what the brief is chosen from (spec 0031) — the read-only verb besidebuild(logic inscripts/brief_candidates.py) that reports exactly what a person curates from: every contradiction (id,title,severity,question, bothsourceswith document reference and quoted passage, andrecommended), every question (id,question,block,route), the registry's documents, the page'slimits(read from the build's own constants: 5 contradictions, 12 questions, 3-5 decisions, 3-5 load-bearing documents),standing_decisionsalready in the template,existing_brief(so a form asks before overwriting) andprovisional_ids. It reusesbuild's own parsers, so a selection form and the build cannot disagree about what an entry is.recommendedis the/sprd-briefcuration step's own rule and nothing more (blocks-outcomeandshapes-designare pre-ticked); a missing or empty input ishas_data: falsewith a note saying what to run next, never an error or a zero. Writes nothing; exit 0 (usage error 2);buildis unchanged- Shepherd's workshop brief form (spec 0032) — the Discovery
briefactivity draws a form instead of a bare Create, keyed by activity id with thebrief-candidatescapability. It readsworkshop_brief.py candidates --json(spec 0031: what the build's own parsers see) and writes throughworkshop_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); apre-workshopquestion 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), aninterviewone 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:buildBriefre-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-jsonvalues (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--forceis 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.pyis the pure single source of truth (mirrorsfindings_model.py): sprint statesplanning → 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 andstatus: readyandeng_review: acceptedanddata_review: accepted|n-a, plus no dependency cycle and nodepends_onpointing outside the slate at an unmerged spec — reusingcheck_dependencies.detect_cycles/topological_sort), the advisory build order (build_order: topological, then unblocks-most, then HIGH→MEDIUM→LOW, then id) andnext_upunder the WIP cap,outcomes, and the frontmatter writer's refusal rules (set_frontmatterwrites ONLY the five keyssprint,next_owner,eng_review,data_review,depends_on; refuses#, quotes, newlines, placeholder tokens; can never writestatus, which stays hand-moved).sprint.pyis the I/O CLI (new slate unslate status handoff ack verdict ready plan close; dual-mode--state|--repo; sprint record.sdlc/sprints/SNN.mdfromtemplates/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 installedspecs/spec-template.mdis never listed or written).generate_sprint_report.pyrenders the self-contained sprint-planning page (sprint-SNN-planning.html, written atreadyand on demand viaplan) and review page (-review.html, atclose) withgenerate_phase_report.md_to_html, splicing an idempotent<!-- sprints:start/end -->block intoindex.html.track_specs.pygainsby_sprint+--sprint SNN(legacy output byte-identical),retro_report.pygains 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 stalesession-handoff.jsonblock. 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--byrefused as labelling-not-enforcement. Never writesstate.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.yamldeclares a"build":block (sprintrun,refinetalk,phase-report,coach;refinealso under"3"),capabilities.pygainssprint-status/sprint-plan/sprint-report,sprint.py status --jsonrows carryrel_pathand a bad--sprintstill prints one JSON document (has_data: false), and the Build › Sprint view (studio/electron/main/sprint.ts,SprintBoard.tsx) rendersstatus --jsonas-is and opens the planning/review page viagenerate_sprint_report.py --json— never a write verb, never a 0 for "no data". Seereferences/sprint-model.md,docs/proposals/sprint-team-layer.mdanddocs/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 existingtypefield, neverkind:, which already means three other things) plusserves: "<parent id>", owned by the Data discipline, worked through/sprd-data, and done only when the data really flows.data_spec_model.pyis the pure source of truth (imports nosubprocess,argparseorbranch_scan):mark_data,link_parent,cited_refs,render_context,replace_section_body,cycle_after_link,status_row,context_coverage,ledger_event, andset_scalar_line, a targeted line edit that keeps a trailing# commentand CRLF and refuses a newline,#or stray quote.data_spec.pyis the I/O CLI (new target status check; dual-mode--state|--repo,--jsonone document,--helptouches no filesystem; reads exit 0 always; writes 0 ok / 1 illegal (missing parent, two files for one id, ledgerDRIFT) / 2 refused — a self-link, a data-spec parent, adepends_oncycle, a conflictingserves— with nothing written).new_spec.pyis imported (next_spec_id,render_spec,slugify,SPEC_FILE_RE), never edited, so its--jsonstays exactly{path,id,name,risk}. One call links both ends: the parent'sdepends_ongains the data id (sosprint_model.build_orderbuilds the data first with no new logic) and itsdata_reviewbecomespendingfrom""/n-a/returned;acceptedis kept and the result carriesdata_review_note— a tool never writesaccepted, which stayssprint.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— onesectionfield indata-spec.shape.yaml, rewritten in place by byte span — lists the parent, a dated read-only copy of its acceptance checks, and everyBR-NN/DL-NN/ADR-NNN/DOC-NNNthe 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— aDOC-NNNlooks in.sdlc/context/intakefirst and aDL-NNin the decision log first, so a summary row never outranks the defining file; one that cannot be found readsnot 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; aDL-NNwith an owner per readiness gap), each passingcheck_spec's vague-line lint, so the grader grades them like any acceptance check.checkis the early look (context-coverage,readiness-gaps-owned,handoff-present) — advisory, exit 0,passed: nullwithnot measuredwhen its source file is absent, never a pass or a fail;status --jsonwith no data spec is exactly{"has_data": false, "specs": []}, andacceptance_dueflags a merged data spec whose parent'sdata_reviewis not yetaccepted. Three new documents undertemplates/phases/02-design/data/, each with a shape and a round-trip fixture:data-dictionary.md(a cell nobody measured readsnot measured, never0),decision-memo.md(constraints, options with evidence, a scorecard, a decision a named human makes) anddata-handoff.md(what is live, how to call it, degraded results, smoke tests, what is left). Branch-safe ids —branch_scan.pyis the only newsubprocessimporter (argv lists, never a shell, neverfetch):track_decisions.py open --all-branchesanddata_spec.py new --all-branchestake the highestDL-NNor spec id on every local ref (refs/heads,refs/remotes) plus one, so two branches cannot both name a decisionDL-07; refs are read as of the last fetch (the help says "rungit fetchfirst"), a ref that could not be read is named inunreadand never counted, outside a git repository the call proceeds locally withrefs_scanned: [], and without the flagopen's output is byte-identical (pinned by the goldens). In--statemode onedata_spec_linkedline (spec ids only, no name) is appended to.sdlc/metrics/spec-log.jsonl;--repomode appends nothing./sprd-dataloads## Contextbefore any interview question (step 1a) and thedata-analystagent keeps its tools (Read, Write, Edit, Grep, Glob): it has no shell and queries no platform, so--liveand 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.yamlgains adata-specrun activity underbuild(Phase 2'sdataactivity still creates its original three documents; the new three are a data spec's deliverables);capabilities.pygainsdata-spec,data-spec-statusanddecision-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.mdand 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 intest_activities_model.py, which gaineddata-spec. Seereferences/data-specs.mdanddocs/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/ -qDoes 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
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.

