rmb32 / barnspek-cli
Barnspek CLI — the wizard plus Writer/Glossary/StoryMap rendering commands as a standalone CLI transport package
Requires
- php: ^8.5
- ext-ds: ^2.0
- php-ds/php-ds: ^2.0
- rmb32/barn-paths: ^0.3@dev
- rmb32/barnspek: ^0.1@dev
- rmb32/filesystem: ^1.0@dev
- rmb32/menu: ^1.0@dev
- rmb32/typed-input: ^2.0@dev
- symfony/console: ^8.0
Requires (Dev)
- deptrac/deptrac: ^4.7
- infection/infection: ^0.35.4
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^12.0
- rmb32/barnspek-gui: ^0.1@dev
- rmb32/barntree: ^0.1.5@dev
- rmb32/typed-json: ^1.0@dev
- squizlabs/php_codesniffer: ^4.0
Suggests
- rmb32/barnspek-gui: Adds `barnspek gui`, which serves the browser UI and opens it
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-14 21:04:45 UTC
README
Early, version 0.x. Not ready for real projects yet; the minor version is the breaking one.
Part of Barn.
The command line for Barnspek: capture user stories and acceptance scenarios through an interactive wizard (or from scripts), then turn them into readable Markdown, a glossary, a visual history of every phase, and tracked work tickets.
Working with an AI agent? Run vendor/bin/barnspek skills:install first. It gives your agent skills for capturing Stories and Scenarios, shaping a Phase and proving Scenarios with Port Generator and Scenario Bridge. See Agent skills.
Requirements
- PHP 8.5+
Installation
composer require --dev rmb32/barnspek rmb32/barnspek-cli
The barn
Barnspek keeps everything you capture in a barn: a .barn/ folder at your project's root. Make one
with barnspek init:
vendor/bin/barnspek init
# Made a barn: /path/to/project
# Barnspek keeps its spec in: /path/to/project/.barn/barnspek
Every other command finds the barn from any folder inside it, names it first (Barn: /path/to/project,
on standard error) and saves to .barn/barnspek/ as JSON, safe to commit. Outside a barn they refuse
and write nothing. Inside another barn, init asks before making a separate one; pass --separate-barn
to make it without asking.
Capture stories
vendor/bin/barnspek create-area-of-business --title="Commerce" --meaning="Selling things online" \
--phase="Launch" --phase-meaning="First release"
vendor/bin/barnspek create-story --area-of-business="Commerce" --role="Customer" --role-meaning="Someone who buys" \
--goal="Update basket quantities" --reward="get exactly what they want" --stage=adjust
vendor/bin/barnspek refine # build out a story's Given/When/Then Scenarios
vendor/bin/barnspek create-scenario --file=scenario.json # the same, for scripts and CI
vendor/bin/barnspek show # list captured areas of business
vendor/bin/barnspek show commerce --json # one area of business's full outline
refine needs a real terminal. A story with no Scenario yet shows as rough in
history.
A new area of business always starts with its first phase (--phase defaults
to "Phase 1"). Areas of business, roles, stories, subjects and facts can be named by their
slug (commerce) or by the title they were created with (Commerce). A
story's reward follows "so that they…".
scenario.json names everything by title or slug. A parameter is only described
(meaning, type) the first time its Fact or action uses it — a new Fact needs
its parameters described even when another Fact of the same Subject already has
one by that name. One fact can appear more than once:
{
"area-of-business": "reservations",
"role": "guest",
"story": "find which rooms are free for their nights",
"title": "search for rooms free from 10 November for 2 nights",
"actionSentence": "search for rooms free for some nights",
"actionParameters": {
"check-in-date": { "meaning": "the first night of the stay", "type": "string", "value": "2026-11-10" },
"nights": { "meaning": "how many nights the stay lasts", "type": "integer", "value": 2 }
},
"given": [
{ "subject": "room", "subjectMeaning": "a room the hotel lets", "fact": "is on offer",
"factMeaning": "the room is part of what the hotel lets to guests",
"parameters": { "room-number": { "meaning": "the number on the door", "type": "string", "value": "101" } } },
{ "subject": "room", "fact": "is on offer", "parameters": { "room-number": { "value": "102" } } }
],
"outcome": "accepted",
"then": []
}
title and meaning are optional and default to the action sentence, which
lets several Scenarios share one action. A Fact's meaning is a state — it is
read after both "Given" and "Then".
Change what you captured
Everything in a phase can be reworded or deleted until that phase is released.
vendor/bin/barnspek reword --area-of-business=front-desk --subject=stay --fact="is under way" \
--meaning="the guest has arrived and is in the room"
vendor/bin/barnspek reword --area-of-business=front-desk --subject=stay --fact="is under way" --title="is in progress"
vendor/bin/barnspek reword --area-of-business=front-desk --action="check a guest in" --parameter=room \
--meaning="the room they are given"
vendor/bin/barnspek reword --area-of-business=front-desk --story="check a guest in" --reward="the guest has a key"
vendor/bin/barnspek reword --area-of-business=front-desk --phase="Phase 1" --meaning="checking guests in and out"
vendor/bin/barnspek delete --area-of-business=front-desk --story="check a guest in" # with its Scenarios
vendor/bin/barnspek delete --area-of-business=front-desk --story="check a guest in" --scenario="a typo"
vendor/bin/barnspek delete --area-of-business=front-desk --subject=stay --fact="is haunted"
reword takes --title, --meaning or both (a story takes --goal and
--reward); what you leave out stays as it is. Every Scenario that uses it
keeps using it under the new words. A parameter is reached through its fact
(--subject, --fact, --parameter) or its action (--action, --parameter).
A parameter can carry an example, shown after its meaning on the glossary. It illustrates the parameter and changes nothing that was released, so it can be set at any time, even once its phase is released. An empty value removes it:
vendor/bin/barnspek set-example --area-of-business=front-desk --action="check a guest in" --parameter=room --example="Room 12"
vendor/bin/barnspek set-example --area-of-business=front-desk --subject=stay --fact="is booked" --parameter=nights --example=3
vendor/bin/barnspek set-example --area-of-business=front-desk --action="check a guest in" --parameter=room --example=
delete says what goes, including each Scenario's Work tickets and last run,
and asks before deleting; pass --yes in a script. A subject, fact, action or
parameter is deleted only once no Scenario uses it, and a subject only once it
has no facts.
Once a phase is released, its stories, their roles and Scenarios, the
subjects, facts and actions those Scenarios use, and the phase itself are
locked: reword and delete refuse, and the change goes through an
elaboration instead (see below).
Phases
vendor/bin/barnspek release-phase --area-of-business=commerce # the active phase
vendor/bin/barnspek release-phase --area-of-business=commerce --phase=launch
vendor/bin/barnspek create-phase --area-of-business=commerce --title="Growth" --meaning="More to sell"
One phase is active at a time: release it before starting the next.
A phase is released only once it is proven: every story in it needs a passing
scenario-bridge run behind each of its Scenarios, with their tickets done. An
elaboration that only rewords, restages or retires has nothing to prove. Until
then release-phase names each story that isn't proven and stops, before it
asks anything. A phase with no stories in it can't be released.
Once it is proven, release-phase lists what the release locks, and asks before releasing;
pass --yes in a script. Then it asks about every story released in an
earlier phase whose reward nobody has settled yet (see below). Leave an answer
blank to come back to it next time. Pass --skip-impact-review, or run it
without a terminal, and it lists them instead. Either way, the phase is
released.
Record whether a reward was delivered
A story's Scenarios prove the role can do what it wanted. Its Impact says whether that gave them what the story promised:
vendor/bin/barnspek record-impact --area-of-business=reservations --story=book-a-room \
--state=achieved --observation="I booked without phoning the front desk" \
--source=role --who="Sam (guest)"
vendor/bin/barnspek record-impact --area-of-business=reservations --story=book-a-room --phase=suites \
--state=none --observation="guests still phone to book a suite" \
--source=measured --who="front desk call log" --observed-at=2026-10-01
--state:evaluating(too early to say),achieved,none(works, but did not deliver) orsuperseded(the need moved on).--source:role,on-their-behalf(the client, a manager, a support desk),measuredorteam.achievedis settled only byroleoron-their-behalf. Frommeasuredorteamit stays open until they say so too.noneandsupersededare settled whoever says them.--phaseis needed only when the story shipped in more than one released phase that is still being evaluated (its own, and each elaboration's).--observed-atdefaults to today. Left out in a terminal, the other answers are asked for.
To see what still needs an answer without releasing anything, and to see what was recorded:
vendor/bin/barnspek outstanding-impacts --area-of-business=reservations # add --json for scripts
vendor/bin/barnspek release-notes --area-of-business=reservations --phase="Phase 1" # a released phase's notes, as Markdown
vendor/bin/barnspek show reservations # each story's phase and impacts
write adds an Impact line to each released story, and history
gives each released card a 🎯 badge. Neither changes a story's colour: a story
can pass every Scenario and still have delivered nothing.
Change a released story
A released story's Scenarios no longer change directly; create-scenario
refuses and tells you what to run. Elaborate the story in the active phase
instead — it is merged into the story when that phase is released:
vendor/bin/barnspek elaborate-story --area-of-business=reservations --story=cancel-their-booking \
--reward="they are not held to a stay they will not take, and asking twice does no harm"
vendor/bin/barnspek elaborate-story --area-of-business=reservations --story=cancel-their-booking \
--goal="cancel their booking" --revise # change the open elaboration's wording
vendor/bin/barnspek elaborate-story --area-of-business=reservations --story=cancel-their-booking \
--stage=resolve # move it to another stage when the phase is released
Capture into it with create-scenario, adding one key to the usual file:
{ "...": "...", "elaboration": true }
{ "...": "...", "amends": "cancel-a-booking-that-has-already-been-cancelled" }
amends replaces one of the story's Scenarios with this one (it may keep the
same title). refine offers a released story once it is elaborated and
captures into the elaboration.
vendor/bin/barnspek retire-scenario --area-of-business=reservations --story=cancel-their-booking \
--scenario=cancel-a-confirmed-booking # stops holding when the phase is released
vendor/bin/barnspek withdraw-from-elaboration --area-of-business=reservations --story=cancel-their-booking \
--scenario=cancel-a-confirmed-booking # undo a pending addition, amendment or retirement
show lists each elaboration under its story; write adds an "Elaboration
in …" section while it is open and a "Changes in …" record once merged; and
history draws it as its own card in its phase.
Place a story on the lifecycle backbone
Every story can sit at one stage of the role's life with the business. The vocabulary is fixed across every area of business, so their maps line up column for column:
| Stage | The role… |
|---|---|
establish | gains what it needs to take part — signs up, registers, is granted access, or creates the thing later stories act on |
adjust | changes something that already exists without completing the core exchange — edits details, reconfigures, corrects a mistake |
transact | completes the exchange the area of business exists for — pays, places the order, submits, publishes |
resolve | gets what already happened to a settled state — tracks it, chases it, disputes a charge, claims a refund, asks for help |
close | ends something for good — cancels, closes, deletes, archives |
--stage is optional everywhere. A story without one is Unassigned, which
is a permanent home for stories that are not about a lifespan at all
(reporting, compliance, notification), not a to-do list.
vendor/bin/barnspek restage-story --area-of-business=commerce --story=update-basket-quantities --stage=transact
vendor/bin/barnspek restage-story --area-of-business=commerce --story=update-basket-quantities --clear
restage-story works until the story's phase is released. After that, the
stage changes through an elaboration, like its wording:
elaborate-story ... --stage=close.
Render what you've captured
vendor/bin/barnspek write commerce --output-dir=docs/stories/commerce # one doc per Story
vendor/bin/barnspek glossary commerce --output=docs/glossary.md
vendor/bin/barnspek glossary --output-dir=docs/glossary # every area of business + index.md
vendor/bin/barnspek history --output-dir=docs/history --stories-dir='../stories/{area-of-business}' # HTML history, phase by phase
vendor/bin/barnspek map --area-of-business=commerce --role=customer # the story map, printed
map prints the story map: every story once, as it reads now, under its lifecycle
stage and grouped by role. A story changed by a released elaboration shows its new
wording and is marked (elaborated); one whose phase isn't released yet is marked
(planned). --role is repeatable and leaves out every other role. --subject=<subject>
keeps only the stories with a scenario whose Given or Then is about that subject; a
scenario replaced in a released phase doesn't count. It is the
GUI's Story map page, drawn from the same query.
The history links each card to its Story's document and each area of business to its glossary.
--stories-dir and --glossary-dir are relative to --output-dir;
{area-of-business} in --stories-dir stands for each area of business's slug, so Stories
written one folder per area of business (as above) are found. Story slugs are only unique
within an area of business, so one folder per area of business is the safe layout. --glossary-dir
defaults to ../glossary, the folder glossary --output-dir=docs/glossary
writes.
The history's columns are the five lifecycle stages (plus Unassigned, when
anything sits there) and its rows are Phases, newest at the top; each card carries its role as a
chip. Each card shows its status: ✏️ rough (no scenarios yet), 📋 expressed
(scenarios, nothing ticketed), 🔨 in progress, 📦 built but not proven by a
passing scenario-bridge run, or ✅ fulfilled. A legend above the grid says what
each icon means. Each Phase row says whether it is released, ready to release
(every story in it fulfilled) or in progress; released rows start folded shut.
It is the GUI's History page, drawn from the same query, so the two always
agree. write includes a story's stage and that stage's meaning in the Markdown
document; show prints it in square brackets after each story.
Track work
vendor/bin/barnspek work:create commerce basket-quantity-updates caps-at-the-stock-level \
--title="Cap quantity" --description="Never add more than is in stock" --reference=SHOP-42
vendor/bin/barnspek work:list commerce basket-quantity-updates caps-at-the-stock-level
vendor/bin/barnspek work:status commerce SHOP-42 in-progress
vendor/bin/barnspek work:delete commerce SHOP-42
work:status and work:delete take the ticket's id or the reference it was created with. A
reference shared by two tickets in the same area of business is refused; name one by its id.
Tickets are disposable. work:delete asks before it deletes one (--yes skips the question), at any
time, released Stories included. Releasing a Phase deletes the done tickets of Scenarios that have been run,
and deleting a Scenario or Story deletes its tickets. The runs stay, so neither a release nor deleting
a done ticket changes a colour. Opening a ticket discards the Scenario's last run, so after deleting one
opened by mistake, run the Scenario again.
A ticket belongs to a Scenario, so each command names the area of business, the Story and
the Scenario within it. There is no work:confirm: a Scenario goes green only
when vendor/bin/scenario-bridge run passes against it, and work:list prints
that last run beside the colour. Run work:create with no arguments in a real terminal
to pick an area of business, role, Story and Scenario instead. A Story with no Scenario
captured yet is refused — run refine first.
Start over
vendor/bin/wizard-reset # preview what would be deleted
vendor/bin/wizard-reset --force # delete the barn's areas of business and Scenarios
Prefer a browser?
Install Barnspek GUI alongside this package and the
barnspek binary gains a gui command that serves it and opens it:
composer require --dev rmb32/barnspek-gui
vendor/bin/barnspek gui
Run any command with --help. The Barnspek quick start (docs/quick-start.md in
rmb32/barnspek) shows the wizard
and the scripted commands side by side.
Agent skills
If you work with an AI agent, copy Barnspek's agent skills into your project:
vendor/bin/barnspek skills:install
That puts five skills in .claude/skills/, the folder Claude Code reads (--dir=<folder> for another
agent's). They teach your agent what Barnspek keeps, the commands that read and the ones that write, and how to write Stories, Scenarios and Phases the Barnspek way, and how to prove a Scenario with Port Generator and Scenario Bridge (setup and every command). Ask it to turn a description of what you want next into a Phase, review your Stories, or check whether a Phase is ready to release: it reads what you have captured first, drafts, asks you grouped questions, and writes through the commands only once you agree.
Run it again after updating Barnspek: files you have changed are kept unless you pass --force.
Related packages
- Barnspek — the core library and full documentation.
- Barnspek GUI — the same capture and Work board in a browser.
- Port Generator and Scenario Bridge — turn Scenarios into runnable tests.
- Scenario Runner CLI — scaffold unit tests for Work tickets.
More docs
License
Proprietary. See LICENSE. Copyright (c) Roger Barnfather.