Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ name: CI
# test — full pytest suite, incl. the golden-repo compose test (real payload +
# flagship profile, snapshot-asserted tree) and the payload<->install-map
# completeness tripwire. BLOCKS.
# test-windows — the same suite on windows-latest. The plugin is authored on Windows and
# ships PowerShell hooks, so Linux-only CI cannot see console-encoding or
# line-ending faults. BLOCKS.
# sync-check — harness/ is GENERATED from delivery-standard/kit and must never drift.
# BLOCKS. A red here means the kit's main moved without a plugin sync.
# actionlint — lints the workflows the harness EMITS, composed and token-stubbed the
Expand All @@ -31,6 +34,19 @@ jobs:
- uses: astral-sh/setup-uv@v8.3.2
- run: uv run --project scripts pytest scripts/tests -q

# The plugin is developed on Windows but CI ran only on Linux, so two Windows-only defects
# reached master unseen: a cp1252 console fault (fixed in 1.3.0) and a CRLF test fixture.
# A separate job rather than a matrix on `test`, so the existing check name is unchanged.
# The runner's default CRLF checkout is deliberate — it mirrors the dev machine this is
# meant to protect; the suite must pass with either line-ending style.
test-windows:
name: pytest (Windows)
runs-on: windows-latest
steps:
- uses: actions/checkout@v7
- uses: astral-sh/setup-uv@v8.3.2
- run: uv run --project scripts pytest scripts/tests -q

sync-check:
name: harness == kit (generated-copy invariant)
runs-on: ubuntu-latest
Expand Down
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,15 @@ desktop.ini
/tmp/
*.log

# Artifact versioning & refresh — transient / non-authoritative local state.
# The committed source of truth stays the metadata ledger (.sdlc/metrics/artifact-log.jsonl);
# the object store and refresh drafts are a local safety net a fresh clone/CI degrades past cleanly.
.sdlc/refresh/**/*.proposed
.sdlc/refresh/**/candidates.json
.sdlc/refresh/_rollback/
.sdlc/versions/objects/
# Override (opt in per repo): to make content snapshots portable across clones/CI, comment out the
# `.sdlc/versions/objects/` line above and commit the store. History stays lean by default.

