em-bigmac-framework · sprint-audit

Sprint Audit — the engineering manager's guide

Everything you need to understand, configure and run the sprint audit. Read it top to bottom the first time as your setup guide; come back to sections 5–8 as your day-to-day reference.

Scoring v3.0 Big Mac · 47 criteria Scrum Management capability Driven by the em-bigmac-framework skill

1What the sprint audit is

The sprint audit evaluates a team's Agile ceremonies — sprint planning, backlog refinement, daily standup, demo/review and retrospective — against the Qualitara Big Mac Best Practices Framework (the 47-criterion Scrum Management capability), and renders a set of HTML reports for the team, the client and Qualitara leadership.

Its job is to turn "how is this team running its sprints?" from an EM opinion into a repeatable, defensible, evidence-backed score. The same transcripts scored twice give the same numbers, because the model never invents a score — it only judges each criterion, and deterministic Python does every calculation.

The one idea to hold onto: the model is the judge, the code is the scorer. For each criterion the model emits a level (exemplary / solid / partial / weak / absent / not evaluable) with evidence. sprint_audit.domain.scoring turns those levels into every number, rating, cap and maturity verdict. That split is what makes the result auditable.

2How it works — the pipeline

An audit runs as six stages. Three are deterministic Python (the orchestrator's prepare / score / finalize subcommands); the model's work slots in between them. You never run these by hand — the skill drives them — but knowing the shape tells you exactly where to look when something needs attention.

Python1 · Prepare

Resolves the engagement, finds the highest unaudited sprint (or the one you named), loads on-disk transcripts or pulls missing ones from Fireflies, runs the transcript analyzer, and writes a compact audit/<ceremony>.summary.json per ceremony.

Model2 · Evaluate

Reads each ceremony summary (not the raw transcript) and emits a level + evidence per criterion to <ceremony>.judgments.json, plus a <ceremony>.narrative.json (what's good / improve / stop / start, per-person feedback for the team).

Python3 · Score

Validates every narrative, builds run.json, runs the deterministic scorer and writes scores.json — per-criterion numbers, ceremony roll-ups, cap-if-absent ceilings that fired, and a coverage report (evaluated / N-E / N-A, confidence distribution).

Model3.5 · Second pass

Conditional. If a ceremony has a low-confidence critical criterion, a cap fired, or a level jumped ≥2 vs the prior sprint, an adversarial re-review runs for that ceremony only. Skipped entirely when nothing triggers.

Model4 · Synthesis

Sprint-level. Writes synthesis.json: the executive headline, ranked top findings (with repeat-finding escalation), exactly three next-sprint actions, client dependencies, improvement areas and per-ceremony commentary.

Python5 · Finalize

Assembles evaluation.json, validates it against the schema, appends the engagement history.json, and renders every HTML report to <report_folder>\sprint_NN\.

Idempotent by design. Re-running any stage is safe. prepare with cached transcripts is a no-op, score uses whatever judgments are on disk, and finalize re-renders from scratch — so "the reports look stale" is always fixed by re-running finalize.

3The framework & how scoring works

There is exactly one framework: the 47 atomic criteria of the Big Mac Scrum Management capability, sourced from Airtable and shipped committed in framework/. Every criterion carries source: bigmac. (Earlier versions blended in a separate agile layer; that was removed in v0.4.0.)

Levels → numbers

The model picks one of six levels per criterion. The scorer maps them deterministically, and excludes the bottom two from both sides of the average:

  • exemplary / solid — passing
  • partial / weak / absent — scored, but below the bar
  • not evaluable / not applicable — excluded from numerator and denominator

Weights

Each criterion has a weight in the overlay: 2.0 load-bearing (10 of them — the value-drivers and key controls), 1.0 standard (31), 0.5 hygiene (6). Ceremonies then roll up with their own weights: demo 0.25, planning / refinement / retro 0.20, standup 0.15.

Caps that bite

Some load-bearing criteria are cap_if_absent: if they're absent the ceremony's rating is ceilinged (yellow or red) no matter the average — a deal-breaker can't be averaged away. Caps that fired are listed in scores.json → rating_caps_applied.

Maturity gate

Criteria roll into a maturity verdict on the Big Mac scale - Foundation → Structure → Mastery (no "Pre-Foundation"). Each criterion's band is sourced 1:1 from Airtable. A band is reached when ≥80% of its evaluable criteria pass and every load-bearing criterion in it passes (a hard gate); progression is cumulative. Below the bar it reads "Foundation - in progress (N%)" so you see the climb, not a flat floor.

Two governance mechanisms you control per engagement

Exceptions engagement.json → exceptions[]

A documented, signed-off reason a criterion doesn't apply (or applies differently). Tiers: not_applicable / structural / temporary exclude the criterion from the score and suppress its cap; compensating_control evaluates a named substitute with a partial floor (can't reach solid/exemplary). Every exception carries an approver, an approval date and a review date that decays its trust over time. Full model: framework/references/exceptions-schema.md.

Team roster engagement.json → team_roster

The delivery team and their alias forms (display-name variants + emails). Anyone in a transcript who isn't on the roster — client stakeholders, other teams, unresolved Speaker N labels — is an external attendee: excluded from participation scoring and per-person feedback, and shown only as a "+N external" aggregate. One person's name variants merge into one feedback card. Full model: framework/references/team-roster-schema.md.

4First-run setup — do this once

Four one-time steps get a fresh machine ready. Every per-account path resolves from your EM manifest — there is no project-dir environment variable to set, and nothing here is hardcoded in the runner.

1

Install the runner

Python 3.11+ required. From tools/sprint-audit/:

pip install -e ".[dev]"

Runtime deps: requests, pyyaml, jsonschema, jinja2. Dev extras add pytest, mypy, ruff.

2

Set the one secret you need

config/config.yaml is gitignored and never committed. Copy the example and set just the Fireflies key:

cp config/config.example.yaml config/config.yaml
fireflies_api_key: "..."    # fetches ceremony transcripts that aren't already on disk

That's the only required key. anthropic_api_key is not used by the runner (the model steps run through the skill + Claude, not an API key here). airtable_api_key / airtable_base_id are only for the optional sync_bigmac utility that refreshes the framework — the framework already ships committed, so a first run needs neither.

3

Add a sprint_audit block to your EM manifest

The skill resolves every path from your manifest — the first of ~/.claude/config/em-manifest.json or the in-repo .claude/config/em-manifest.json that exists. Add an accounts.<account>.sprint_audit block:

"sprint_audit": {
  "engagements": ["<engagement to audit>"],
  "report_folder": "C:\\Users\\<you>\\Desktop\\<Account>\\Sprint Audits",
  "data_folder":   "C:\\Users\\<you>\\Desktop\\<Account>\\Sprint Audits\\_audit-data",
  "renderer_path": "<repo>/tools/sprint-audit"
}
KeyMeaning
engagementsThe engagement(s) under this account you audit
report_folderWhere rendered HTML lands (<report_folder>\sprint_NN\)
data_folderWhere per-engagement audit data + durable state lives
renderer_pathAbsolute path to tools/sprint-audit in your checkout

Use plugins/em-core/shared/em-manifest.template.json for the exact shape, or run /em-setup — it walks you through creating/updating the manifest, including this block.

4

Create the per-engagement data

Hand-authored — there is no scaffolding command. Under your manifest data_folder:

<data_folder>\
  <account-slug>\
    <engagement-slug>\
      engagement.json          ← engagement config (you write)
      history.json             ← auto-appended by finalize — do not hand-edit
      sprints\
        sprint_01\
          manifest.json        ← per-sprint config (you write)
          transcripts\         ← Fireflies-pulled .txt + .meta.json
          audit\               ← orchestrator + model write here
          evaluation.json      ← the reports' source of truth

Slug rule: lowercase, hyphens for spaces, no other punctuation. The account_slug / engagement_slug inside the JSON must match the folder names. Copy the committed examples as your starting point: audits/example-client/example-engagement/engagement.json, and .../sprints/example_manifest.json renamed to manifest.json inside sprint_01/ (it ships prefixed so it isn't picked up as a real sprint).

A complete engagement.json (with the two governance blocks) and the per-sprint manifest.json are shown in section 7.

5Running an audit

You drive everything through the em-bigmac-framework skill from a Claude Code session. The most common forms:

You say…What happens
run sprint audit (no args)Lists available engagements and asks you to pick.
run sprint audit for <account> / <engagement>Audits the highest unaudited sprint for that engagement.
run sprint audit for <account> / <engagement> / sprint_NNAudits that specific sprint.
rerun <ceremony> / re-audit <ceremony>Re-audits a single ceremony without touching the others.
regenerate HTML / rerender reportsRe-renders the reports from existing data (e.g. after a template change).
generate client report / board reportRenders just that audience's report for the sprint.

Under the hood the skill resolves RENDERER, AUDITS_DIR and REPORTS_DIR from your manifest, then runs each orchestrator command from $RENDERER with PYTHONPATH=src:

# Prepare  (fetch transcripts, analyze, write per-ceremony summaries)
PYTHONPATH=src python -m sprint_audit.orchestrator prepare \
  --account "<account>" --engagement "<engagement>" --audits-dir "$AUDITS_DIR"

# Score    (validate narratives, deterministic scorer)
PYTHONPATH=src python -m sprint_audit.orchestrator score "$SPRINT_DIR"

# Finalize (assemble, validate, append history, render every report flat)
PYTHONPATH=src python -m sprint_audit.orchestrator finalize "$SPRINT_DIR" \
  --reports-dir "$REPORTS_DIR" --flat-reports

PDFs are on demand only — never part of a normal run. When you actually need them: python -m sprint_audit.cli.export_pdf --reports-dir <sprint folder> (needs headless Chrome or Edge).

6The reports you get

One finalize renders these per sprint, into <report_folder>\sprint_NN\:

ReportAudienceWhat's in it
sprint_summary.htmlEM + leadershipCross-ceremony view, tabbed: Story / Overview (incl. the Predictability and Team signals panels) / Ceremony summary / Big Mac criteria / Maturity gate (3-band, with the capability matrix).
<ceremony>.html (one each)TeamPer-ceremony scorecard: Verdict / Coaching / Evidence / Examples / Cross-refs.
participant_feedback_report.htmlEM (1:1 prep)Per-person feedback rolled up across ceremonies + sprint-wide patterns. Always rendered. Roster-filtered to the delivery team.
client_report.htmlClientTrajectory, three findings translated to plain language, where the client can help. No internal approval metadata.
internal_report.htmlQualitara leadershipStatus snapshot, team profile, improvement areas, a 3-bucket continue / watch / consider recommendation.

Durable state stays under data_folder: the per-ceremony audit/*.json, the sprint evaluation.json, and the engagement history.json (auto-appended — never hand-edit it).

7Configuration reference

engagement.json — one per engagement

{
  "account": "Acme Robotics",
  "account_slug": "acme-robotics",
  "engagement": "Digital Service",
  "engagement_slug": "digital-service",
  "fireflies_title_hints": ["ACR |", "Digital Service |"],
  "team_size": 7,
  "notes": "anything you want to remember",

  "team_roster": {
    "members": [
      {"name": "Marcus Cole", "role": "Developer",
       "aliases": ["Cole, Marcus", "marcus.cole@client.com"]}
    ]
  },

  "exceptions": [
    {
      "criterion_id": "reclaYUZIYFMBXwNW",
      "criterion_text": "Each sprint has a clearly defined, achievable sprint goal",
      "source": "bigmac",
      "tier": "compensating_control",
      "scope": "engagement", "scope_value": null,
      "reason": "Team runs three products; a single sprint goal is structurally infeasible.",
      "compensating_control": "Per-product weekly OKRs reviewed at planning.",
      "approved_by": "EM + PO (named)", "approved_on": "2026-04-01", "review_on": "2026-10-01",
      "applied_since_sprint": 1, "added_at": "2026-04-01T09:00:00-06:00"
    }
  ]
}
FieldPurpose
account / account_slugDisplay name + folder name
engagement / engagement_slugDisplay name + folder name
fireflies_title_hintsPartial-string matches the fetcher uses when a ceremony has no fireflies_id
team_sizeSurfaced in the internal report's profile section
team_rosterOptional. Delivery team + aliases; gates participation scoring & per-person feedback
exceptionsOptional. Signed-off per-criterion exceptions (tier / scope / approver)

manifest.json — one per sprint

{
  "sprint_name": "Sprint 02 26.2.2",
  "sprint_number": 2,
  "team_name": "Digital Service - Acme Robotics",
  "start_date": "2026-04-08",
  "end_date": "2026-04-21",
  "ceremonies": [
    {"type": "sprint_planning",    "fireflies_title": "FW Sprint Planning", "fireflies_id": "01KNMA..."},
    {"type": "backlog_refinement", "fireflies_title": "ACR | Backlog Refinement", "date": "2026-04-16"},
    {"type": "demo",               "fireflies_title": "ACR | Sprint Review",  "date": "2026-04-22"},
    {"type": "retrospective",      "fireflies_title": "ACR | Retrospective",  "date": "2026-04-23"},
    {"type": "daily_standup",      "fireflies_title": "Digital Service | Daily Standup"}
  ]
}

Ceremony matching, in priority order: (1) fireflies_id — exact, no ambiguity; (2) fireflies_title + date — fuzzy title within a few days; (3) daily_standup is special-cased — every matching transcript between start_date and end_date is bundled into one stitched transcript. Omit a ceremony entirely when it wasn't held; the renderer shows "not held" everywhere it matters. Valid type values: sprint_planning, backlog_refinement, daily_standup, demo, retrospective.

outcomes — delivery results (optional, in the sprint manifest)

Add an outcomes block to a sprint's manifest.json to light up the Predictability panel and the sprint-goal-met maturity gate. These are board facts, not transcript evidence — so the audit measures outcomes, not just ceremony mechanics.

"outcomes": {
  "committed_points": 40,
  "completed_points": 32,
  "scope_added_points": 8,
  "sprint_goal_met": true,
  "cycle_time_days_median": 3.5,
  "board_url": "https://your-board/sprint-42"
}

All fields optional. committed+completed → commitment reliability; scope_added → scope churn; sprint_goal_met → a missed goal blocks Foundation maturity for that sprint. When the block is absent the panel and the gate are simply omitted — no penalty.

What's committed vs not. Portable code (src/, framework/, reports/, tests/) and the single fictional example-client are committed. Real per-account data (audits/<account>/), rendered reports, and secrets (config/config.yaml) are gitignored — they live under your Desktop folders, never in the repo.

8Troubleshooting

SymptomFirst thing to check
ModuleNotFoundError: sprint_auditYou ran the runner without PYTHONPATH=src. Run from tools/sprint-audit/ with PYTHONPATH=src python -m sprint_audit…
Account '<X>' has no sprint_audit blockAdd the accounts.<account>.sprint_audit block to your manifest, or run /em-setup.
Fireflies is not configuredconfig/config.yaml exists and has fireflies_api_key.
manifest.json not foundThe sprint dir needs a manifest.json — the example ships as example_manifest.json and must be copied/renamed.
narrative validation: …The model-written audit/<ceremony>.narrative.json doesn't match the schema. Fix it and re-run score.
evaluation validation: …Usually synthesis wasn't patched in. Confirm audit/synthesis.json exists and re-run finalize.
Reports look staleRe-run finalize — it re-renders from scratch. The orchestrator is idempotent.
A real team member shows under "external"Add their missing alias (a name variant or email) to team_roster and re-run finalize.