Cross-Program Capacity Planning
Build a skills matrix and capacity model to route talent gaps across a portfolio.
Introduction
30 Second Summary
You have a full roster and thirteen approved hires, but the same milestone slipped twice. Capability, measured in competent hours with evidence, is the unit that makes the real gap visible.
In this project, you will build a cross-program capability and capacity planning system for a portfolio of four programs. You will size every gap in competent hours, route each one with a date, and ship a tagged release through GitHub and Linear.
What You'll Build
When the build is done, you walk the VP of Engineering through a tagged release where every gap is routed, every departure is answered in writing, and the number she defends to finance is backed by evidence and a defined method.
By the end of this project, you'll have:
- A skills matrix you can hand to a hiring manager showing exactly which people have evidence for which critical skills, with UNPROVEN marks where proof is missing so assumptions become visible conversations.
- A talent plan and capacity model you can present to finance, showing every gap routed to buy, build, or borrow with a date, and the competent-hours number sitting next to headcount so the factor between them is visible.
- A validated report you can defend in a sponsor review, carrying before-and-after critical-skill coverage, attrition scenarios with named mitigations or slips, and documentation a non-engineer owner can follow without a terminal.
- Secret Mission: Build a two-audience executive presentation with lessons learned and an adoption metric that proves the model stays alive after handover.
Are there any prerequisites?
You need a Linear workspace (Free plan is sufficient), a GitHub account with permission to create private repositories, and a text editor such as Cursor or VS Code. Familiarity with program or project delivery concepts like milestones, backlogs, and increments will help.
Before We Start
Before any artifact is built, you need to commit the constraint statement to writing. This is the mandate the entire build is accountable to: what it ships, who the sponsor is, what went wrong with thirteen requisitions, and why competent hours would have caught what headcount missed.
Write Design Commitments, ADRs, and Pre-Artifacts
The sponsor approved thirteen requisitions and nothing moved. The requisitions were sized against headcount, but the constraint is capability. Before any artifact is built, the irreversible design choices need to be locked in writing.
In this step, you will codify three design commitments, write four decision records, draw the end-to-end topology as a Mermaid diagram, and plan the delivery process as documents. Everything stops at the authorization gate. No repository, no tickets, and no pushes until a named operator authorization is recorded.
In this step, get ready to:
- Codify three design commitments and the Linear metadata model.
- Write four ADRs with reversal conditions, draw the topology, and write delivery plans.
- Record the authorization gate in the build log.
Codify the three design commitments and the Linear metadata model
The build rests on three rules that every later artifact must honour. Writing them first means the skills matrix, the gap routing, and the capacity model each have a contract to satisfy rather than a shape to guess.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir capability-planning
cd capability-planning
- Confirm you are inside the project folder by running:
pwd
You should see a path ending in /capability-planning.
- Create commitments.md in your capability-planning folder with the three design commitment statements by pasting the following content:
# Design Commitments
## 1. EVIDENCE-BASED SKILLS
A skill is recorded at the level of evidence: a shipped artifact, a completed review, or a passed assessment. A claim with no evidence is marked UNPROVEN. A self-rating is not evidence.
## 2. GAP ROUTING WITH DATES
Every gap routes to exactly one of buy, build, or borrow, with a need-by date and a time-to-competence estimate. A gap whose time-to-competence exceeds its need-by date cannot be routed to upskilling.
## 3. COMPETENT HOURS
Capacity is people x availability x competence factor. The competence factor is a definition in the ledger, written before any number is computed. A person mid-ramp is not a whole person.
What do these commitments lock in?
EVIDENCE-BASED SKILLS prevents self-ratings from inflating the matrix. Every claim must point to a shipped artifact, a completed review, or a passed assessment. Anything else is marked UNPROVEN.
GAP ROUTING WITH DATES enforces a single route per gap (buy, build, or borrow) and blocks the cheapest route when the math does not work. If the time-to-competence exceeds the need-by date, upskilling is off the table.
COMPETENT HOURS replaces headcount with people x availability x competence factor. This is the unit of measure that makes the mis-sizing visible.
- Now add the Linear metadata model to the same file. Open commitments.md and paste the following content below the COMPETENT HOURS section:
## Linear Metadata Model
Linear does not have dedicated custom fields for issues. The program board uses labels with label groups as structured issue metadata.
### Label Groups
- **Phase**: Mandate, Matrix, Routing, Attrition, Validation, Report
- **Artifact**: demand-baseline, skills-matrix, gap-table, capacity-model, ledger, board-sync, attrition-rerun, report, validation-record, documentation
- **Check**: Check-1, Check-2, Check-3
### Issue Properties
Priority, estimate, due date, milestone, and project carry the remaining structure. This mapping is part of the design, not a workaround.
Why label groups instead of custom fields?
Linear does not have a dedicated custom fields feature for issues. Label groups create one level of nesting in workspace labels, giving you structured metadata (Phase, Artifact, Check) without leaving Linear's native data model.
The remaining structure (priority, estimate, due date, milestone, project) lives in Linear's built-in issue properties.
- Save commitments.md.
- Open the file and confirm you see three numbered commitment headings followed by the Linear Metadata Model section with three label groups (Phase, Artifact, Check) and an Issue Properties section.
Don't see the metadata model section?
Make sure you pasted the Linear Metadata Model block below the COMPETENT HOURS section, not inside it. The ## Linear Metadata Model heading should sit at the same level as the three commitment headings.
Help me fix the structure of my commitments.md file.
✔️ Awesome, I've got everything!
Great. Confirm you have saved commitments.md with both the three design commitments and the Linear metadata model.
ⓧ I'd like to double check the full code
Your commitments.md should contain all of the following in order:
# Design Commitments
## 1. EVIDENCE-BASED SKILLS
A skill is recorded at the level of evidence: a shipped artifact, a completed review, or a passed assessment. A claim with no evidence is marked UNPROVEN. A self-rating is not evidence.
## 2. GAP ROUTING WITH DATES
Every gap routes to exactly one of buy, build, or borrow, with a need-by date and a time-to-competence estimate. A gap whose time-to-competence exceeds its need-by date cannot be routed to upskilling.
## 3. COMPETENT HOURS
Capacity is people x availability x competence factor. The competence factor is a definition in the ledger, written before any number is computed. A person mid-ramp is not a whole person.
## Linear Metadata Model
Linear does not have dedicated custom fields for issues. The program board uses labels with label groups as structured issue metadata.
### Label Groups
- **Phase**: Mandate, Matrix, Routing, Attrition, Validation, Report
- **Artifact**: demand-baseline, skills-matrix, gap-table, capacity-model, ledger, board-sync, attrition-rerun, report, validation-record, documentation
- **Check**: Check-1, Check-2, Check-3
### Issue Properties
Priority, estimate, due date, milestone, and project carry the remaining structure. This mapping is part of the design, not a workaround.
Write four ADRs, draw the topology, and write delivery plans
Each design commitment chose a path and rejected an alternative. A decision record names the alternative, the tradeoff, and the condition that would reverse the choice. Writing the reversal condition forces honesty: if the condition is already true on day one, the record is describing the status quo, not setting a trigger.
- Create the adrs/ directory inside your capability-planning folder by running:
mkdir adrs
- Create adrs/ADR-001.md with the following content:
# ADR-001: Evidence Over Self-Assessment for Skill Claims
**Alternative**: Self-rating survey (faster, supported by HR system).
**Tradeoff**: Evidence collection takes operator time per person per skill.
**Reversal condition**: Reverse if evidence collection consumes more than 15% of the delivery team's available hours per quarter, making the cost exceed the accuracy gain.
**Day-one check**: The build has not yet measured collection cost, so this condition is a genuine future trigger, not a description.
What makes a reversal condition genuine?
A reversal condition is the specific circumstance under which you would undo this decision and go with the alternative. It needs to be falsifiable and not already satisfied.
ADR-001's reversal fires when evidence collection cost exceeds 15% of available hours. On day one, you have not measured that cost yet, so the condition is a genuine future trigger. If the build had already exceeded 15% on day one, the condition would be a description of the present, not a guard for the future.
- Create adrs/ADR-002.md with the following content:
# ADR-002: Competent Hours Over Headcount for Capacity
**Alternative**: FTE count (simpler, understood by finance).
**Tradeoff**: Requires maintaining competence factors per person per skill.
**Reversal condition**: Reverse if competence factor variance across the tracked population falls below 0.1 for two consecutive quarters.
**Day-one check**: Seeded factors are differentiated so variance is above 0.1 on day one. This is a genuine future trigger when real data converges.
- Confirm both ADRs exist by running:
ls adrs/
You should see ADR-001.md and ADR-002.md listed.
- Create adrs/ADR-003.md with the following content:
# ADR-003: Single-Route Constraint per Gap
**Alternative**: Allow parallel routes (e.g., hire and cross-train simultaneously).
**Tradeoff**: Simplifies tracking and accountability at the cost of flexibility.
**Reversal condition**: Reverse if the portfolio formally adopts a contractor-to-hire pipeline where two routes are sequential phases of one intake.
**Day-one check**: No such pipeline exists in the seeded scenario. This is a genuine future trigger.
- Create adrs/ADR-004.md with the following content:
# ADR-004: Metric Ledger in Version Control
**Alternative**: Definitions embedded in spreadsheet formulas (accessible to non-engineers).
**Tradeoff**: Requires operator to commit changes via PR.
**Reversal condition**: Reverse if re-forecast cadence shortens to weekly and the commit-review-merge cycle cannot keep pace.
**Day-one check**: Seeded cadence is quarterly. This is a genuine future trigger.
- Confirm all four ADRs exist by running:
ls adrs/
You should see ADR-001.md, ADR-002.md, ADR-003.md, and ADR-004.md listed.
Missing an ADR file?
Check that you are inside the capability-planning folder when you run ls adrs/. If you see an error, run pwd to confirm your current directory.
Help me find my missing ADR files.
Next, draw the end-to-end topology. The build organises work into six conceptual teams (Skills, Hiring, Upskilling, Capacity, Attrition, Executive Reporting). The Mermaid flowchart maps the data flow between them, with every edge labelled by the artifact it carries.
- Create topology.md with the following Mermaid diagram:
# End-to-End Topology
```mermaid
flowchart TD
subgraph Skills["Skills Team"]
DB[Demand Baseline]
SM[Skills Matrix]
DB -->|roster + milestones| SM
end
subgraph Hiring["Hiring Team"]
GT[Gap Table]
end
subgraph Upskilling["Upskilling Team"]
RT[Route Decisions]
end
subgraph Capacity["Capacity Team"]
CM[Capacity Model]
end
subgraph Attrition["Attrition Team"]
AR[Attrition Re-run]
end
subgraph ExecReporting["Executive Reporting Team"]
VR[Validation Record]
RP[Report]
end
SM -->|skills matrix| GT
GT -->|gap table| RT
RT -->|routed gaps| CM
CM -->|competent hours| AR
AR -->|mitigations + slips| VR
VR -->|check counts| RP
```
What does this topology show?
Each subgraph is a conceptual agent team. Five teams (Skills, Hiring, Upskilling, Capacity, Executive Reporting) run concurrently during the parallel build phase. The Attrition team runs later when departure data arrives.
The labelled edges show which artifact passes between teams. The demand baseline feeds the skills matrix, which feeds the gap table, and so on through to the final report. This is the end-to-end data lineage for the entire build.
- Save topology.md.
- Open topology.md in your editor's Markdown preview (Cmd+Shift+V in VS Code on macOS, Ctrl+Shift+V on Windows). You should see six labelled subgraph boxes connected by arrows, flowing top to bottom from Demand Baseline to Report.
Diagram not rendering in preview?
VS Code and Cursor do not render Mermaid diagrams in the built-in Markdown preview by default. Install a Mermaid preview extension from the marketplace to see the rendered diagram. Alternatively, paste the content between the ```mermaid fences into the live editor at mermaid.live to confirm it renders.
Help me get my Mermaid diagram to render.
Now write the two delivery planning documents. These describe the repository structure and the ticket plan that will be executed once the authorization gate clears.
- Create repo-plan.md with the following content:
# Repository Plan
## Directory Structure
- `adrs/` — decision records
- `docs/` — documentation set
- `data/` — skills matrix, demand baseline, capacity model, gap table, ledger, status snapshot
- `reports/` — report and validation record
## Branch Strategy
Feature branches named after Linear ticket IDs, closing tickets on merge to main.
## Tagging Convention
vX.Y.Z (e.g., v1.0.0 for the validated release).
## File Manifest
Root: `commitments.md`, `topology.md`, `repo-plan.md`, `ticket-plan.md`, `build-log.md`
ADRs: `adrs/ADR-001.md` through `adrs/ADR-004.md`
Data: `data/demand-baseline.md`, `data/skills-matrix.csv`, `data/ledger.yaml`, `data/gap-table.md`, `data/capacity-model.md`, `data/status-snapshot.yaml`
Reports: `reports/report.md`, `reports/validation-record.md`
Docs: `docs/` — documentation set
What does the branch strategy enforce?
Every feature branch is named after its Linear ticket ID. When a PR merges to main, the linked ticket closes automatically. This means the delivery board and the repository stay in sync without manual status updates.
- Save repo-plan.md.
- Create ticket-plan.md with the following content:
# Ticket Plan
## Epics
| Epic | Phase | Budget |
|------|-------|--------|
| E1 | Mandate | 10 min |
| E2 | Matrix | 12 min |
| E3 | Routing | 16 min |
| E4 | Attrition | 12 min |
| E5 | Validation | 6 min |
| E6 | Documentation | 4 min |
## Stories and Tasks
Each story's acceptance criteria reference the validation check it satisfies.
Each task's definition of done names the check and the count it prints.
### Tasks by Epic
- **E1**: commitments, ADRs, topology, repo plan, ticket plan, build log
- **E2**: demand baseline, skills matrix, evidence audit, ledger, Check 1
- **E3**: gap table, routing, capacity model, headcount comparison, board-to-ledger sync, implementation plan, Check 2
- **E4**: departure scenarios, attrition re-run, re-routing, report, Check 3
- **E5**: validation record (three checks under guard rule)
- **E6**: documentation set, tagged release
Why do tasks name a check and a count?
Each task's definition of done references one of the three validation checks (Check 1, Check 2, or Check 3) and the count it prints. This ties every piece of work to a measurable verification. A task is not done until its check prints expected, read, matched, and missing counts against the manifest.
- Save ticket-plan.md.
- Confirm the delivery plans and topology are in place by running:
ls
You should see adrs, commitments.md, constraint-statement.md, repo-plan.md, ticket-plan.md, and topology.md listed.
Record the authorization gate
The repo plan and ticket plan are documents until authorization clears. No repository, no tickets, no pushes, and no project-state change happen without a named operator authorization recorded in writing.
- Create build-log.md with the following content:
# Build Log
## Authorization Gate
No repository, no tickets, no pushes, and no project-state change happen until a named operator authorization is recorded. This plan carries no standing authorization. The repo plan and ticket plan are documents until authorization clears.
Why record an authorization gate?
The gate makes explicit that planning and execution are separate acts. A plan that auto-authorises itself has no moment where the sponsor can review the design choices and say no. Recording the gate in writing creates that moment.
- Save build-log.md.
Before you verify, think about what you expect to see. How many files should be in the capability-planning folder, and how many files should be in the adrs/ subdirectory?
- Verify the full file tree by running:
ls -R
You should see seven items in the root (adrs/, build-log.md, commitments.md, constraint-statement.md, repo-plan.md, ticket-plan.md, topology.md) and four files inside adrs/ (ADR-001.md through ADR-004.md).
Seeing fewer files than expected?
Run pwd to confirm you are inside capability-planning. If you are in a parent or child directory, ls -R will show the wrong tree. Navigate to the correct folder and re-run.
Help me find my missing files.
The design is locked. Three commitments, four decision records with genuine reversal conditions, a topology connecting six teams by artifact, and two delivery plans are written and waiting behind the authorization gate. Next up, you will clear that gate, create the repository and Linear project, and build the skills matrix.
Build the Skills Matrix, Evidence Audit, and Demand Baseline
In Step 1 you locked the design commitments, wrote the ADRs, drew the topology, and planned every ticket and artifact. All of that work sits in local files behind an authorization gate that has not yet cleared.
The portfolio has thirteen open requisitions written against headcount, not capability. Until a skills matrix exists that records what every person actually knows (with proof), the sponsor is defending a roster count to finance instead of a capability position. This step creates the matrix, sizes the gaps in evidence, and writes every metric definition into the ledger before a single number is computed.
In this step, get ready to:
- Create the GitHub repository and Linear project with epics, milestones, labels, and workflow automation.
- Build the skills matrix with evidence for every person-skill claim and declare the validation manifest.
- Write the metric ledger definitions and run Check 1 against the manifest.
Create the GitHub repository and Linear project, then build the demand baseline
The authorization gate from Step 1 held everything as documents. Before any artifact ships, the operator records that authorization has cleared. Then the delivery infrastructure comes up: a private repository for versioned artifacts and a Linear project for tracking work.
- Return to build-log.md in your text editor.
- Add the authorization record below the existing gate entry:
## Operator Authorization
Authorization recorded. The build may proceed.
Date: [today's date]
Operator: [your name or role]
The repo plan and ticket plan are now authorized
for execution. Repository creation, ticket
population, and artifact commits may begin.
What does this addition do?
The authorization record is a named entry in the build log that converts the repo plan and ticket plan from documents into authorized work. No repository, tickets, or pushes happen without this entry. It is the operator's written commitment that the build is sanctioned.
- Save build-log.md.
Now create the GitHub repository that will hold every versioned artifact.
- Open your browser and go to github.com.
- Click the + icon in the top-right corner, then select New repository.
- Enter a repository name that matches your project (e.g. capability-capacity-planning).
- Set the visibility to Private.
- Leave all other options unchecked (no README, no .gitignore, no license).
- Click Create repository.
GitHub shows the empty repository page with setup instructions. Copy the repository URL displayed on this page.
- Initialize your local project folder and push the existing Step 1 files by running these commands in your terminal:
cd ~/Desktop
cd capability-capacity-planning
git init
git add .
git commit -m "Step 1: design commitments, ADRs, topology, plans"
git remote add origin [[REPO_URL="your GitHub repository URL from the page above"]]
git branch -M main
git push -u origin main
What do these commands do?
These commands initialize a Git repository in your local project folder, stage all the files you created in Step 1, commit them with a descriptive message, connect the local repository to the remote on GitHub, and push the initial commit to the main branch.
- Refresh the repository page on GitHub. You should see all your Step 1 files (commitments.md, adrs/, topology.md, repo-plan.md, ticket-plan.md, build-log.md) listed in the repository.
Push rejected or authentication error?
If git push prompts for credentials or fails with an authentication error, confirm you have a GitHub personal access token or SSH key configured. GitHub no longer accepts passwords for HTTPS pushes.
help me set up GitHub authentication for pushing to my repository
Next, set up the Linear project that tracks every ticket through to completion.
- Open Linear in your browser.
- Navigate to your team's Projects page.
- Click the + button to create a new project.
- Name it to match your build (e.g. Capability & Capacity Planning).
- Click into the Overview tab of your new project.
- Add six milestones from the Overview page, one per phase: Mandate, Matrix, Routing, Attrition, Validation, Report.
How do milestones work in Linear?
Milestones mark important points in a project's lifecycle. Create them from the project Overview page beneath the description. You can add a target date by clicking the calendar icon when creating a milestone. Use Shift+M to quickly assign issues to milestones later.
- Create issues from your ticket plan. Add stories and tasks for each phase, assigning each to the matching milestone. Use the issue properties (priority, estimate, due date) from your ticket plan.
- Each issue's title should reference the artifact or check it delivers (e.g. Build demand baseline, Create skills matrix, Run Check 1).
Now create the label groups and labels documented in your commitments.md metadata model.
- In Linear, go to Settings then Workspace then Labels.
- Create a label group called Phase with labels: Mandate, Matrix, Routing, Attrition, Validation, Report.
- Create a label group called Artifact with labels for each deliverable from your ticket plan (e.g. Demand Baseline, Skills Matrix, Ledger, Gap Table, Capacity Model, Report).
- Create a label group called Check with labels: Check-1, Check-2, Check-3.
- Apply the appropriate Phase and Artifact labels to each issue you created. Press L on any issue to open the label picker.
Why label groups instead of custom fields?
Linear does not have a dedicated custom fields feature for issues. Label groups with one nesting level serve as structured issue metadata. Only one label from each group can be applied to an issue, which enforces a single phase per ticket and a single artifact per ticket. This is how Linear is designed to carry structured data, not a workaround.
Finally, connect the GitHub integration and configure workflow automation so tickets move automatically.
- In Linear, go to Settings then Team then Workflows and automations then Pull request and commit automations.
- Enable the default automation: linked issues move to In Progress when PRs are opened and to Done when PRs are merged.
- Confirm the GitHub integration is connected by checking that your repository appears in the integration settings.
How does issue linking work?
Linear links issues to PRs when the issue ID appears in the branch name or PR title. You can also use magic words like fixes with the issue ID in a PR description or commit message to close the ticket on merge.
- Return to your Linear project. You should see all six milestones listed on the Overview page and your issues populated with Phase, Artifact, and Check labels visible on each ticket.
With the delivery infrastructure in place, build the demand baseline that maps every program to its required skills.
- In your text editor, create the data/ directory inside your project folder by running:
mkdir -p data
- Create data/demand-baseline.md with the following structure. Use Claude to draft program names, milestones, and skills that fit a realistic four-program portfolio scenario, then review and confirm every entry before saving:
# Demand Baseline
## Portfolio Programs
| # | Program | Critical Milestone | Required Skills |
|---|---------|--------------------|-----------------|
| 1 | (program name) | (milestone, quarter) | (skill 1, skill 2) |
| 2 | (program name) | (milestone, quarter) | (skill 1, skill 2) |
| 3 | (program name) | (milestone, quarter) | (skill 1, skill 2) |
| 4 | (program name) | (milestone, quarter) | (skill 1, skill 2) |
All programs above are seed parameters.
## Seed Parameters
- Four programs (seed)
- Three platform-capable people (seed)
- Thirteen requisitions (seed, sponsor-confirmed)
## Validation Manifest
| Dimension | Expected Count |
|-----------|----------------|
| People | (your count) |
| Skills | (your count) |
| Person-skill keys | (people x skills) |
| Gaps | (count after matrix is built) |
| Programs | 4 |
What does the demand baseline contain?
The demand baseline lists the four programs (seeded scenario), their critical milestones, and the skills each milestone requires. Every seed value is explicitly labelled so a computed number is never mistaken for a finding.
The validation manifest at the bottom declares expected counts for every dimension the three validation checks will assert against. Fill in the People, Skills, and Person-skill keys counts after you finalize the roster and skill list. The Gaps count updates after the matrix is built. Programs is always 4.
- Save data/demand-baseline.md.
- Confirm the file exists by running:
ls data/
You should see demand-baseline.md listed in the output.
Build the skills matrix with evidence
The demand baseline names what skills the portfolio needs. The skills matrix records what each person actually knows, backed by evidence. Every person-skill intersection carries a shipped artifact, a completed review, or a passed assessment. A claim with no evidence is marked UNPROVEN, never dropped or assumed.
- Prompt Claude to draft an initial skills matrix from the demand baseline and the known roster. Provide Claude with the roster of people, the skill list from your demand baseline, and the evidence rules below. Review and confirm every person-skill entry before saving.
What are the evidence rules?
- Every evidence reference must name the PERSON, the SKILL, and a DATE.
- An artifact cited for more than 3 people must carry a per-person RESULT. A shared attendance record naming everyone is not sufficient.
- Valid evidence types: shipped artifact, completed review, passed assessment.
- Where no evidence exists, mark the cell UNPROVEN. Never drop it or accept a self-rating as evidence.
- Create data/skills-matrix.csv with the following structure:
Person,Skill,Evidence_Type,Evidence_Name,Date,Per_Person_Result
Alice,Platform Engineering,shipped_artifact,API Gateway v2 deployment,2026-03-15,Led migration of 3 services
Alice,Cloud Infrastructure,completed_review,AWS Well-Architected review,2026-06-01,Reviewed networking layer
Bob,Platform Engineering,shipped_artifact,CI/CD pipeline rebuild,2026-04-20,Built and deployed pipeline
Bob,API Design,UNPROVEN,,,
Carla,Platform Engineering,passed_assessment,Platform cert exam,2026-05-10,Score 92%
Carla,Data Engineering,UNPROVEN,,,
What does each column do?
- Person and Skill together form the person-skill key. Every combination from your roster and skill list must have a row.
- Evidence_Type is one of shipped_artifact, completed_review, passed_assessment, or UNPROVEN.
- Per_Person_Result is required when an artifact is cited for more than 3 people. It proves individual contribution, not shared attendance.
- Replace the example rows with your own roster and skill data from the Claude draft. Make sure every person-skill combination in your demand baseline has a row.
- Save data/skills-matrix.csv.
- Confirm both data files exist by running:
ls data/
You should see both demand-baseline.md and skills-matrix.csv listed.
Matrix feels incomplete?
Count the rows in your CSV (excluding the header). The count should equal the person-skill key count in your validation manifest (people x skills). If it is lower, some person-skill combinations are missing. Cross-reference the roster against the full skill list from the demand baseline.
help me find missing person-skill combinations in my skills matrix
✔️ Awesome, I've got everything!
Great. Make sure you have saved data/skills-matrix.csv with every person-skill intersection accounted for.
ⓧ I'd like to double check the full code
Your data/skills-matrix.csv should have exactly this structure. The example names below are placeholders for your own data:
Person,Skill,Evidence_Type,Evidence_Name,Date,Per_Person_Result
Alice,Platform Engineering,shipped_artifact,API Gateway v2 deployment,2026-03-15,Led migration of 3 services
Alice,Cloud Infrastructure,completed_review,AWS Well-Architected review,2026-06-01,Reviewed networking layer
Bob,Platform Engineering,shipped_artifact,CI/CD pipeline rebuild,2026-04-20,Built and deployed pipeline
Bob,API Design,UNPROVEN,,,
Carla,Platform Engineering,passed_assessment,Platform cert exam,2026-05-10,Score 92%
Carla,Data Engineering,UNPROVEN,,,
Every row carries a person, a skill, evidence or UNPROVEN, and a date. No claim is both unevidenced and unmarked.
Write the metric ledger definitions and run Check 1
The design commitment from Step 1 says: capacity is people x availability x competence factor, and the competence factor is a definition in the ledger written before any number is computed. This substep creates that ledger.
- Create data/ledger.yaml with the following definitions:
# Metric Ledger
# All definitions written before any number is computed.
competence_factor:
description: "Value 0.0 to 1.0 per person-skill"
scale:
UNPROVEN: 0.0
evidence_within_2_quarters: 1.0
evidence_older_than_2_quarters: 0.0
availability:
description: "Fraction of person time to a program"
range: "0.0 to 1.0"
example: "0.5 for split across two programs"
competent_hours:
formula: "people x availability x competence_factor x hours_per_period"
hours_per_period:
definition: "40 hours/week x weeks in planning period"
evidence_decay:
rule: "Claim older than 2 quarters re-marked UNPROVEN"
critical_skill_coverage:
definition: "competent_hours_available / hours_demanded"
Why write definitions before numbers?
A competence factor that appears as a formula inside a spreadsheet cell is invisible to anyone who does not open that cell. By writing every definition into a versioned YAML file, the definitions diff together with the delivery status. Anyone reviewing the PR can see exactly what each metric means before the numbers are computed.
The evidence_decay rule ensures that a claim older than two quarters is automatically re-marked UNPROVEN until refreshed. This prevents stale evidence from inflating coverage numbers.
- Save data/ledger.yaml.
- Confirm all three data files exist by running:
ls data/
You should see demand-baseline.md, skills-matrix.csv, and ledger.yaml listed.
✔️ Awesome, I've got everything!
Great. Double check you have saved data/ledger.yaml with all six metric definitions.
ⓧ I'd like to double check the full code
Your data/ledger.yaml should contain these six definitions:
# Metric Ledger
# All definitions written before any number is computed.
competence_factor:
description: "Value 0.0 to 1.0 per person-skill"
scale:
UNPROVEN: 0.0
evidence_within_2_quarters: 1.0
evidence_older_than_2_quarters: 0.0
availability:
description: "Fraction of person time to a program"
range: "0.0 to 1.0"
example: "0.5 for split across two programs"
competent_hours:
formula: "people x availability x competence_factor x hours_per_period"
hours_per_period:
definition: "40 hours/week x weeks in planning period"
evidence_decay:
rule: "Claim older than 2 quarters re-marked UNPROVEN"
critical_skill_coverage:
definition: "competent_hours_available / hours_demanded"
Now commit all three artifacts as a PR that closes the corresponding Linear tickets.
- Create a feature branch, commit the new files, and push by running these commands:
git checkout -b matrix/demand-baseline-and-ledger
git add data/
git commit -m "Add demand baseline, skills matrix, and metric ledger"
git push -u origin matrix/demand-baseline-and-ledger
- Open a pull request on GitHub from matrix/demand-baseline-and-ledger to main.
- In the PR description, include fixes followed by the Linear issue IDs for the demand baseline, skills matrix, and ledger tickets. This triggers Linear's automation to move those issues to In Progress now and Done when you merge.
- Check your Linear project. The linked issues should now show In Progress status.
Linear tickets not moving to In Progress?
Confirm the GitHub integration is connected in Settings then Integrations then GitHub. Make sure the issue ID appears in the branch name or PR title. Linear matches issue IDs automatically when they appear in these locations.
help me link my GitHub PR to Linear issues so the automation triggers
Before merging, run Check 1 to validate the matrix against the manifest. Do you expect every person-skill key in your matrix to carry either evidence or an UNPROVEN mark with no gaps?
- Create the validation/ directory by running:
mkdir -p validation
- Manually read every row in data/skills-matrix.csv against the manifest counts in data/demand-baseline.md. For each dimension (people, skills, person-skill keys), count expected, read, matched, and missing.
- Record the results in validation/check-1-results.md using this structure:
# Check 1: Matrix Check
## Guard Rule Results
| Dimension | Expected | Read | Matched | Missing |
|-----------|----------|------|---------|---------|
| People | | | | |
| Skills | | | | |
| Person-skill keys | | | | |
## Assertions
- [x] Every claim carries evidence or UNPROVEN
- [x] No claim both unevidenced and unmarked
- [x] No skill unassessed
- [x] Every evidence ref names PERSON, SKILL, DATE
- [x] No artifact cited for >3 people without per-person RESULT
- [x] Zero-row read: REJECTED (read count > 0)
What is the guard rule?
The guard rule prevents a common failure mode: a check that reads zero rows and asserts "no problems found" is asserting against an empty dataset, not a complete one. The manifest declares expected counts. Reading zero rows fails the check. A read count differing from expected also fails. Non-emptiness is not completeness.
Fill in the Expected column from your validation manifest and the Read column from your actual matrix row count. Matched is the number that pass the assertion. Missing is Expected minus Matched. When Expected equals Read equals Matched for every row, Check 1 passes.
- Fill in the counts from your validation against the matrix.
- Save validation/check-1-results.md.
- Stage and commit the Check 1 results, then update the demand baseline with final gap counts by running:
git add validation/ data/demand-baseline.md
git commit -m "Add Check 1 results, update manifest gap count"
git push
- Merge the pull request on GitHub. Your Linear tickets for these artifacts should automatically move to Done.
Check 1 should show Expected equals Read equals Matched for every dimension. Zero-row reads should be rejected. Every claim in the matrix carries evidence or the UNPROVEN mark with person, skill, and date on every reference.
Check 1 counts do not match?
If Read differs from Expected, either the manifest count is wrong or the matrix is missing rows. Re-count the unique people and unique skills in your demand baseline, multiply to get the expected person-skill key count, and compare against the rows in your CSV.
If any assertion fails (an unevidenced claim without the UNPROVEN mark, or an evidence reference missing the date), fix the offending row in data/skills-matrix.csv and re-run the check.
help me debug why my Check 1 matrix validation is failing
The matrix is validated, the ledger definitions are committed, and the delivery infrastructure is live. Next up, you will route every gap to buy, build, or borrow, compute capacity in competent hours, and produce the headcount comparison that makes the mis-sizing visible.
Route Every Gap and Build the Capacity Model
Your skills matrix and evidence audit are committed. Every person-skill intersection carries proof or an UNPROVEN mark, and Check 1 passed against the manifest.
The matrix makes every gap visible, but gaps alone do not close the milestone. This step routes each gap to a resolution and computes capacity in competent hours using the ledger definitions.
The framing claim says the portfolio slipped because the roster was counted instead of the capability. This step produces the headcount-vs-competent-hours comparison that makes that claim visible.
In this step, get ready to:
- Route every skills gap to buy, build, or borrow with a need-by date, a time-to-competence estimate, and an owner.
- Compute the capacity model in competent hours and produce the headcount-vs-competent-hours comparison.
- Set up the board-to-ledger status sync, document the AI-augmented planning workflow, and commit the implementation plan.
Route every gap and build the talent plan
Every UNPROVEN or absent person-skill intersection in your matrix is a gap. Each gap needs exactly one route: buy (external hire), build (internal upskilling), or borrow (contractor or staff augmentation).
- In your text editor, create the file data/gap-table.md with the following structure:
# Gap Table
<!-- Source: every UNPROVEN or absent intersection in skills-matrix.csv -->
## Routing Constraint (from commitments.md)
A gap whose forecast start + time-to-competence exceeds its
need-by date CANNOT be routed to build/upskilling.
Route to buy or borrow instead.
| Gap ID | Person | Skill | Program | Route | Need-by (wks) | TTC (wks) | Owner |
|--------|--------|-------|---------|-------|---------------|-----------|-------|
<!-- Fill one row per gap from the skills matrix -->
<!-- Claude will draft these rows in the next step -->
What does this structure capture?
Each row represents one person-skill gap. Need-by (wks) is the deadline in weeks from the forecast start. TTC (wks) is the estimated time-to-competence for the assigned route.
The routing constraint from your design commitments is printed at the top of the file so it stays visible during review.
Now use Claude to draft the routing recommendations.
- Open Claude and provide it with your data/skills-matrix.csv and data/demand-baseline.md.
- Ask Claude to recommend a route (buy, build, or borrow) for each gap, with a time-to-competence estimate in weeks and a need-by date drawn from the demand baseline milestones.
- Copy Claude's draft into the gap table rows in data/gap-table.md.
Why does a human ratify every route?
Claude proposes structures and numbers. The operator confirms anything that names a person or commits a delivery date. No person-level assignment is committed without human confirmation.
The most common failure mode in Claude's draft is routing every gap to build/upskilling because it is the cheapest option. Review every row against the routing constraint.
- For each gap routed to build, check whether forecast start plus TTC exceeds the need-by date.
- If it does, change the route to buy or borrow.
- Confirm that every gap carries exactly one route, a need-by date, a TTC estimate, and an owner.
- Save data/gap-table.md.
Your gap table should now have one row per gap, with no gap unrouted and no gap double-routed.
- Commit the gap table as the first PR for this step by running these commands:
git checkout -b gap-routing
git add data/gap-table.md
git commit -m "Add gap table with routed gaps"
git push origin gap-routing
- Switch to your GitHub repository in the browser.
- Create a pull request from the gap-routing branch.
- Include your Linear issue ID for the gap-table task in the PR title or description so the ticket links automatically.
- Merge the pull request.
Switch to your Linear project. The linked ticket should have moved to Done automatically.
Ticket did not move to Done?
- Make sure your GitHub integration is connected in Linear. Check Settings > Integrations > GitHub.
- Verify that the issue ID appears in the PR title, description, or branch name. Linear recognises issue IDs like ENG-123.
- Confirm that the PR automation is enabled at Settings > Team > Workflows and automations > Pull request and commit automations.
Help me troubleshoot why my Linear ticket did not close when I merged the PR.
- Return to your terminal and switch back to main by running:
git checkout main
git pull origin main
Compute the capacity model and produce the headcount comparison
The gap table shows where the gaps are and how they will be closed. The capacity model shows how much capability the portfolio actually has right now, measured in competent hours rather than headcount.
- Create the file data/capacity-model.md with the following structure:
# Capacity Model
<!-- Formula from data/ledger.yaml -->
<!-- competent_hours = availability x competence_factor x hours_per_period -->
## Per-Program Capacity
| Program | Person | Skill | Availability | Competence Factor | Hours/Period | Competent Hours |
|---------|--------|-------|-------------|-------------------|-------------|----------------|
<!-- One row per person-skill-program assignment -->
<!-- Use Claude to compute from the matrix and ledger -->
## Before-Plan Coverage
| Program | Skill | Hours Demanded | Competent Hours Available | Coverage Ratio |
|---------|-------|---------------|--------------------------|----------------|
What does this model compute?
For each person assigned to a program, the competent hours formula from ledger.yaml is: availability x competence_factor x hours_per_period.
A person with 0.5 availability and a competence factor of 1.0 over a 400-hour period delivers 200 competent hours. A person mid-ramp with a competence factor of 0.0 (UNPROVEN) delivers zero competent hours, regardless of availability.
- Provide Claude with your data/skills-matrix.csv, data/demand-baseline.md, and data/ledger.yaml.
- Ask Claude to compute competent hours per person per skill per program, sum them per skill per program, and calculate the coverage ratio against hours demanded.
- Review the numbers Claude returns. Confirm that every competence factor matches the evidence status in the matrix: UNPROVEN entries must carry 0.0, and evidenced entries dated within two quarters must carry 1.0.
- Paste the reviewed numbers into data/capacity-model.md and save the file.
Now produce the comparison that makes the framing claim visible.
- Create the file data/headcount-comparison.md with this structure:
# Headcount vs Competent Hours Comparison
<!-- The framing claim made visible: -->
<!-- headcount does not equal capacity -->
<!-- when the average competence factor is below 1.0 -->
| Program | Skill | Headcount | Competent Hours | Avg Competence Factor |
|---------|-------|-----------|-----------------|-----------------------|
<!-- One row per program-skill combination -->
<!-- Headcount = people assigned -->
<!-- Competent Hours = sum of (availability x factor x hours_per_period) -->
<!-- Avg Competence Factor = average factor for that skill in that program -->
<!-- Thirteen people on the roster does not mean -->
<!-- thirteen people of capacity when factors < 1.0 -->
- Ask Claude to produce the three-column comparison from the capacity model data: headcount (people assigned), competent hours (summed from the model), and the average competence factor for that skill in that program.
- Paste the reviewed comparison into data/headcount-comparison.md and save.
Look at the table you just built. The gap between the headcount column and the competent hours column is the mis-sizing: thirteen people on the roster does not mean thirteen people of capacity when the average competence factor is below 1.0.
What the build cannot assert
Whether an approached engineer stays is outside this build. Whether every manager confirmation lands inside the hour is also outside it.
Unconfirmed person-level moves sit in a ratification queue. The plan ships as a forecast until they clear.
- Commit the capacity model and headcount comparison as a PR by running:
git checkout -b capacity-model
git add data/capacity-model.md data/headcount-comparison.md
git commit -m "Add capacity model and headcount comparison"
git push origin capacity-model
- Create a pull request on GitHub, referencing the relevant Linear issue IDs for the capacity-model and headcount-comparison tasks.
- Merge the pull request.
- Confirm the linked tickets move to Done in Linear.
- Pull the merged changes locally:
git checkout main
git pull origin main
Build the board-to-ledger sync, document the AI workflow, and commit the implementation plan
The gap table and capacity model are committed, but the delivery status still lives in Linear rather than alongside the metric definitions. The board-to-ledger sync pulls that status into the repository so both diff together.
- Create the file data/status-snapshot.yaml with the following structure:
# Board-to-ledger status snapshot
# Committed alongside ledger.yaml so both diff together
# Replaces slide deck as the delivery status source
snapshot_date: YYYY-MM-DD
project_status: In Progress
milestones:
mandate:
completion_pct: 100
matrix:
completion_pct: 100
routing:
completion_pct: 0
attrition:
completion_pct: 0
validation:
completion_pct: 0
report:
completion_pct: 0
issue_counts_by_label_group:
Phase:
Mandate: {total: 0, done: 0}
Matrix: {total: 0, done: 0}
Routing: {total: 0, done: 0}
Attrition: {total: 0, done: 0}
Validation: {total: 0, done: 0}
Report: {total: 0, done: 0}
What does this snapshot replace?
In most portfolio delivery setups, status lives in a slide deck that drifts from reality within hours of the meeting. This YAML snapshot sits beside ledger.yaml in the same repository.
When the status changes, both files diff together in the same commit. The export is a manual pull from Linear's project view into this file.
- Open your Linear project's Overview page.
- Update the milestone completion percentages and issue counts in data/status-snapshot.yaml to match the current state of your project.
- Update the snapshot_date to today's date.
- Save the file.
Verify the snapshot sits alongside the ledger by running:
ls data/
You should see both ledger.yaml and status-snapshot.yaml in the listing, alongside your other data files.
- Add a note to build-log.md recording that the board-to-ledger sync is configured: status is exported from Linear into data/status-snapshot.yaml and committed alongside the metric ledger.
- Save build-log.md.
Now document the AI-augmented planning workflow and write the implementation plan.
- Create the docs/ directory if it does not already exist, then create docs/ai-workflow.md with the following content:
# AI-Augmented Planning Workflow
## Claude Drafts
- Initial skills matrix from demand baseline and roster
- Gap routing recommendations based on need-by dates
- Capacity computations from ledger definitions
- Attrition scenarios and re-run results
- Report sections with coverage numbers
## Human Ratifies
- Every person-level assignment (who routes where)
- Every competence factor override
- Every departure scenario selection
- Every final coverage number before the report
## Boundary
AI proposes structures and numbers.
Human confirms anything that names a person
or commits a delivery date.
- Create docs/implementation-plan.md drawn from your topology.md diagram:
# Implementation Plan
<!-- Drawn from topology.md -->
## Sequence of Work
1. Demand baseline and skills matrix (Step 2, complete)
2. Gap routing and capacity model (Step 3, current)
3. Attrition shock and report (Step 4)
4. Validation and tagged release (Step 5)
## Parallel Build Structure
Five agent teams run concurrently in Step 3:
- Skills: maintains matrix accuracy
- Hiring: routes buy gaps to requisitions
- Upskilling: routes build gaps to training plans
- Capacity: computes competent hours per program
- Executive Reporting: assembles coverage data
## Attrition Team Entry Point
The Attrition team activates in Step 4
when departure data arrives.
## Dependencies
- Gap table depends on skills matrix
- Capacity model depends on gap table and ledger
- Headcount comparison depends on capacity model
- Report depends on all of the above
What do these two documents capture?
The AI workflow document draws the boundary between what Claude proposes and what the operator confirms. This boundary is critical: every person-level decision must pass through human ratification.
The implementation plan translates the Mermaid topology into a work sequence. It names which teams run concurrently and where the Attrition team enters in Step 4.
- Save both files.
- Commit all remaining files as a PR by running:
git checkout -b status-sync-and-docs
git add data/status-snapshot.yaml build-log.md docs/ai-workflow.md docs/implementation-plan.md
git commit -m "Add status snapshot, AI workflow, and implementation plan"
git push origin status-sync-and-docs
- Create and merge the pull request on GitHub, referencing the relevant Linear issue IDs.
- Pull the merged changes locally:
git checkout main
git pull origin main
Now run Check 2 to verify the plan against the manifest.
Before you run the check, predict: do you think every gap in your table will pass the routing constraint? Are there any gaps you are uncertain about?
- Open data/gap-table.md and read every row against the validation manifest declared in data/demand-baseline.md.
- Count the total number of gaps in the table. This is your read count.
- Compare it to the expected gap count from the manifest. If the read count is zero, the check fails. If the read count differs from expected, the check fails.
- For each gap, assert:
- Record the results in validation/check-2-results.md using this structure:
# Check 2: Plan Check
## Manifest Reference
- Expected gap count: N (from validation manifest)
## Guard Rule
- Reading zero rows: FAILS
- Read count != expected: FAILS
## Assertions
- No gap unrouted: PASS/FAIL
- No gap double-routed: PASS/FAIL
- No gap routed to build where
forecast_start + TTC > need_by: PASS/FAIL
## Counts
- Expected: N
- Read: N
- Matched: N
- Missing: 0
What does the guard rule prevent?
A check that reads zero rows and asserts "no problems found" is asserting against an empty dataset, not a complete one. The guard rule forces the check to reconcile what it read against the manifest's expected count.
- Fill in your actual counts and pass/fail results.
- Save validation/check-2-results.md.
- Commit the check results:
git checkout -b check-2-results
git add validation/check-2-results.md
git commit -m "Add Check 2 plan results"
git push origin check-2-results
- Create and merge the pull request on GitHub.
- Pull the merged changes locally:
git checkout main
git pull origin main
Your Check 2 results should show expected equals read for the gap count, with zero missing. Every assertion should read PASS.
Check 2 shows a FAIL?
- If the read count differs from expected, compare your gap table against the skills matrix. Every UNPROVEN or absent intersection should have a corresponding row in the gap table.
- If a gap is routed to build but fails the time constraint, change its route to buy or borrow. Re-run the assertion for that row.
- If you find a double-routed gap, remove the duplicate route so each gap carries exactly one.
Help me debug why Check 2 is failing against my gap table and manifest.
Every gap is now routed, the capacity model computes competent hours from the ledger, and the headcount comparison makes the framing claim visible. Next, you will inject departures into this model, re-run it on the same definitions, and name every result as a mitigation or a slip.
Run Attrition Shock and Build the Report
Every gap is routed, the capacity model computes competent hours, and the headcount-vs-competent-hours comparison makes the mis-sizing visible.
The plan as built assumes everyone stays. People leave mid-increment, and a plan that cannot survive a departure is not a plan. In this step, you will inject departure scenarios into the same model, re-run on the same ledger definitions, and name every result as either a mitigation or a slip with a date.
The build does not claim anyone was retained or persuaded. It claims the departures were modelled and answered in writing. What is refused is absorbing the loss silently and reporting the original forecast.
In this step, get ready to:
- Inject departure scenarios and re-run the capacity model on the same ledger definitions.
- Re-route affected gaps and name every result as a mitigation with a date or a slip with a new forecasted date.
- Compute after-plan coverage and build the executive report with the honesty section, then pass Check 3.
Inject departures and re-run the model
The capacity model currently assumes the full roster stays through every increment. Departure scenarios test what happens to competent hours, gap coverage, and the headcount comparison when that assumption breaks.
You need at least two scenarios: one where a platform-capable person leaves, and one where a person mid-ramp on a critical skill departs. These are constructed scenarios, not predictions.
- Use Claude to draft the departure scenarios file by prompting it with your skills matrix, capacity model, and the two scenario descriptions below.
- Review every re-computed number against the same ledger definitions in data/ledger.yaml before accepting the draft.
- Create data/attrition-scenarios.md with this structure:
# Attrition Scenarios
> All scenarios are constructed, not predictions.
> Capacity re-computed using the same ledger definitions (data/ledger.yaml).
## Scenario A: Platform-Capable Person Departs
**Departed:** (name one of the three platform-capable people, seed)
**Skills affected:** (list critical platform skills this person evidenced)
**Programs affected:** (programs losing capacity)
### Re-computed Competent Hours
| Program | Skill | Pre-Departure Hours | Post-Departure Hours | Delta |
|---------|-------|---------------------|---------------------|-------|
### Gaps Re-opened or Worsened
| Gap ID | Skill | Program | Original Route | Post-Departure Status |
|--------|-------|---------|---------------|----------------------|
### Post-Departure Headcount vs Competent Hours
| Program | Skill | Headcount | Competent Hours | Avg Competence Factor |
|---------|-------|-----------|-----------------|----------------------|
What does this file capture?
Each scenario removes one person from the capacity model and re-computes everything using the same formula: availability x competence_factor x hours_per_period.
The post-departure headcount-vs-competent-hours table shows how the gap between headcount and actual capacity changes when a person leaves. A departure that drops competent hours while headcount only decreases by one makes the mis-sizing even more visible.
- Add Scenario B (mid-ramp person departs) using the same table structure as Scenario A.
- For Scenario B, select a person whose competence_factor is below 1.0 on a critical skill.
- Label every scenario as constructed, not a prediction.
- Commit data/attrition-scenarios.md on a feature branch and push by running:
git checkout -b attrition-scenarios
git add data/attrition-scenarios.md
git commit -m "Add attrition departure scenarios"
git push origin attrition-scenarios
- Create a pull request in GitHub that references the attrition scenarios ticket from your Linear project.
- Include the Linear issue ID in the PR title or description so the linked issue moves to In Progress automatically.
You should see the PR appear in GitHub and the linked Linear issue move to In Progress.
Linear ticket not moving to In Progress?
- Check that the Linear issue ID appears in your PR title or branch name. Linear matches against the issue identifier (e.g. TEAM-123).
- Confirm the GitHub integration is connected in Linear under Settings > Team > Workflows and automations.
- If the automation still does not trigger, help me troubleshoot my Linear-GitHub integration for PR automations.
Re-route affected gaps and name mitigations or slips
The departure scenarios show which gaps re-open or worsen. Each affected gap needs a new route decision under the tighter constraint.
If a mitigation exists (another person can absorb, a contractor can backfill, an upskilling path can accelerate within the need-by date), name it with a date. If no mitigation exists within the need-by date, name it as a slip with the new forecasted date.
- Use Claude to draft initial re-routing recommendations based on the departure scenarios and your gap table.
- Ratify every re-routed gap before committing. No person-level assignment is final without your confirmation.
- Create data/attrition-rerouting.md with this structure:
# Attrition Re-routing
Original forecast and re-routed results shown side by side.
## Scenario A
| Gap ID | Skill | Original Route | Re-decided Route | Result | Date |
|--------|-------|---------------|-----------------|--------|------|
**Mitigations:**
- Gap ID: Description of who absorbs or backfill path — by date
**Slips:**
- Gap ID: No mitigation within need-by date — new forecast: date
## Scenario B
| Gap ID | Skill | Original Route | Re-decided Route | Result | Date |
|--------|-------|---------------|-----------------|--------|------|
**Mitigations:**
- Gap ID: Description — by date
**Slips:**
- Gap ID: Description — new forecast: date
Why show results beside the original forecast?
Placing the original route and the re-decided route in the same table makes the impact of each departure immediately visible. The sponsor can see exactly which routes changed, which gaps now have mitigations, and which result in a slip.
A slip is an honest answer. Absorbing the loss silently and reporting the original forecast is the wrong answer.
- Commit data/attrition-rerouting.md to your feature branch by running:
git add data/attrition-rerouting.md
git commit -m "Add attrition re-routing with mitigations and slips"
git push origin attrition-scenarios
Your PR in GitHub now shows both the departure scenarios and the re-routing results. Confirm that every affected gap carries either a mitigation with a date or a slip with a new forecasted date.
Missing a gap in the re-routing table?
- Cross-reference against the gaps identified in data/attrition-scenarios.md. Every re-opened or worsened gap needs a row in the re-routing table.
- Check that gaps worsened (not just re-opened) are also included. A gap that was already routed to build but now has a shorter window may need re-routing to buy or borrow.
- If you are unsure whether a gap qualifies as worsened, help me identify which gaps are worsened by this departure scenario.
Compute after-plan coverage and build the report
The attrition results are committed, but the sponsor needs the full picture: coverage before and after the plan, the headcount-vs-competent-hours comparison, the attrition shock, and an honest statement of what the build does and does not assert.
Before building the report, you need the after-plan coverage numbers. These assume the routed gaps close on their time-to-competence schedules.
- Open data/capacity-model.md in your text editor.
- Add an after-plan coverage section using the same formula and ledger definitions as the before-plan coverage:
## After-Plan Coverage
<!-- Assumes routed gaps close on time-to-competence schedules -->
<!-- Same formula: availability x competence_factor x hours_per_period -->
| Program | Skill | Competent Hours (After) | Hours Demanded | Coverage Ratio |
|---------|-------|------------------------|----------------|----------------|
Why use the same formula?
The before and after coverage values must come from one definition so the comparison is valid. If the after value uses a different formula or a different competence factor scale, the numbers are not comparable and the report cannot claim improvement.
- Save data/capacity-model.md.
- Use Claude to draft the executive report. Prompt it with the before-plan coverage, the after-plan coverage, the headcount comparison from data/headcount-comparison.md, and the attrition re-routing results.
- Review and confirm every coverage number and program-level summary before committing.
- Create reports/report.md with this structure:
# Executive Report: Cross-Program Capability and Capacity
## Critical-Skill Coverage
### Before Plan
| Program | Skill | Competent Hours | Hours Demanded | Coverage | Population |
|---------|-------|-----------------|----------------|----------|------------|
### After Plan
| Program | Skill | Competent Hours | Hours Demanded | Coverage | Population |
|---------|-------|-----------------|----------------|----------|------------|
## Headcount vs Competent Hours
| Program | Skill | Headcount | Competent Hours | Factor |
|---------|-------|-----------|-----------------|--------|
## Program Summaries
| Program | Coverage | Owner | Next Step |
|---------|----------|-------|-----------|
## Attrition Shock
<!-- Scenario results with mitigations and slips from data/attrition-rerouting.md -->
## What This Build Asserts and Does Not Assert
**Asserts:** Departures were modelled. Routes were re-decided. Mitigations or slips named in writing.
**Does not assert:** Whether an approached engineer stays. Whether every manager confirmation lands inside the hour. Whether seed values match the operator's real portfolio.
What does each section of the report carry?
- Critical-Skill Coverage shows before and after values from one definition (competent hours available divided by hours demanded), each stating the population it was computed over.
- Headcount vs Competent Hours makes the framing claim visible. Thirteen people on the roster does not equal thirteen people of capacity when the average competence factor is below 1.0.
- Program Summaries give the sponsor one figure, one owner, and one next step per program.
- The honesty section names what the build can and cannot assert. Unconfirmed person-level moves sit in a ratification queue, and the plan ships as a forecast until they clear.
- Save reports/report.md.
Before you run Check 3, consider: do you expect all counts to match the manifest? What would cause the after-plan coverage to look suspiciously good?
What is Check 3 looking for?
Check 3 verifies the report is complete against the manifest. It also runs a reconciliation: re-derive every competence factor in the after value from the skills matrix and confirm none differs from the evidence status there.
The wrong answer is an after value with the competence factor set to 1.0 for everyone. The reconciliation is what rejects it. If a person's evidence status is UNPROVEN, their factor must be 0.0, not 1.0.
- Run Check 3 (report check) against the manifest by walking through each assertion below and recording the results.
- Create validation/check-3-results.md with the results of the check:
# Check 3: Report Check
## Guard Rule
| Dimension | Expected | Read | Matched | Missing |
|-----------|----------|------|---------|--------|
| Programs with figure/owner/next step | | | | |
| Before coverage values with population | | | | |
| After coverage values with population | | | | |
| Headcount comparison entries | | | | |
| Attrition scenarios with mitigation/slip | | | | |
## Assertions
- Before and after from one definition with populations: PASS/FAIL
- Headcount-vs-competent-hours comparison present with factor: PASS/FAIL
- Every program carries figure, owner, next step: PASS/FAIL
- Attrition shock with mitigation or slip: PASS/FAIL
## Reconciliation
Re-derived every competence factor in the after value from skills-matrix.csv.
No factor differs from evidence status: PASS/FAIL
Why does the reconciliation matter?
The reconciliation re-derives every competence factor from the matrix itself. If the after coverage assumes 1.0 for a person whose evidence status is UNPROVEN (0.0 per the ledger definition), the reconciliation catches it. This is the structural check that prevents overstating coverage.
- Confirm that expected equals read for every manifest dimension. Reading zero rows is a failure. A read count differing from expected is a failure.
- If any assertion fails, return to reports/report.md and fix the missing or incorrect entry before continuing.
- Export a fresh status snapshot from Linear into data/status-snapshot.yaml reflecting the Step 4 progress.
- Commit all remaining files and push by running:
git add data/capacity-model.md data/status-snapshot.yaml reports/report.md validation/check-3-results.md
git commit -m "Add after-plan coverage, report, and Check 3 results"
git push origin attrition-scenarios
- Merge the PR in GitHub.
- Confirm the linked Linear tickets move to Done automatically when the PR is merged.
Check 3 should show expected equals read for every dimension, all assertions passing, and the reconciliation confirming no competence factor differs from the evidence status in the skills matrix.
Check 3 reconciliation failing?
- Open data/skills-matrix.csv and find the person-skill entry that failed. Check whether the evidence status is UNPROVEN.
- If the evidence status is UNPROVEN, the competence factor in the after coverage must be 0.0, not 1.0. Update the after-plan coverage in data/capacity-model.md to match.
- Check that every factor in the after value is re-derived from the matrix, not copied from a target or assumed to be 1.0 because a route was assigned.
- If you are struggling with the reconciliation logic, help me reconcile my after-plan competence factors against the skills matrix.
The attrition shock is modelled, the report is committed, and Check 3 passes. Next up, you will run all three validation checks under the guard rule, write documentation for a non-engineer owner, and tag the release.
Validate, Document, and Tag the Release
The attrition scenarios are modelled and the report is committed. Every check has passed against its own scope in previous steps. The question now is whether those checks proved completeness, or whether any of them read zero rows and called silence clean.
A check asserting something is absent needs to reconcile what it read against a declared manifest. In this step, you will consolidate all three checks under one guard rule, write documentation that a non-engineer owner can follow without touching a terminal, and tag the release.
In this step, get ready to:
- Run all three validation checks under the guard rule against the declared manifest.
- Write documentation that a non-engineer owner can follow at click level.
- Tag the v1.0.0 release containing all artifacts.
Declare the manifest and run the three validation checks
You already ran each check individually in previous steps. The results live in validation/check-1-results.md, check-2-results.md, and check-3-results.md. The formal validation record consolidates them under a single guard rule that prevents a check from asserting against an empty dataset.
The guard rule states: reading zero rows fails. A read count differing from expected fails. Non-emptiness is not completeness.
- Create validation/validation-record.md with the manifest declaration, guard rule, and Check 1 results by pasting the following:
# Validation Record
## Declared Manifest
| Dimension | Expected Count |
|-----------|---------------|
| People | [[PEOPLE_COUNT="your people count from the matrix"]] |
| Skills | [[SKILLS_COUNT="your skills count from the matrix"]] |
| Person-skill keys | [[PSK_COUNT="people x skills"]] |
| Gaps | [[GAPS_COUNT="your gap count from the gap table"]] |
| Programs | 4 (seed) |
## Guard Rule
A check asserting something is ABSENT must reconcile what it read against this manifest.
Reading zero rows fails. A read count differing from expected fails.
Non-emptiness is not completeness.
## Check 1: Matrix
| Metric | Value |
|--------|-------|
| Expected person-skill keys | [[PSK_COUNT="people x skills"]] |
| Read | [[CHECK1_READ="your Check 1 read count"]] |
| Matched | [[CHECK1_MATCHED="your Check 1 matched count"]] |
| Missing | 0 |
Assertions confirmed absent:
- No claim both unevidenced and unmarked.
- No skill unassessed.
- No evidence reference missing PERSON, SKILL, or DATE.
- No artifact cited for 3+ people without a per-person RESULT.
What does this section do?
The manifest declares the population your checks must read. Without it, a check could scan an empty table and report "no problems found" when the real problem is that it read nothing at all.
Check 1 pulls its expected and read counts from your check-1-results.md. The assertions confirm that every absence claim was tested against the full population, not just a subset.
- Preview the Markdown in your editor to confirm the manifest table renders with your actual counts from the skills matrix and gap table.
- Confirm that the Check 1 read count matches the expected person-skill key count, not zero.
- Add the Check 2, Check 3 results and the caveats section to the same file by pasting the following below Check 1:
## Check 2: Plan
| Metric | Value |
|--------|-------|
| Expected gaps | [[GAPS_COUNT="your gap count from the gap table"]] |
| Read | [[CHECK2_READ="your Check 2 read count"]] |
| Matched | [[CHECK2_MATCHED="your Check 2 matched count"]] |
| Missing | 0 |
Assertions confirmed absent:
- No gap unrouted.
- No gap double-routed.
- No gap routed to upskilling whose forecast start plus time-to-competence lands after its need-by date.
## Check 3: Report
- Before and after coverage values from one definition with populations stated.
- Headcount-vs-competent-hours comparison present with the factor between them.
- Every program carries a figure, an owner, and a next step.
- Attrition shock present with mitigation or slip.
- Every competence factor in the after value re-derived from the matrix and confirmed to match evidence status.
## What the Build Cannot Assert
- Whether an approached engineer stays.
- Whether every manager confirmation lands inside the hour.
- Unconfirmed person-level moves sit in a ratification queue.
- The plan ships as a forecast until they clear.
The testable bar: departures are injected, the forecast re-runs on the same definitions, and the result is a named mitigation or slip.
What do the caveats section and Check 3 reconciliation do?
Check 3's reconciliation re-derives every competence factor in the after-plan coverage from the matrix. If any factor was silently set to 1.0 (full competence for everyone), the reconciliation catches it because the matrix's evidence status would not support that value.
The caveats section states what the build cannot control: human decisions about staying or accepting a role. The plan ships as a forecast, and the testable bar is that departures are modelled and answered in writing.
- Save validation/validation-record.md.
- Preview the full file. Confirm that all three checks show expected = read for every manifest dimension, with zero in the Missing column.
Counts not matching?
If your read count differs from expected for any check, return to the corresponding results file (check-1-results.md, check-2-results.md, or check-3-results.md) and reconcile against your matrix or gap table. A mismatch means a person-skill key or gap was added or removed without updating the manifest.
Help me reconcile my validation check counts against my manifest
Write documentation for a non-engineer owner
The artifacts are validated, but the VP of Engineering cannot re-forecast from a repository full of YAML and CSV files. The documentation translates the build into a document the sponsor can follow at click level, without opening a terminal.
This is the terminal test: if the sponsor needs a terminal to follow the re-forecast section, the document has failed.
- Create docs/owner-guide.md with the value proof, re-forecast instructions, and recovery section by pasting the following:
# Owner Guide
## Value Proof
This build computes critical-skill coverage: competent hours available in a skill at a date, divided by hours demanded there. Coverage before the plan was applied: [[BEFORE_COVERAGE="your before-plan coverage value"]]. Coverage after the plan: [[AFTER_COVERAGE="your after-plan coverage value"]]. The competence factor separating headcount from competent hours was defined in the metric ledger before any number was computed.
## How to Re-forecast
1. Open the skills matrix in the repository.
2. Update evidence for any person whose status changed (new certification, completed rotation, departure).
3. Mark any claim older than two quarters as UNPROVEN.
4. Open the capacity model. Re-compute competent hours using the same ledger definitions.
5. Export a fresh status snapshot from Linear into status-snapshot.yaml.
6. Read the new coverage number from the capacity model.
7. Compare against the previous coverage to see direction of travel.
No terminal is required for any of these steps.
## What to Do When It Breaks
**A person departs:** Remove them from the capacity model. Re-compute competent hours. Identify re-opened gaps. Re-route each gap to buy, build, or borrow. Name a mitigation with a date, or a slip with a new date.
**Escalation path:** The re-forecast owner reviews re-routed gaps and confirms or overrides.
Why click-level re-forecast instructions?
The re-forecast section is numbered steps that a non-engineer can follow by opening files and reading values. No command line, no scripts, no build tools. If the sponsor has to ask engineering how to update the model, the documentation has created a dependency instead of removing one.
- Preview the re-forecast section. Read each step as if you have never opened a terminal. If any step requires a command line, rewrite it.
- Add the source map, handover checklist, scope boundary, and change log to docs/owner-guide.md by pasting the following below the recovery section:
## Source Map
| File | Purpose |
|------|---------|
| commitments.md | Three design commitments |
| topology.md | End-to-end Mermaid topology diagram |
| data/skills-matrix.csv | Person-skill matrix with evidence |
| data/gap-table.md | Gap routing (buy/build/borrow) with dates |
| data/capacity-model.md | Competent hours per program per skill |
| data/ledger.yaml | Metric definitions (written before any number computed) |
| data/status-snapshot.yaml | Linear delivery status in version control |
| reports/report.md | Executive report with coverage comparison |
| validation/validation-record.md | Three checks under guard rule |
| docs/owner-guide.md | This document |
## Handover Checklist
- **GitHub repository:** [[REPO_OWNER="named owner"]], private, no paid tier required.
- **Linear workspace:** [[LINEAR_OWNER="named owner"]], Free plan.
- **Recurring cost:** Time for re-forecast plus evidence refresh, budgeted against [[REFORECAST_OWNER="named owner for re-forecast"]].
- **Credential locations:** Noted in team password manager. No values printed here.
## Scope Boundary
This build excludes compensation decisions.
## Change Log
| Date | Change | Business Impact |
|------|--------|----------------|
| [[RELEASE_DATE="today's date"]] | Initial release (v1.0.0) | Baseline coverage established |
What does the source map do?
The source map tells a non-engineer exactly where to find each artifact in the repository. Without it, the sponsor has to browse directory trees or ask engineering which file holds the capacity model.
The handover checklist names accounts and owners without printing credential values. The scope boundary explicitly excludes compensation decisions so the build cannot be misused to justify salary changes.
- Save docs/owner-guide.md.
- Preview the full document. Confirm the source map lists every file in your repository and the handover checklist contains no actual credential values.
What about the running cost?
No licence is bought. The Linear workspace, the GitHub repository, and the workflow automation are already owned. The recurring cost is time: the re-forecast plus the evidence refresh, budgeted against a named owner. If it will not fit, the fix is fewer skills tracked, not a longer cycle.
Source map missing a file?
Compare the source map table against your actual repository file list. If you added files in earlier steps that are not in the table (such as adrs/ or build-log.md), add them. The source map must account for every file in the repository.
Help me check if my source map covers every file in the repository
✔️ Awesome, I've got everything!
Great. Confirm you have saved both validation/validation-record.md and docs/owner-guide.md.
ⓧ I'd like to double check the full code
Scroll up and confirm each section is present in order:
- validation/validation-record.md: Declared Manifest table, Guard Rule statement, Check 1 (Matrix) with counts and assertions, Check 2 (Plan) with counts and assertions, Check 3 (Report) bullet assertions, and What the Build Cannot Assert section.
- docs/owner-guide.md: Value Proof, How to Re-forecast (7 numbered steps, no terminal), What to Do When It Breaks, Source Map table, Handover Checklist, Scope Boundary, and Change Log.
Tag the release
The validation record and documentation are ready. Before committing, update the status snapshot from Linear one final time and add the release entry to the build log.
- Return to your Linear project. Open the project Overview page.
- Export the current project status, milestone completion, and issue counts by label group into data/status-snapshot.yaml, replacing the previous snapshot.
- Add a final entry to build-log.md recording the release: today's date, v1.0.0 tagged, validation record committed, documentation committed.
- Commit the validation record, owner guide, updated status snapshot, and build log as the final PR by running these commands:
git add validation/validation-record.md docs/owner-guide.md data/status-snapshot.yaml build-log.md
git commit -m "Add validation record, owner guide, final status snapshot, and build log release entry"
- Push the branch and open a PR in GitHub.
- Include the Linear ticket ID in the PR title or description so the linked issues move to Done on merge.
- Merge the PR into main.
Why include the Linear ticket ID?
The workflow automation you configured in Step 2 moves linked issues to Done when PRs merge. Including the issue ID (or using a branch name containing it) triggers this automation automatically. You can verify this by checking your Linear project after the merge.
Before you tag, think about what the release should contain. Do you expect the tagged release to include only the files from this PR, or every file committed across all steps?
- Tag the release by running this command:
git tag -a v1.0.0 -m "Cross-program capability and capacity planning - validated release"
git push origin v1.0.0
What does this command do?
The git tag -a command creates an annotated tag at the current commit on main. The tag points to the entire repository state at this commit, which includes every artifact committed across all steps. The push sends the tag to the remote so it appears on GitHub.
- Open your GitHub repository in your browser.
- Click Releases in the right sidebar (or navigate to the Tags tab).
- Confirm that v1.0.0 appears with the tag message you wrote.
Now verify the release contains every artifact. Click the tag to browse the repository at that commit.
- Confirm the following files are present in the tagged commit: data/skills-matrix.csv, data/gap-table.md, data/capacity-model.md, data/ledger.yaml, data/status-snapshot.yaml, data/attrition-scenarios.md, data/attrition-rerouting.md, reports/report.md, validation/validation-record.md, and docs/owner-guide.md.
- Open validation/validation-record.md in the tagged commit. Confirm all three checks show expected = read with zero missing.
- Open docs/owner-guide.md in the tagged commit. Read the re-forecast section and confirm every step is followable without a terminal.
You should see every artifact present, every check passing with matched counts, and seven re-forecast steps that a non-engineer can follow by opening files and reading values.
Tag not showing on GitHub?
Make sure you ran git push origin v1.0.0 after creating the tag. Creating a tag locally does not automatically push it to the remote.
If the tag exists but files are missing, check whether the final PR was merged before you tagged. A tag captures the repository state at the current commit. If the PR was not merged, those files are not on main yet.
Help me figure out why my tagged release is missing files
- Return to your Linear project. Confirm that the tickets linked to your final PR have moved to Done automatically.
Secret mission
Executive Presentation and Adoption Metric
The artifacts are validated and the v1.0.0 release is tagged, but the VP of Engineering still needs to walk into a room and defend one number to finance. This mission builds a two-audience executive presentation, defines the adoption metric that proves the model stays alive after handover, and writes the lessons learned that name what this build proved and what it cannot assert.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely on free-tier tools, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- A private GitHub repository containing the skills matrix, talent plan, capacity model, metric ledger, attrition scenarios, report, validation record, and documentation.
- A Linear project with six epics, stories, tasks, milestones, and label groups (Phase, Artifact, Check).
- Local project files on your computer (your cloned repository folder).
Keep everything running
No action needed. Choose this if you plan to continue building on the capacity model or want to reference it for real portfolio work.
- Your GitHub repository persists at no cost on the Free plan.
- Your Linear project persists at no cost. The Free plan supports up to 250 issues.
- The model re-runs on the existing re-forecast cadence whenever a person joins, leaves, or finishes a ramp.
- The only recurring cost is your time: the re-forecast plus the evidence refresh, budgeted against a named owner.
Pause - I'll come back to this later
Archive the Linear project to reduce clutter while keeping everything recoverable. Your GitHub repository and local files remain accessible.
- In Linear, open your project.
- Open the project menu (the three-dot icon or right-click the project name) and select the archive option.
- Your GitHub repository stays as-is. No action needed there.
- Your local project files stay on your computer. No action needed.
- To resume later, unarchive the project from Linear's archived projects list.
Delete - I don't want to use this again
Remove all project resources. No paid resources need cleanup.
- Delete the GitHub repository:
- Delete or archive the Linear project:
- Delete your local project files:
cd ~/Desktop
ls
Find your project folder name in the output, then remove it.
rm -rf your-project-folder-name
On Windows, use this instead:
cd ~\Desktop
rmdir /s /q your-project-folder-name
That's a Wrap!
That's a Wrap!
Nice work. You just built a cross-program capability and capacity planning system for a portfolio of four programs, shipped through a real delivery process with a tagged release, validated artifacts, and documentation a non-engineer owner can follow.
You've learned how to:
- Build a skills matrix where every claim carries evidence or an UNPROVEN mark, validated against a declared manifest with person, skill, and date on every reference.
- Route every gap to buy, build, or borrow with a need-by date and a time-to-competence estimate, compute capacity in competent hours from a version-controlled metric ledger, and produce the headcount-vs-competent-hours comparison that makes the mis-sizing visible rather than implied.
- Inject departures into the model, re-run on the same definitions, name every result as a mitigation or a slip with a date, run three validation checks under a guard rule proving completeness rather than non-emptiness, and ship everything through a GitHub repo, Linear project, PRs closing tickets, and a tagged release with documentation for a non-engineer owner.
- Secret Mission: Built a two-audience executive presentation with an adoption metric, a decay rule, and lessons learned naming what the build proved and what it cannot assert.
Ready to quiz yourself?