# Do not ignore .sdlc/ — it's part of the plugin's test data
# Target projects will have their own .gitignore
13 changes: 12 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A Claude Code plugin that orchestrates the full SDLC lifecycle using company-con
## Architecture
- `plugin.json` — Plugin manifest (entry point for Claude Code)
- `SKILL.md` — Main skill definition (loaded when plugin activates)
- `commands/` — 23 slash commands (`/sdlc`, `/sdlc-setup`, `/sdlc-status`, `/sdlc-next`, `/sdlc-gate`, `/sdlc-enhance`, `/sdlc-coach`, `/sdlc-review`, `/sdlc-intake`, `/sdlc-brief`, `/sdlc-spec`, `/sdlc-phase-report`, `/sdlc-audit`, `/sdlc-feature`, `/sdlc-experience`, `/sdlc-data`, `/sdlc-rules`, `/sdlc-channel`, `/sdlc-evals`, `/sdlc-harness`, `/sdlc-upgrade`, `/sdlc-revise`, `/sdlc-audit-artifacts`)
- `commands/` — 26 slash commands (`/sdlc`, `/sdlc-setup`, `/sdlc-status`, `/sdlc-next`, `/sdlc-gate`, `/sdlc-enhance`, `/sdlc-coach`, `/sdlc-review`, `/sdlc-intake`, `/sdlc-brief`, `/sdlc-spec`, `/sdlc-phase-report`, `/sdlc-audit`, `/sdlc-feature`, `/sdlc-experience`, `/sdlc-data`, `/sdlc-rules`, `/sdlc-channel`, `/sdlc-evals`, `/sdlc-harness`, `/sdlc-upgrade`, `/sdlc-revise`, `/sdlc-audit-artifacts`, `/sdlc-version`, `/sdlc-refresh`, `/sdlc-retro`)
- `agents/` — 13 agents (orchestrator, requirements-analyst, compliance-checker, section-evaluator, narrative-enhancer, gate-repair, multi-reviewer, discovery-analyst, 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
Expand Down Expand Up @@ -60,6 +60,9 @@ When adding a new agent or command, document both modes in its file. `discovery-
- **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 (`/sdlc-feature`, `/sdlc-experience`, `/sdlc-data`, `/sdlc-rules`, `/sdlc-channel`, `/sdlc-evals`, `/sdlc-harness`, `/sdlc-upgrade`) drive five interview-driven discipline agents (`feature-architect`, `visual-designer`, `conversation-designer`, `data-analyst`, `bizreq-analyst`) that also serve as `/sdlc-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 `/sdlc-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`, `/sdlc-coach`, `/sdlc-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 `/sdlc-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. `/sdlc-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). `/sdlc-audit-artifacts` is the read-only sibling to `/sdlc-audit` (freshness dashboard, `--impact`, `--history`). A `record --scan` step in `/sdlc-next` captures direct edits at advance time; `/sdlc-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. `/sdlc-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). `/sdlc-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 `/sdlc-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 `/sdlc-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

## Testing
```bash
Expand All @@ -81,5 +84,13 @@ 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 --project scripts python -m pytest scripts/tests/ -q
```
13 changes: 10 additions & 3 deletions commands/sdlc-audit-artifacts.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ gate, changes a verdict, or edits your artifacts, specs, or `state.yaml`.
```bash
uv run --project ${CLAUDE_PLUGIN_ROOT}/scripts ${CLAUDE_PLUGIN_ROOT}/scripts/audit_artifacts.py report --history FR-012 --state .sdlc/state.yaml
```
`--history` shows the change **metadata** (when/who/why). To see what the artifact actually
**said** at each version — or to diff two versions — point the user at the read-only
`/sdlc-version list|show|diff <id|file>` command (the content complement). Rolling one back lives
there too, behind its own named-human confirm; this command never mutates.

4. **Display results** with the honest confidence labels intact:
- Every downstream edge is tagged **declared** (a written-down link: a frozen layer's
Expand Down Expand Up @@ -80,8 +84,11 @@ gate, changes a verdict, or edits your artifacts, specs, or `state.yaml`.

- The user runs `/sdlc-audit-artifacts` — never `audit_artifacts.py` by hand. The command owns the
scan, the lens selection, and the disposition recording.
- **Read-only with respect to your project.** The only thing this command ever writes is the
change-ledger (`.sdlc/metrics/artifact-log.jsonl`) — the audit trail itself. It never edits an
artifact, a spec, or `state.yaml`. To *change* an artifact, use `/sdlc-revise`.
- **Read-only with respect to your project.** The only authoritative thing this command writes is the
change-ledger (`.sdlc/metrics/artifact-log.jsonl`) — the audit trail itself; the `record --scan` in
step 2 also best-effort captures each changed artifact's content into the gitignored, non-
authoritative version store (`.sdlc/versions/`), which a store fault silently skips without changing
this command's output. It never edits an artifact, a spec, or `state.yaml`. To *change* an artifact,
use `/sdlc-revise`; to *roll one back*, `/sdlc-version`.
- **Advisory by construction** — `audit_artifacts.py` exits 0 always. Staleness is never a gate; a
candidate is a prompt for a human's judgement, not a verdict.
17 changes: 15 additions & 2 deletions commands/sdlc-next.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,21 @@ Run exit gate checks for the current phase and advance to the next phase if all
```bash
uv run --project ${CLAUDE_PLUGIN_ROOT}/scripts ${CLAUDE_PLUGIN_ROOT}/scripts/audit_artifacts.py record --scan --state .sdlc/state.yaml
```
This appends only to `.sdlc/metrics/artifact-log.jsonl` (the audit trail) — it never modifies
artifacts or `state.yaml`, and never blocks the advance. If the script is absent, skip it.
This appends only to `.sdlc/metrics/artifact-log.jsonl` (the audit trail) and captures each
changed artifact's **content** lockstep into the local version store (`.sdlc/versions/`), so the
edit is diffable and roll-back-able via `/sdlc-version`. Content capture is best-effort — a store
fault leaves the ledger append and this command byte-identical. It never modifies artifacts or
`state.yaml`, and never blocks the advance. If the script is absent, skip it.

- **Then, optionally surface merged-spec drift (advisory, exit 0):** if any specs reached
`status: merged` during the loop just completed, offer to back-propagate what they shipped into
the pre-Build artifacts. Run the read-only scan and, for any spec with drifted upstreams, suggest
the human run `/sdlc-refresh detect --spec <path>`:
```bash
uv run --project ${CLAUDE_PLUGIN_ROOT}/scripts ${CLAUDE_PLUGIN_ROOT}/scripts/audit_artifacts.py refresh scan --state .sdlc/state.yaml
```
This writes nothing and never blocks — it only points at candidates a named human may choose to
refresh. Skip silently if there are no merged specs or the script is absent.

Then perform the advance via `advance_phase.py` (it applies the state updates below and records any
discipline sign-offs captured in step 4 on the phase's existing sign-off record):
Expand Down
Loading