EDPA Playbook — From zero to first PI
Complete guide for deploying the EDPA methodology on a new project
Prerequisites
Tools
| Tool | Minimum version | Check |
|---|---|---|
| Python | 3.10+ | python3 --version |
| pyyaml + openpyxl + ruamel.yaml | any | pip install pyyaml openpyxl ruamel.yaml |
| Git | 2.30+ | git --version |
| GitHub CLI (gh) | 2.40+ | gh --version — optional |
EDPA V2 runs purely locally on top of git. gh is needed only if you want the optional PR-signal sync (the edpa-contribution-sync.yml workflow) — see 2.3 Optional GitHub PR-signal sync.
Optional: GitHub CLI scopes for PR-signal sync
If you enable the optional PR-signal sync, the ordinary repo scope is enough:
gh auth login # ordinary repo scope is enough
gh auth status admin:org, project) from EDPA V1 are no longer needed — GitHub Projects and org Issue Types were removed in 2.0.0.Team and backlog
Before you start, you need:
- A defined team: names, roles (Arch, Dev, DevSecOps, PM, QA, BO), FTE, capacities
- At least a basic backlog (1 Epic, 2-3 Features, 5-10 Stories)
- A Git repository (a GitHub remote is optional)
role: in people.yaml is a free-form
label used for reports (timesheet column, Excel exports) and as
input to the synthetic calibration corpus. Engine signal weights
apply uniformly regardless of a person's role —
commit_author=4.00, pr_reviewer=2.17,
issue_comment=1.46, etc. are the same for everyone.
Phase 1: Infrastructure (Day 1, ~1 hour)
Path A: Claude Code (recommended)
In a terminal with Claude Code installed:
/edpa:setup --with-ci --with-hooks --with-rules Claude Code (the /edpa:setup skill) runs steps 1.1-1.4 automatically — it vendors the engine into .edpa/engine/, seeds the configuration and id_counters.yaml, and optionally installs git hooks, the PR-signal CI workflow, and .claude/rules/. Idempotent — re-running breaks nothing.
Path B: Manual CLI
The steps below describe the manual procedure without Claude Code.
1.1 Install EDPA and vendor the engine
Variant A: Shell installer (recommended)
cd my-project
curl -fsSL https://edpa.technomaton.com/install.sh | sh Variant B: project_setup.py
project_setup.py vendors the engine + seeds the configs + id_counters.yaml. Extra flags install git hooks, the contribution-sync CI workflow, and .claude/rules/. No --org/--repo — no GitHub provisioning.
python3 .edpa/engine/scripts/project_setup.py --with-ci --with-hooks --with-rules Resulting structure:
my-project/
.edpa/
config/
edpa.yaml # Project + governance (source of methodology version)
people.yaml # Team and capacities (teams, people)
cw_heuristics.yaml # CW signal weights
id_counters.yaml # ID counters (S-1, E-1, F-1, ...)
backlog/
initiatives/ # 1 file = 1 initiative (.md with YAML frontmatter)
epics/ # 1 file = 1 epic
features/ # 1 file = 1 feature
stories/ # 1 file = 1 story
iterations/ # PI and iterations (PI-2026-1.yaml, PI-2026-1.1.yaml, ...)
reports/ # Generated reports + edpa_results.json per iteration
snapshots/ # Frozen iteration snapshots
data/ # Ground truth for calibration (post-PI)
engine/ # VENDORED engine (installed by project_setup.py)
VERSION
scripts/
engine.py # EDPA computational core
backlog.py # Backlog management (add, tree, show, wsjf, validate)
detect_contributors.py # evidence[] -> contributors[] (cw)
calibrate_signals.py # Signal-weights calibration (MAD)
reports.py # Timesheets + PI summary + xlsx
board.py # Local HTML Kanban board
capacity_override.py # Per-person/iteration capacity override
sync_pr_contributions.py # Materialize PR-thread signals (optional CI)
... # other helper scripts + hooks/
templates/
people.yaml.tmpl
cw_heuristics.yaml.tmpl
edpa.yaml.tmpl
.github/workflows/ # ONLY with --with-ci
edpa-contribution-sync.yml # After PR merge: PR-thread signals -> evidence[]
.claude/rules/ # ONLY with --with-rules (architectural rules)
docs/ # Documentation .edpa/engine/scripts/*.py (NOT .claude/edpa/scripts/, NOT plugin/edpa/scripts/). Templates live in .edpa/engine/templates/.1.2 Configure the team and capacities (people.yaml)
project_setup.py already seeds .edpa/config/people.yaml from the template. Or manually:
cp .edpa/engine/templates/people.yaml.tmpl .edpa/config/people.yaml Edit .edpa/config/people.yaml. This is where teams + people live — no separate registry. Set cadence (iteration_weeks / pi_iterations) per-PI in the pi: block of .edpa/iterations/PI-*.yaml:
teams:
- id: "My Team"
planning_factor: 0.8 # Plan to 80% of capacity
people:
- id: alice
name: "Alice Smith"
role: Arch # Arch, Dev, DevSecOps, PM, QA, BO
team: "My Team"
fte: 0.5
capacity_per_iteration: 20 # hours: FTE x 40 for a 1-week iter.
# (40 for a 2-week iter.)
email: "alice@example.com"
availability: confirmed # confirmed, partial, unavailable
- id: bob
name: "Bob Johnson"
role: Dev
team: "My Team"
fte: 1.0
capacity_per_iteration: 40 # FTE x 40 for a 1-week iter.
email: "bob@example.com"
availability: confirmed
# Add more team members... planning_factor is a planning heuristic (plan to 80%). The EDPA engine always calculates against 100% of capacity — the buffer absorbs unplanned work. A person's github: field is optional — it is used only by the optional PR-signal sync to map a GH login to people[].id.registry/capacity file. The whole team and its capacities live in people.yaml.capacity_per_iteration in people.yaml. To adjust it for a single iteration (PTO, overtime, ramp) use the /edpa:capacity <iteration> --add --person <id> --hours 20|+8|-12 --note "…" command (also --list / --remove). The override is stored in the iteration YAML people: block and shows in the report as capacity / capacity_baseline / capacity_override. Closed iterations reject overrides — set them before closing (Stage 1 of /edpa:close-iteration).1.3 Configure the project + governance (edpa.yaml)
project_setup.py seeds .edpa/config/edpa.yaml from the template (NOT project.yaml — that does not exist in V2). Or manually:
cp .edpa/engine/templates/edpa.yaml.tmpl .edpa/config/edpa.yaml Edit .edpa/config/edpa.yaml:
project:
name: "My Project" # ← Display name (the only required field)
description: ""
domain: "myproject.com"
# Optional: funding (grants) + organizations (for audit/billing)
governance:
# Auto-stamped to the plugin version by the installer.
methodology: "EDPA v2.11.1"
# Single computation path since v1.14 (no simple/full/gates mode selector,
# no audit_mode — snapshots always carry the full signals[] audit trail).
naming:
pi_pattern: "PI-{year}-{pi_num}" # PI-2026-1
iteration_pattern: "PI-{year}-{pi_num}.{iter_num}" # PI-2026-1.3
branch_pattern: "{type}/{item_id}-{description}" # feature/S-200-omop-parser
item_prefixes:
initiative: "I"
epic: "E"
feature: "F"
story: "S"
task: "T"
defect: "D"
event: "EV" calculation_mode (simple/full) and audit_mode fields. The engine has a single computation path (JS × CW) and snapshots always carry the full audit trail.1.4 Configure CW signal weights (cw_heuristics.yaml)
project_setup.py seeds .edpa/config/cw_heuristics.yaml (NOT heuristics.yaml). Or manually:
cp .edpa/engine/templates/cw_heuristics.yaml.tmpl .edpa/config/cw_heuristics.yaml Default weights are calibrated by Monte Carlo simulation. For most teams the defaults are fine to start with. Calibration against your own data is done after the first PI (see Phase 3).
EDPA V2 is evidence-driven: cw[person, item] = contribution_score / Σ contribution_score, where contribution_score = Σ signal_weight. The key default signal weights:
| Signal | Weight | Meaning |
|---|---|---|
commit_author | 4.00 | Commit with the ID in branch, title, or message |
pr_reviewer | 2.17 | PR review submitted (excluding self) |
issue_comment | 1.46 | Issue/PR comment (excluding bots) |
Contributor role weights (--contributor PERSON:ROLE:CW) — owner 1.0 / key 0.6 / reviewer 0.25 / consulted 0.15; evidence_threshold 1.0. cw_heuristics.yaml additionally carries gate_weights for Feature/Epic/Initiative — a status transition on the parent distributes its Job Size across the lifecycle (sum = 1.0 per type).
1.5 Configure iterations (iterations/)
PI and iterations live in .edpa/iterations/ as separate files:
# .edpa/iterations/PI-2026-1.yaml — PI-level metadata
pi:
id: PI-2026-1
status: active
iteration_weeks: 1 # AI-native default
pi_iterations: 5 # 4 delivery + 1 IP
start_date: 2026-04-01
end_date: 2026-05-05 # .edpa/iterations/PI-2026-1.1.yaml — per-iteration data
iteration:
id: PI-2026-1.1
pi: PI-2026-1
start_date: 2026-04-01
end_date: 2026-04-07
weeks: 1
status: planned
# planning / delivery / stories_detail follow as the iteration runs Create PI-2026-1.2.yaml through PI-2026-1.5.yaml the same way; the last gets type: IP (Innovation & Planning). Continuity (no gaps / overlaps, weeks × 7 ≈ date span) is enforced by validate_iterations.py and the automatic PostToolUse hook.
github_org, github_project_number, sync_interval) — V2 is local-first, no GitHub Project is provisioned.1.6 Populate the backlog
EDPA uses a file-per-item structure — every work item is a separate .md file with YAML frontmatter (in V2 NOT .yaml):
.edpa/
config/
edpa.yaml # project + governance
people.yaml # team and capacities
cw_heuristics.yaml # CW signal weights
id_counters.yaml # ID counters
backlog/
initiatives/
I-1.md # 1 file = 1 initiative
epics/
E-1.md # 1 file = 1 epic
features/
F-1.md # 1 file = 1 feature
stories/
S-1.md # 1 file = 1 story
iterations/
PI-2026-1.1.yaml # iteration plan Example story file (.edpa/backlog/stories/S-1.md — YAML frontmatter + optional body):
---
id: S-1
type: Story
title: "Parser implementation"
status: Backlog
parent: F-1
js: 5
assignee: bob
iteration: PI-2026-1.1
---
Optional description / acceptance criteria in Markdown. Adding a new item (LOCAL — no GitHub calls):
# Claude Code:
/edpa:add # interactively adds one item (Initiative/Epic/Feature/Story/Defect/Event/Risk)
# CLI:
python3 .edpa/engine/scripts/backlog.py add --type Story --parent F-1 \
--title "..." --js 5 --assignee bob --iteration PI-2026-1.1
python3 .edpa/engine/scripts/backlog.py add --type Epic --parent I-1 \
--title "..." --js 13 --bv 13 --tc 8 --rr-oe 5 backlog.py add is purely local in V2 (no gh): the ID is allocated from id_counters.yaml (S-42, E-15, I-3), the parent hierarchy is validated, the YAML is written under .edpa/backlog/, and the change is auto-committed as feat(<ID>):. PR-derived signals arrive later via the optional contribution-sync workflow.
add (creating a GH issue, sub-issue API, rewriting the title to "{ID}: {title}"). In V2 add is local-only — the V1 "strictly GH-first" claim NO LONGER holds.Contributors can be assigned right at creation time (per-item CW share):
python3 .edpa/engine/scripts/backlog.py add --type Story --parent F-1 --title "..." --js 5 \
--contributor bob:owner:0.7 --contributor carol:reviewer:0.3
# Format PERSON:ROLE:CW, ROLE ∈ {owner,key,reviewer,consulted}, CW ∈ [0,1] Verify integrity:
python3 .edpa/engine/scripts/backlog.py validate Show the hierarchy:
python3 .edpa/engine/scripts/backlog.py tree
python3 .edpa/engine/scripts/backlog.py tree --level epic Show WSJF prioritization:
python3 .edpa/engine/scripts/backlog.py wsjf
python3 .edpa/engine/scripts/backlog.py wsjf --level feature Show item detail:
python3 .edpa/engine/scripts/backlog.py show S-1
python3 .edpa/engine/scripts/backlog.py show E-1 1.7 Verify setup
# Demo EDPA engine (no real data)
python3 .edpa/engine/scripts/engine.py --demo
# Backlog validation
python3 .edpa/engine/scripts/backlog.py validate
# Project status
python3 .edpa/engine/scripts/backlog.py status
# Visual HTML board (local-first replacement for the Projects board)
python3 .edpa/engine/scripts/board.py --output .edpa/board.html 1.8 Commit the base configuration
backlog.py add commits items automatically. Commit the initial config and the vendored engine:
git add .edpa/
git commit -m "feat(edpa): initial EDPA V2 setup for my-project"
git push origin main # optional, if you have a GitHub remote issue_types.py setup --org), §1.8 GitHub Project (project_setup.py --org/--repo/--project-title, custom fields, sub-issues), §1.9 Project views (project_views.py, create_project_views.py, Playwright). V2 replaces the board view with the local board.py / /edpa:board.Phase 2: First iteration (Week 1)
2.1 Iteration Planning
- Capacity confirmation — team confirms availability for the iteration
- Story selection — from the backlog by WSJF order, up to 80% of capacity
# WSJF ranking features
python3 .edpa/engine/scripts/backlog.py wsjf --level feature
# Tree for a specific iteration
python3 .edpa/engine/scripts/backlog.py tree --iteration PI-2026-1.1 - Assignee allocation — every story must have an assignee
- Backlog update —
iteration: PI-2026-1.1on the selected stories
2.2 Daily work
The branch-naming convention {type}/{ITEM}-{description} is in V2 enforced by git hooks (--with-hooks), no longer by CI:
# Format: {type}/{ITEM_ID}-{description}
git checkout -b feature/S-200-omop-parser
git checkout -b feature/F-102-anon-engine
git checkout -b bugfix/S-215-upload-validation
git checkout -b chore/T-050-ci-pipeline Allowed types: feature, bugfix, hotfix, chore
Allowed prefixes: S (Story), F (Feature), E (Epic), T (Task), D (Defect), I (Initiative), EV (Event)
Git hooks (--with-hooks) materialize evidence locally, without GitHub:
| Hook | What it does |
|---|---|
| pre-commit | ID safety — reference check |
| commit-msg | Requires an item reference or no-ticket: |
| post-commit | Materializes commit_author, yaml_edit and state_transition into evidence[] (local_evidence.py) |
| pre-push | Checks for ID collisions |
ref) run /edpa:materialize (the MCP tool edpa_materialize) or the CLI python3 .edpa/engine/scripts/local_evidence.py --materialize [--iteration PI-2026-1.1 | --all-iterations]. The EDPA_NO_LOCAL_EVIDENCE=1 variable disables only the automatic hook — --materialize ignores it.Hook registration is idempotent and self-refreshing (the SessionStart auto-update re-registers them after a plugin update). Foreign (non-EDPA) hooks are never clobbered — they are skipped with a warning. Check hook state (active / missing / foreign / lefthook) anytime with the read-only doctor:
python3 .edpa/engine/scripts/project_setup.py --check-hooks
python3 .edpa/engine/scripts/project_setup.py --refresh-hooks # register only lefthook.yml (lefthook owns .git/hooks/), EDPA does not write .git/hooks/ — instead it prints a paste-ready snippet to add to lefthook.yml, then run lefthook install. Note: pre-push must have use_stdin: true or lefthook hangs.pre-commit:
commands:
edpa-id-safety:
run: sh .edpa/engine/scripts/hooks/pre-commit-id-safety
commit-msg:
commands:
edpa-ticket-attached:
run: sh .edpa/engine/scripts/hooks/commit-msg-ticket-attached {1}
post-commit:
commands:
edpa-evidence:
run: sh .edpa/engine/scripts/hooks/post-commit-evidence
pre-push:
commands:
edpa-id-safety:
run: sh .edpa/engine/scripts/hooks/pre-push-id-safety {1} {2}
use_stdin: true Commit convention:
git commit -m "feat(S-200): implement OMOP CDM parser"
git commit -m "fix(S-215): validate upload file size"
git commit -m "test(S-201): add unit tests for parser"
git commit -m "docs(E-10): update epic hypothesis" The EDPA engine recognizes S-XXX, F-XXX, E-XXX references in commits (and optionally in PRs) as evidence.
Pull Request workflow (optional, only with a GitHub remote):
git push origin feature/S-200-omop-parser
gh pr create --title "S-200: OMOP CDM parser implementation" \
--body "Closes #42
## Changes
- Implemented OMOP CDM parser
- Added schema validation
## Testing
- Unit tests: 15 passing
" PR review = a signal for EDPA. For PR-thread signals (pr_reviewer, issue_comment) to land in evidence[], the optional contribution-sync workflow is required — see 2.3.
2.3 Optional GitHub PR-signal sync
EDPA V2 works purely locally — the post-commit hook and /edpa:materialize write commit_author, yaml_edit and state_transition into evidence[]; the engine is a pure reader. GitHub is optional and serves only to materialize PR-thread signals.
With --with-ci, the single V2 workflow .github/workflows/edpa-contribution-sync.yml is installed. After a PR merge it runs sync_pr_contributions.py, which extracts signals (pr_reviewer, issue_comment) from the PR thread and writes them into the relevant items' evidence[]. It requires the EDPA_TOKEN secret (see docs/edpa-token-setup.md). Flow metrics can be read via the MCP tool edpa_flow_metrics.
sync.py (pull/push/diff/status/conflicts/--mock) — it does not exist in V2. Likewise the edpa-sync-projects-to-git.yml and edpa-sync-git-to-projects.yml workflows. No GitHub Project is propagated. For a board, use the local /edpa:board.2.4 Iteration Close
At the end of each iteration (1 week AI-native / 2 weeks classic):
Claude Code (recommended):
/edpa:close-iteration PI-2026-1.1 Claude Code (the close-iteration skill) prepares capacity, runs the EDPA engine, and generates the reports automatically.
Manual CLI:
python3 .edpa/engine/scripts/engine.py --edpa-root .edpa --iteration PI-2026-1.1
# optional: --output path/edpa_results.json Outputs:
.edpa/reports/iteration-PI-2026-1.1/
edpa_results.json # Complete calculation (JSON)
timesheet-alice.md # Timesheet per person (DerivedHours > 0)
timesheet-team.md # Aggregated team rollup
edpa-results.xlsx # Team Summary + Item Costs (Excel)
.edpa/snapshots/
PI-2026-1.1.json # Frozen snapshot (audit trail) timesheet-<id>.md (in V2 NO LONGER vykaz-*.md).Calculation (the single path since v1.14): score = JS × CW, hours = (score / Σ score) × capacity. No --mode simple|full and no Role Strength.
Score = JobSize × CW
DerivedHours = (Score / ΣScores) × Capacity CW is a per-item normalized share (Σ across persons on an item = 1.0), computed in detect_contributors.py from evidence (commit author, PR reviewer, issue comment, /contribute directives). The engine consumes CW verbatim.
Invariants (engine checks automatically):
- Sum of a person's
DerivedHours== their capacity (exactly) - Sum of ratios == 1.0
- No negative hours
- If an invariant fails, the engine reports
FAIL
2.5 Update the iteration plan
After closing an iteration, create or update .edpa/iterations/PI-2026-1.1.yaml:
iteration:
id: PI-2026-1.1
pi: PI-2026-1
start_date: 2026-04-01
end_date: 2026-04-14
weeks: 2
status: closed
planning:
capacity: 380
planning_factor: 0.80
planned_sp: 24
stories:
- S-200
- S-201
- S-202
delivery:
delivered_sp: 24
predictability: "100%"
velocity: 24
spillover: []
unplanned: []
notes: "First iteration -- full delivery." Phase 3: PI Close (after 4-5 iterations)
Path A: Claude Code (recommended)
/edpa:calibrate Claude Code (the /edpa:autocalib skill) runs calibration of the signal weights via a Monte Carlo + coordinate-descent optimizer, evaluates MAD, and proposes adjustments to cw_heuristics.yaml. To generate reports:
/edpa:reports PI-2026-1 Path B: Manual CLI
3.1 Retrospective
At the end of a PI (after 4 delivery iterations + 1 IP iteration):
- Walk through auto-detection vs. reality on 5-10 stories
- For each story compare: who was auto-detected, what CW the engine assigned, does it match reality?
3.2 Ground truth for calibration (optional)
The calibrator runs against a Monte Carlo synthetic corpus and
searches for MAD-optimal signal weights in
.edpa/config/cw_heuristics.yaml. A
ground_truth.yaml is not required — you can
run it anytime, even before the first PI close.
If you want to calibrate against real records from a PI retro,
write team-confirmed CW corrections into
.edpa/data/ground_truth.yaml:
records:
- person: alice # id from .edpa/config/people.yaml
item: S-200
iteration: PI-2026-1.1
confirmed_cw: 0.35 # PM/retro-confirmed share (0..1)
notes: "design review + arch. decisions"
- person: bob
item: S-200
iteration: PI-2026-1.1
confirmed_cw: 0.65
notes: "implementation + tests"
# ... at least 20 records for reliable calibration 3.3 Calibrating CW signals
Claude Code (recommended):
/edpa:calibrate Manual CLI:
python3 .edpa/engine/scripts/calibrate_signals.py \
--ground-truth .edpa/data/ground_truth.yaml \
--heuristics .edpa/config/cw_heuristics.yaml calibrate_signals.py (the /edpa:calibrate skill) runs in two phases: a random Monte Carlo sample across the signal weights → coordinate-descent (Nelder-Mead) refinement around the best candidates. The metric is MAD on a synthetic corpus; lower MAD = better calibration.
Output (illustrative):
MAD=0.041200
RECORDS=20
TOTAL_DEVIATION=0.824000 MAD interpretation (Mean Absolute Deviation):
| MAD | Rating | Action |
|---|---|---|
| < 0.03 | Excellent | No change |
| 0.03 - 0.06 | Good | Minor corrections (optional) |
| 0.06 - 0.10 | Acceptable | Consider recalibrating signal weights |
| > 0.10 | Poor | Calibration required |
Correcting signal weights:
If MAD > 0.06, analyze where the largest deviations are:
- Look at records where
abs(auto_cw - confirmed_cw)is highest - Identify patterns by role (typically BO, PM, Arch — strategic roles are under-credited by git)
- Adjust the
signals:weights in.edpa/config/cw_heuristics.yaml - Re-run calibration
evaluate_cw.py (replaced by calibrate_signals.py / /edpa:calibrate) and the heuristics.yaml file (replaced by cw_heuristics.yaml).3.4 Planning the next PI
- New epics/features — add via
backlog.py add(auto-commit) into.edpa/backlog/ - WSJF prioritization:
python3 .edpa/engine/scripts/backlog.py wsjf - Capacity planning — update
.edpa/config/people.yaml(FTE changes, availability) - Create new iteration files in
.edpa/iterations/for the new PI
Phase 4: Continuous operation
With Claude Code (recommended)
Every iteration:
/edpa:close-iteration PI-2026-1.X # close the iteration
/edpa:reports PI-2026-1.X # generate reports Every PI:
/edpa:close-pi PI-2026-1 # close the PI: guard iterations, status, rollup
/edpa:calibrate # calibrate signal weights Each iteration (1 week AI-native / 2 weeks classic)
- Planning — pick stories, assign assignees
- Daily work — branch naming, commits with references, (optional) PR review
- Iteration Close — EDPA engine, report generation
- Review — team checks the timesheets, reports corrections (manual CW override via
--contributor//contribute)
Each PI (5 weeks AI-native / 10 weeks classic)
- Retrospective — auto-detected CW vs. reality
- Ground truth — record at least 20 new entries
- CW calibration —
calibrate_signals.py, evaluate MAD - Velocity trend — compare delivery across iterations
- Predictability — (delivered_sp / planned_sp) across iterations
# Status for a whole iteration
python3 .edpa/engine/scripts/backlog.py status --iteration PI-2026-1.3
# Overall project status
python3 .edpa/engine/scripts/backlog.py status
# Capacity override (e.g. extra hours outside evidence)
python3 .edpa/engine/scripts/capacity_override.py PI-2026-1.1 --add --person bob --hours 12
# Velocity / flow reports
python3 .edpa/engine/scripts/reports.py PI-2026-1.1 Optional automation via GitHub Actions
V2 has a single optional workflow (only with --with-ci):
| Workflow | Trigger | What it does |
|---|---|---|
edpa-contribution-sync.yml | after PR merge | sync_pr_contributions.py materializes PR-thread signals (pr_reviewer, issue_comment) into evidence[] |
edpa-branch-check.yml, edpa-iteration-close.yml, edpa-sync-projects-to-git.yml, edpa-sync-git-to-projects.yml. The branch convention is now guarded by a local git hook, iteration close runs locally, and bidirectional sync does not exist.Checklist — What to have ready
Day 1
- Engine vendored into
.edpa/engine/(/edpa:setuporproject_setup.py) .edpa/config/people.yaml— team with roles, FTE, capacities (teams + people).edpa/config/edpa.yaml— project name, governance, naming.edpa/config/cw_heuristics.yaml— default signal weights (from the template).edpa/config/id_counters.yaml— seeded.edpa/iterations/— PI + iterations with dates- Backlog populated via
backlog.py add(at least 1 Epic, 3 Features, 10 Stories) backlog.py validatepasses without errorsengine.py --demoruns successfully- (optional) git hooks installed (
--with-hooks), contribution-sync CI (--with-ci)
Week 1
- Team works with the branch-naming convention (
feature/S-XXX-description) - Commits reference work items (
feat(S-XXX): ...) - post-commit hook records
commit_authorevidence - (optional) PR reviews are happening and contribution-sync materializes them
End of iteration 1
- EDPA engine run for the iteration
edpa_results.jsongenerated in.edpa/reports/iteration-<ID>/- Timesheets
timesheet-<id>.mdgenerated (per-person) - All invariants passed (
all_invariants_passed: true) - Snapshot frozen in
.edpa/snapshots/ - Team reviewed the results
End of iterations 2-4
- Velocity stable (deviation < 20%)
- Predictability > 80% (delivered / planned)
End of PI 1
- Ground truth recorded (min. 20 entries)
- CW calibration completed (
calibrate_signals.py//edpa:calibrate) - MAD evaluated (target: < 0.06)
- Signal weights adjusted in
cw_heuristics.yaml(if MAD > 0.06) - PI 2 planning — new epics, WSJF, capacities
- New iteration files in
.edpa/iterations/for the new PI
CLI Reference
python3 .edpa/engine/scripts/<script>.py.Claude Code commands (recommended)
| Command | Description |
|---|---|
/edpa:setup --with-ci --with-hooks --with-rules | Vendors the engine, seeds configs + id_counters, optional hooks/CI/rules |
/edpa:add | Add a backlog item (local, auto-commit) |
/edpa:close-iteration PI-2026-1.X | Close an iteration — capacity, EDPA engine, reports |
/edpa:reports PI-2026-1.X | Generate timesheets and PI summary |
/edpa:close-pi PI-2026-1 | Close a PI — guard iterations, flip pi.status, rollup report |
/edpa:board | Visual HTML Kanban board (local) |
/edpa:calibrate | Calibrate signal weights — MAD, propose cw_heuristics.yaml adjustments |
engine.py — EDPA computational core
| Command | Description |
|---|---|
engine.py --demo | Demo with built-in sample data |
engine.py --edpa-root .edpa --iteration PI-2026-1.3 | Full EDPA calculation for an iteration (reads backlog/config/heuristics from .edpa) |
engine.py --edpa-root .edpa --iteration ID --output path/edpa_results.json | Custom output path |
engine.py --status | Configuration state |
backlog.py — Backlog management
| Command | Description |
|---|---|
backlog.py add --type Story --parent F-1 --title "..." --js 5 --assignee bob --iteration PI-2026-1.1 | Add an item locally (ID from id_counters.yaml, auto-commit feat(<ID>):) |
backlog.py add --type Epic --parent I-1 --title "..." --js 13 --bv 13 --tc 8 --rr-oe 5 | Add an Epic with WSJF metrics |
backlog.py add ... --contributor PERSON:ROLE:CW | Add a contributor (owner/key/reviewer/consulted, CW ∈ [0,1]) |
backlog.py tree | Show the full hierarchy (I → E → F → S) |
backlog.py tree --level epic|feature|story | Filter by level |
backlog.py tree --iteration PI-2026-1.1 | Filter stories by iteration |
backlog.py show S-1 | Item detail |
backlog.py status [--iteration PI-2026-1.1] | Project / iteration status |
backlog.py wsjf [--level feature] | WSJF prioritization |
backlog.py validate | Backlog integrity check |
reports.py — Timesheets and PI summary
| Command | Description |
|---|---|
reports.py PI-2026-1.1 | Per-person timesheet-<id>.md + timesheet-team.md + xlsx |
reports.py --pi PI-2026-1 | Aggregate all iterations under a PI |
reports.py PI-2026-1.1 --out path/ | Custom output directory |
board.py — Local HTML board
| Command | Description |
|---|---|
board.py --output .edpa/board.html | Generate a Kanban board from .edpa/backlog/ |
board.py --iteration PI-2026-1.4 --open | Filter by iteration + open in the browser |
capacity_override.py — Capacity override
| Command | Description |
|---|---|
capacity_override.py PI-2026-1.1 --list | List existing overrides |
capacity_override.py PI-2026-1.1 --add --person bob --hours 12 | Add an override |
capacity_override.py PI-2026-1.1 --remove --person bob | Remove an override |
detect_contributors.py / calibrate_signals.py
| Command | Description |
|---|---|
detect_contributors.py | Convert evidence[] → contributors[] (cw) from real signals |
calibrate_signals.py --ground-truth .edpa/data/ground_truth.yaml --heuristics .edpa/config/cw_heuristics.yaml | Calibrate signal weights (MAD) |
sync_pr_contributions.py (optional, CI)
| Command | Description |
|---|---|
sync_pr_contributions.py | Materializes PR-thread signals (pr_reviewer, issue_comment) into evidence[]. Runs edpa-contribution-sync.yml after a PR merge; requires EDPA_TOKEN. |
sync.py (bidirectional sync — does not exist in V2), issue_types.py (org Issue Types), project_setup.py --org/--repo/--project-title (GitHub Project provisioning — in V2 project_setup.py only vendors the engine), project_views.py + create_project_views.py (Project views / Playwright), evaluate_cw.py (replaced by calibrate_signals.py).Architecture
V2 is local-first: .edpa/ (backlog in .md + config) is the single source of truth, git is the audit trail. GitHub is optional — the only inbound flow is the one-way materialization of PR-thread signals into evidence[].
.edpa/ (git) -- SINGLE SOURCE OF TRUTH
backlog/**/*.md + config/ + iterations/
|
+-----------------+-----------------+
| | |
EDPA engine backlog.py board.py
(compute) (add/tree/wsjf) (HTML board)
| |
+-----+-----+ v
| | .edpa/board.html
reports/ snapshots/ (local)
timesheet-*.md *.json
xlsx Data flow
(optional) GitHub PR thread
reviews / comments
|
| edpa-contribution-sync.yml (after PR merge)
| sync_pr_contributions.py
v -- ONE-WAY: signals -> evidence[]
post-commit hook / /edpa:materialize
local_evidence.py --materialize (dedup by ref)
| writes commit_author, yaml_edit, state_transition
v -- into evidence[]
+-------------------------------------+
| .edpa/ (git) -- SOURCE OF TRUTH |
| backlog *.md config iterations |
| evidence[] -- single signal source|
+-------------------------------------+
|
aggregate_signals (skip weight-0)
evidence[] -> contributors[] (cw)
|
+-----+-----------------------+
| | |
EDPA engine reports.py board.py
(pure reader) timesheets HTML board
|
reports/ + snapshots/ + xlsx Evidence detection
EDPA is evidence-driven. cw[person, item] = contribution_score / Σ_persons contribution_score, where contribution_score = Σ signal_weight. Default signal weights (cw_heuristics.yaml):
| Signal | Weight | Source |
|---|---|---|
commit_author | 4.00 | commit with the ID (local git, post-commit hook) |
pr_reviewer | 2.17 | PR review submitted (excluding self) |
issue_comment | 1.46 | issue/PR comment (excluding bots) |
Local signals (commit_author, yaml_edit, state_transition) are materialized into evidence[] by the post-commit hook and /edpa:materialize; the engine reads them only from evidence[] (no git scan at compute time). PR-thread signals (pr_reviewer, issue_comment) arrive only via the optional contribution-sync. A manual /contribute @person weight:X (or --contributor) carries its weight verbatim.
Contributor role weights: owner 1.0 / key 0.6 / reviewer 0.25 / consulted 0.15; evidence_threshold 1.0. Signals aggregate additively into contribution_score per (person, item), then normalize per-item: cw = score / Σ_persons score (Σ across persons = 1.0).
EDPA calculation
The single computation path since v1.14 (no simple/full/gates mode). For each person in an iteration:
- Gather evidence on each item →
contributors[]withcw - For each (person, item) pair:
score = JS × CW - Per person:
ratio_i = score_i / Σ scores - Derived hours:
hours_i = ratio_i × capacity
Σ DerivedHours = capacity per person (exactly, not approximately). Feature/Epic/Initiative status transitions additionally distribute the parent's Job Size via gate_weights.Troubleshooting
gh auth (only the optional PR-signal sync)
The EDPA V2 core does not need gh. If the optional contribution-sync workflow fails on authentication, add the EDPA_TOKEN secret (see docs/edpa-token-setup.md) or locally:
gh auth login # ordinary repo scope is enough
gh auth status admin:org, project) — GitHub Projects and org Issue Types were removed in 2.0.0.Backlog validate — errors
ERROR: S-200 references non-existent feature F-999 Fix:
- Verify that every
feature:reference in stories points to an existing feature - Every story must have a parent feature
- Every feature must have a parent epic
WARNING: S-200 Job Size exceeds maximum (JS=13, max=8) Fix:
- Story JS max 8 (classic 2/10) or 5 (AI-native 1/5)
- Split a large story into smaller ones
EDPA engine — invariant failure
INVARIANT FAILURE -- check results
All invariants passed: NO Possible causes:
- Person has no evidence (not assignee, no commits)
- All CW values are 0 (no evidence above the threshold)
- Capacity is 0
Fix:
- Verify every person has at least one assigned work item
- Verify commits/PRs reference the correct item IDs (S-XXX, F-XXX)
- Verify
.edpa/config/people.yaml— capacities > 0
Branch naming — rejected commit/push (git hook)
Branch name does not follow EDPA convention.
Required format: {type}/{item-id}-{description} In V2 the convention is guarded by a local git hook (--with-hooks), no longer by CI.
Fix:
- Format:
feature/S-200-omop-parser,bugfix/S-215-fix-validation - Type:
feature,bugfix,hotfix,chore - Prefix:
S(Story),F(Feature),E(Epic),T(Task),D(Defect),EV(Event) main,develop,release/*are exceptions (pass without checking)
commit-msg hook — missing item reference
commit-msg: no item reference found Fix:
- Add an item reference to the message:
feat(S-200): ... - Or, for commits without a ticket, use the
no-ticket:prefix
Python — missing dependencies
ERROR: pyyaml required. Install with: pip install pyyaml Fix:
pip install pyyaml openpyxl ruamel.yaml MAD too high (> 0.10)
Possible causes:
- Strategic roles (BO, PM, Arch) have systematically lower auto-CW than reality
- Git measures only commits/PRs, not decision-making, specification, mentoring
Fix:
- Analyze records with the largest
abs(auto_cw - confirmed_cw) - Group by role
- Adjust the
signals:weights in.edpa/config/cw_heuristics.yaml(raise the weight of the signal that represents that role) - Optionally add a manual
/contribute @person weight:Xon items where strategic work is invisible in git - Re-run
calibrate_signals.py
Iteration close — "config not found"
ERROR: .edpa/config/people.yaml not found. Run EDPA setup first. Fix:
Run setup (vendors the engine + seeds the configs):
/edpa:setup
# or
python3 .edpa/engine/scripts/project_setup.py Or copy from the templates in .edpa/engine/templates/*.tmpl:
cp .edpa/engine/templates/people.yaml.tmpl .edpa/config/people.yaml
cp .edpa/engine/templates/cw_heuristics.yaml.tmpl .edpa/config/cw_heuristics.yaml
cp .edpa/engine/templates/edpa.yaml.tmpl .edpa/config/edpa.yaml The files in .edpa/config/*.yaml (not *.yaml.tmpl) must be committed in the repo.
PR-signal sync — missing evidence from a PR
PR-thread signals (pr_reviewer, issue_comment) land in evidence[] only via the optional contribution-sync.
Fix:
- Verify
.github/workflows/edpa-contribution-sync.ymlis installed (/edpa:setup --with-ci) - Verify the
EDPA_TOKENsecret (seedocs/edpa-token-setup.md) - The workflow runs only after a PR merge; before merge, evidence holds only local signals (
commit_author,yaml_edit)
Glossary
| Term | Meaning |
|---|---|
| PI | Planning Interval (AI-native 5 weeks = 4 delivery + 1 IP; classic SAFe 10 weeks) |
| IP | Innovation & Planning (the last iteration of a PI) |
| JS | Job Size — relative work size (Fibonacci) |
| BV | Business Value |
| TC | Time Criticality |
| RR-OE | Risk Reduction & Opportunity Enablement (CLI flag --rr-oe, legacy alias --rr) |
| WSJF | Weighted Shortest Job First = (BV+TC+RR-OE)/JS |
| CW | Contribution Weight — per-item share (0.0 - 1.0); per-item Σ cw = 1.0 |
| Signal | Proof of contribution from git/PR (commit_author, pr_reviewer, issue_comment) with a weight |
| MAD | Mean Absolute Deviation (calibration metric) |
| Evidence | evidence[] on an item — aggregated signals; detect_contributors.py computes contributors[] from them |
| Gate | Status transition on a Feature/Epic/Initiative; distributes the parent JS via gate_weights |
| Ground truth | Team-confirmed reality (used for calibration) |