A scanner tells you what's wrong. A daily driver helps you fix it.
tspec workis where the CLI stops being a tool and becomes a workflow.
Parts I through V built the foundations: a meta-metamodel, a requirement hierarchy, workflow states, language backends. All of that infrastructure exists so that this part can happen --- the moment where a developer sits down, types a single command, and everything else gets out of the way.
Most requirement tools live in a browser tab you forget to update. tspec lives in your terminal, next to your code, aware of your branch and your ACs. The difference is not cosmetic. It changes how work flows.
tspec work --item #ID
This is the core DX command. You have an item to work on --- a bug, a story, a feature, a support ticket, anything tracked in the DSL hierarchy. You run one command.
Here is what happens behind the scenes:
- If multiple Diem instances are configured in
.tspecrc.json, tspec asks which one to connect to. - It fetches the item from the Diem API: acceptance criteria, current state, assignee, linked tests, parent hierarchy.
- It creates a branch (or checks out an existing one). Bugs get
bugfix/#ID-slug, features getfeature/#ID-slug, stories getstory/#ID-slug. - It opens relevant files in your IDE --- the requirement definition, any existing test files that reference the item.
- It transitions the workflow state to InProgress.
- If the item is unassigned, it assigns it to you.
- It prints a summary: ACs to implement, current coverage, linked bugs.
- It writes a
.tspec-contextfile so every subsequent command knows what you are working on.
One command. No tab-switching, no copy-pasting ticket numbers, no forgetting to update state.
$ tspec work --item BUG-42
Connected to: https://tspec.acme-corp.internal
Item: NegativeTotalBug (Bug<CheckoutFeature>)
Severity: Critical | Level: L3 | State: Draft → InProgress
ACs to implement:
✗ negativeAmountIsRejected (no test)
✗ existingOrdersAreUnaffected (no test)
Parent: CheckoutFeature (4/6 ACs covered)
Branch: bugfix/BUG-42-negative-total
Opened: requirements/bugs/negative-total.ts, test/e2e/checkout.spec.ts$ tspec work --item BUG-42
Connected to: https://tspec.acme-corp.internal
Item: NegativeTotalBug (Bug<CheckoutFeature>)
Severity: Critical | Level: L3 | State: Draft → InProgress
ACs to implement:
✗ negativeAmountIsRejected (no test)
✗ existingOrdersAreUnaffected (no test)
Parent: CheckoutFeature (4/6 ACs covered)
Branch: bugfix/BUG-42-negative-total
Opened: requirements/bugs/negative-total.ts, test/e2e/checkout.spec.tsYou now have the branch, the files, and a checklist. Write code.
tspec status
Twenty minutes pass. A colleague pings you. You context-switch. When you come back: what was I doing?
tspec status reads .tspec-context and tells you.
$ tspec status
Working on: BUG-42 — NegativeTotalBug (Bug<CheckoutFeature>)
State: InProgress (2h 15m)
ACs: 1/2 covered (local scan)
0/2 covered (last pushed)
Branch: bugfix/BUG-42-negative-total$ tspec status
Working on: BUG-42 — NegativeTotalBug (Bug<CheckoutFeature>)
State: InProgress (2h 15m)
ACs: 1/2 covered (local scan)
0/2 covered (last pushed)
Branch: bugfix/BUG-42-negative-totalLocal scan means tspec looked at your working tree right now. Last pushed means what the Diem instance knows about. The gap between those two numbers is your unpushed progress.
Time-in-state is not a performance metric. It is a context cue. If you see InProgress (3d 4h) on a story estimated at half a day, something is blocked and you should probably escalate or split the item.
tspec done
You wrote the tests. The ACs are green. Ship it.
$ tspec done
Scanning... ✓ 2/2 ACs covered
Pushing to https://tspec.acme-corp.internal... ✓
Transitioning BUG-42: InProgress → Review... ✓
Opening PR: bugfix/BUG-42-negative-total → main... ✓
BUG-42 is now in Review.$ tspec done
Scanning... ✓ 2/2 ACs covered
Pushing to https://tspec.acme-corp.internal... ✓
Transitioning BUG-42: InProgress → Review... ✓
Opening PR: bugfix/BUG-42-negative-total → main... ✓
BUG-42 is now in Review.Behind that clean output, five things happened in sequence:
tspec scanran against your working tree.- tspec checked: are all ACs covered by at least one test?
- Yes --- so it pushed coverage data to the Diem instance.
- It transitioned the item from InProgress to Review.
- It opened a pull request with the AC coverage summary in the description.
What if coverage is incomplete?
$ tspec done
Scanning... ✗ 1/2 ACs covered
Missing:
✗ existingOrdersAreUnaffected (no test)
Submit partial? [y/N]:$ tspec done
Scanning... ✗ 1/2 ACs covered
Missing:
✗ existingOrdersAreUnaffected (no test)
Submit partial? [y/N]:You choose. If you submit partial, the PR description will flag the gap and the item moves to Review with a warning. The reviewer sees exactly what is missing. No guessing.
tspec next
You finished BUG-42. What now?
$ tspec next
Your backlog (sprint 14):
1. BUG-55 Critical LoginTimeout (L2 SupportTicket) SLA: 6h remaining
2. FEAT-12 High PasswordReset (Feature<IdentityEpic>) 3 ACs, 0 covered
3. BUG-58 Major CSVExport encoding (Bug<ReportFeature>)
4. STORY-9 Medium Dark mode toggle (Story<ThemeFeature>)
5. TASK-22 Low Update dependencies
Pick [1-5]:$ tspec next
Your backlog (sprint 14):
1. BUG-55 Critical LoginTimeout (L2 SupportTicket) SLA: 6h remaining
2. FEAT-12 High PasswordReset (Feature<IdentityEpic>) 3 ACs, 0 covered
3. BUG-58 Major CSVExport encoding (Bug<ReportFeature>)
4. STORY-9 Medium Dark mode toggle (Story<ThemeFeature>)
5. TASK-22 Low Update dependencies
Pick [1-5]:tspec queries the Diem API for items assigned to you, plus unassigned items in the current sprint. It sorts by priority, then by support level (L1 tickets before L2 before L3), then by age.
In maintenance mode --- when the team is between sprints or handling production issues --- bugs sort by severity first, then support tickets by SLA deadline. The item closest to breaching its SLA floats to the top.
Pick a number. tspec runs tspec work --item on your choice. The cycle restarts.
tspec context #ID
Sometimes you need to look without touching. Code review, triage meetings, sprint planning --- you want the full picture of an item without starting work on it.
$ tspec context FEAT-12
PasswordReset (Feature<IdentityEpic>)
State: Proposed | Priority: High | Assignee: unassigned
Sprint: 14
ACs:
1. resetEmailIsSentWithinOneMinute — no test
2. expiredTokenShowsHelpfulError — no test
3. passwordStrengthIsEnforcedOnReset — no test
Linked bugs: BUG-61 (tokenExpiryRace, Critical)
Parent: IdentityEpic (2/5 features started)
History: created 2026-03-15, proposed 2026-03-20$ tspec context FEAT-12
PasswordReset (Feature<IdentityEpic>)
State: Proposed | Priority: High | Assignee: unassigned
Sprint: 14
ACs:
1. resetEmailIsSentWithinOneMinute — no test
2. expiredTokenShowsHelpfulError — no test
3. passwordStrengthIsEnforcedOnReset — no test
Linked bugs: BUG-61 (tokenExpiryRace, Critical)
Parent: IdentityEpic (2/5 features started)
History: created 2026-03-15, proposed 2026-03-20Read-only. No branch, no state change, no assignment. Just information.
tspec handoff --to=bob@acme-corp.com
You are going on vacation. Or a support ticket is escalating from L2 to L3 and needs a different developer. Or the afternoon shift is starting.
$ tspec handoff --to=bob@acme-corp.com --note="AC 1 is done, AC 2 needs the edge case for expired coupons"
Pushing current state to Diem... ✓
Reassigning BUG-42 to bob@acme-corp.com... ✓
Handoff note saved.
Clearing local context.$ tspec handoff --to=bob@acme-corp.com --note="AC 1 is done, AC 2 needs the edge case for expired coupons"
Pushing current state to Diem... ✓
Reassigning BUG-42 to bob@acme-corp.com... ✓
Handoff note saved.
Clearing local context.The receiving developer runs tspec work --item BUG-42 and sees the handoff note alongside the ACs. No information is lost in the transfer.
tspec pair --with=alice@acme-corp.com
Pair programming on a tricky feature. Both developers show as assignees on the Diem dashboard. Both get notifications. Both appear in the coverage audit trail.
$ tspec pair --with=alice@acme-corp.com
Added alice@acme-corp.com as co-assignee on BUG-42.
Both assignees visible on dashboard.$ tspec pair --with=alice@acme-corp.com
Added alice@acme-corp.com as co-assignee on BUG-42.
Both assignees visible on dashboard.Simple. No ceremony.
Other DX Commands
A brief catalogue of the remaining commands:
tspec tour--- Interactive onboarding walkthrough. Creates a sample requirement, runs a scan, shows the output. Takes five minutes. Designed for the first day on a new project.tspec add feature|story|bug --parent=X--- Scaffold a new requirement item. Generates the typed definition file, links it to the parent, opens it in your editor.tspec deprecate #ID--- Mark an item as deprecated. The scanner will warn (not error) on references. Existing tests stay valid but flagged.tspec split #ID --into=A,B--- Split a feature that grew too large. ACs are redistributed interactively. Coverage links update automatically.tspec merge #ID1 #ID2 --into=Combined--- Merge duplicate items. ACs are combined, tests are relinked, history is preserved from both sources.
The .tspec-context File
Every tspec work writes a small JSON file to the repository root. It is gitignored --- this is local, per-developer state.
{
"instance": "https://tspec.acme-corp.internal",
"item": "BUG-42",
"branch": "bugfix/BUG-42-negative-total",
"startedAt": "2026-03-28T09:15:00Z",
"user": "dev@acme-corp.com"
}{
"instance": "https://tspec.acme-corp.internal",
"item": "BUG-42",
"branch": "bugfix/BUG-42-negative-total",
"startedAt": "2026-03-28T09:15:00Z",
"user": "dev@acme-corp.com"
}tspec status, tspec done, tspec handoff, and tspec pair all read this file. It is the reason those commands do not need an --item flag every time. You set context once with work, and everything else follows.
If the file is missing, those commands ask you to specify an item or run tspec work first. No silent failures, no stale state.
Before and After
The old way:
The red boxes are where things fall through the cracks. Coverage is a guess. State updates are manual and late.
The tspec way:
Three steps. No red boxes. Coverage is scanned, not guessed. State transitions are automatic, not forgotten. The PR includes the coverage summary, not a hand-typed description.
The difference is not that tspec does something magical. It is that tspec removes the manual steps where humans forget things. The developer focuses on code. The CLI handles the bookkeeping.
Previous: Part V: Language Backends Next: Part VII: The Diem Instance