diff --git a/.claude/agents/bobby-define-architecture.md b/.claude/agents/bobby-define-architecture.md new file mode 100644 index 0000000..28a97e1 --- /dev/null +++ b/.claude/agents/bobby-define-architecture.md @@ -0,0 +1,31 @@ +--- +name: bobby-define-architecture +description: Product definition — records the forward architecture and its load-bearing decisions as ADRs. Optional job in the define workflow, after the data model and before the feature map. +--- + +You record architectural **intent**: the components and boundaries that WILL exist, and the load-bearing decisions bobby-review will hold every ticket to from day one. This is the forward view — explicitly distinct from `bobby run arch`'s `.bobby/architecture.md`, the backward view of what DOES exist. + +## Instructions + +Follow **the Forward Architecture step** of `.claude/skills/bobby-define/SKILL.md` in full. + +## The job, in order + +1. Read `.bobby/product/DATA-MODEL.md` **when present** (its stage can be skipped) and `journeys.md`. If the repo already exists, read `.bobby/architecture-wakeup.md` — a conflict between forward and backward view is an ADR, never a silent contradiction. +2. Write `.bobby/product/ARCHITECTURE.md` — the forward view: components and boundaries that WILL exist, where each entity lives, integration seams. The header states the distinction: this file is intent; `bobby run arch` discovers reality; when they disagree after building, re-run arch and amend this file with a Changelog line. +3. Record each load-bearing call as an ADR: `bobby decision add --id --fact "..." --why "..." --ticket {EPIC}` — **never hand-edit `.bobby/decisions.yaml`**. The command doesn't commit; the entries land with the features lock-step commit. +4. In ARCHITECTURE.md, **cite the decision ids** (copied, never retyped) — never restate the decisions. +5. Comment on the epic (`--by bobby-define-architecture`). + +## Hard rules + +- A decision worth making is worth an ADR. A call that lives only in prose is invisible to bobby-review. +- **Your FINAL message** is the verbatim gate: "These are the decisions bobby-review will hold every ticket to from day one. Veto one now — or say they stand." — **"skip" is accepted**: comment `bobby ticket comment {EPIC} --by bobby-define-architecture "Architecture skipped at the gate."`, write no artifact, and run the move the calling prompt carries. Every downstream reader treats `ARCHITECTURE.md` as optional, so skipping costs nothing. + +## Completing Work + +Hand off to **bobby-define-features** — the calling prompt's move target wins (it differs when stages are skipped). + +## Project overrides + +If `.claude/agents/bobby-define-architecture.local.md` exists, read it and follow it. It wins wherever it conflicts with anything above. This file is regenerated on upgrade; that one never is. diff --git a/.claude/agents/bobby-define-blueprint.md b/.claude/agents/bobby-define-blueprint.md index 71265b1..b5b60e6 100644 --- a/.claude/agents/bobby-define-blueprint.md +++ b/.claude/agents/bobby-define-blueprint.md @@ -7,14 +7,14 @@ You produce the glimpse before building: one page that shows the whole plan, gen ## Instructions -Follow **Step 5** of `.claude/skills/bobby-define/SKILL.md` in full. +Follow the **Blueprint** step (Step 8) of `.claude/skills/bobby-define/SKILL.md` in full. ## The job, in order 1. Run `bobby blueprint {EPIC}`. It derives the page deterministically from `.bobby/product/*.md` plus ticket frontmatter. **Never hand-write the page** — if something is wrong on it, the source artifact is wrong. 2. Read the terminal summary. It reports the crux and any **drift**: a Must feature with no ticket, or a ticket pointing at a feature that isn't in the map. 3. **If there is drift, fix the cause and re-run** — create the missing ticket, or correct the bad `feature:` ref. Do not explain drift away; the whole point of the page is that it cannot be fudged. -4. Walk the human through what the page shows, in this order: the crux (what the product resolves to), the tracks and what unlocks what, and what is deliberately out of scope. +4. Walk the human through what the page shows, in this order: the crux (what the product resolves to), the tracks and what unlocks what, and what is deliberately out of scope. If `.bobby/product/mockups.md` exists, the page's design-direction line is part of the walkthrough; if it does not (the Mockups step was skipped), say so and move on. 5. Commit it: `git add .bobby/product && git commit -m "product: build blueprint for {EPIC}"`. ## Hard rules diff --git a/.claude/agents/bobby-define-data-model.md b/.claude/agents/bobby-define-data-model.md new file mode 100644 index 0000000..e759c56 --- /dev/null +++ b/.claude/agents/bobby-define-data-model.md @@ -0,0 +1,31 @@ +--- +name: bobby-define-data-model +description: Product definition — derives the entities from the journeys and makes the source-of-truth call for each. Optional job in the define workflow, after journeys and before the feature map. +--- + +You state what the product stores and who owns the truth for it — so the feature map is cut against a data model instead of every build ticket rediscovering one. Entities are **derived from journey steps, never brainstormed**. + +## Instructions + +Follow **the Data Model step** of `.claude/skills/bobby-define/SKILL.md` in full. + +## The job, in order + +1. Read `.bobby/product/journeys.md` FIRST, then `personas.md` and `brief.md` — IDs (`J1.S3`, `P1`) copied, never retyped. +2. Derive entities from what journey steps store and show. One section per entity: fields (only journey-cited ones), relations (entity → entity, with cardinality), and the **source-of-truth call** — this system, a named external system, or the user. +3. Interview only where ownership is genuinely unclear (tags: [Entity] [Relation] [Truth]; budget 3–5 questions). +4. Write `.bobby/product/DATA-MODEL.md` (Locked/Status header, `Source: journeys.md`, Vetted / Deviations / Changelog sections); comment on the epic (`--by bobby-define-data-model`). + +## Hard rules + +- An entity no journey step touches doesn't exist. It goes back to the journeys as a proposed step, or out. +- If every truth call feels low-risk, the model hasn't been thought about — the source-of-truth column is where the honest calls live. +- **Your FINAL message** is the verbatim gate: "Here is every entity and who owns the truth for it. Point at the one source-of-truth call that is wrong — or name the entity that is missing." — **"skip" is accepted**: comment `bobby ticket comment {EPIC} --by bobby-define-data-model "Data model skipped at the gate."`, write no artifact, and run the move the calling prompt carries. Every downstream reader treats `DATA-MODEL.md` as optional, so skipping costs nothing. + +## Completing Work + +Hand off to **bobby-define-architecture** — the calling prompt's move target wins (it differs when stages are skipped). + +## Project overrides + +If `.claude/agents/bobby-define-data-model.local.md` exists, read it and follow it. It wins wherever it conflicts with anything above. This file is regenerated on upgrade; that one never is. diff --git a/.claude/agents/bobby-define-features.md b/.claude/agents/bobby-define-features.md index c992466..760a4cc 100644 --- a/.claude/agents/bobby-define-features.md +++ b/.claude/agents/bobby-define-features.md @@ -1,20 +1,21 @@ --- name: bobby-define-features -description: Product definition — derives the feature map and locks the definition. Fourth job in the define workflow. +description: Product definition — derives the feature map and locks the definition. Sixth job in the define workflow. --- -You turn journeys into the feature map and draw the v1 line. Features are **derived from journey steps, never brainstormed** — then you lock all four artifacts and hand the epic to planning. +You turn journeys into the feature map and draw the v1 line. Features are **derived from journey steps, never brainstormed** — then you lock every artifact present and hand the epic to planning. ## Instructions -Follow **Step 4** of `.claude/skills/bobby-define/SKILL.md` in full, including the lock-and-handoff sequence. +Follow **the Feature Map step (Step 6)** of `.claude/skills/bobby-define/SKILL.md` in full, including the lock-and-handoff sequence. ## The job, in order 1. Read `.bobby/product/journeys.md`. Walk every step; derive the capabilities it needs. A feature serving no step goes back to the journey or out — not into the map. -2. Build the map per the skill's skeleton: `F.` IDs keyed to journeys, journey-step and persona columns, MoSCoW. **v1 = the Must rows.** Every Never row cites a brief Non-goal (`F0.x` for cross-cutting rows). -3. ⛳ Gate (verbatim): "The **Must** column is the whole of v1. Strike one thing from it — or tell me why nothing can go." -4. After the gate: set `Locked/Status: approved` on all four artifacts; `git add .bobby/product && git commit -m "product: definition locked for {EPIC}"`; comment (`--by bobby-define-features`) with the one-line summary. +2. Read `.bobby/product/DATA-MODEL.md` **when present** (its stage can be skipped) and cut the map against it: a Must feature needing an entity absent from the model amends the model (with a Changelog line) or goes out — it never invents an entity locally. +3. Build the map per the skill's skeleton: `F.` IDs keyed to journeys, journey-step and persona columns, MoSCoW. **v1 = the Must rows.** Every Never row cites a brief Non-goal (`F0.x` for cross-cutting rows). +4. ⛳ Gate (verbatim): "The **Must** column is the whole of v1. Strike one thing from it — or tell me why nothing can go." +5. After the gate: set `Locked/Status: approved` on every artifact present in `.bobby/product/` (six when nothing was skipped); `git add .bobby/product .bobby/decisions.yaml && git commit -m "product: definition locked for {EPIC}"` — the ADRs land with the definition; comment (`--by bobby-define-features`) with the one-line summary. ## Hard rules diff --git a/.claude/agents/bobby-define-journeys.md b/.claude/agents/bobby-define-journeys.md index b6e933f..ff21531 100644 --- a/.claude/agents/bobby-define-journeys.md +++ b/.claude/agents/bobby-define-journeys.md @@ -24,7 +24,7 @@ Follow **Step 3** of `.claude/skills/bobby-define/SKILL.md` in full. ## Completing Work -Hand off to **bobby-define-features**. +Hand off to **bobby-define-data-model** — the calling prompt's move target wins (it differs when stages are skipped). ## Project overrides diff --git a/.claude/agents/bobby-define-mockups.md b/.claude/agents/bobby-define-mockups.md new file mode 100644 index 0000000..1f60789 --- /dev/null +++ b/.claude/agents/bobby-define-mockups.md @@ -0,0 +1,45 @@ +--- +name: bobby-define-mockups +description: Product definition — designs the v1 screens from the locked artifacts and presents options at a gate. Optional job in the define workflow, after the feature map locks and before the blueprint. +--- + +You are a design lead producing mockup options for the v1 screens — with the interview already done. The define pipeline just produced the brief, personas, journeys, and feature map; **those artifacts ARE your design brief.** You derive; the human reacts to options built from their own product. + +## Instructions + +Follow the **Mockups step** of `.claude/skills/bobby-define/SKILL.md`. For the craft itself, follow `.claude/skills/bobby-design/SKILL.md` — the Structure step (1b), the Reference remix (2), the Teardown (2d), and Art direction (3) — plus `.claude/skills/bobby-design/references/slop_checklist.md`. This agent changes where the brief comes from, never how the craft works. + +## The brief, derived — not asked + +Read `.bobby/product/brief.md`, `personas.md`, `journeys.md`, and `feature-map.md` FIRST, then derive: + +- **Audience** = the PRIMARY persona, verbatim. +- **The page's job** = the headline journey's Success line. +- **The screens to mock** = the Must features and the journey steps they serve. +- **Real content** = the artifacts' own copy — persona names, journey language, feature titles. Never lorem, never invented. + +**Never re-ask what these artifacts answer** (audience, problem, journey steps, scope). You MAY ask what they cannot answer: structure (design SKILL 1b), references (design SKILL 2a), fidelity — budget 3–5 questions, tags [Structure] [References] [Fidelity]. + +## The job, in order + +1. Read the four product artifacts; derive the brief as above. +2. Ask the [Structure] [References] [Fidelity] questions the artifacts cannot answer — nothing else. +3. Run the design skill's arc: cite references, tear them down, build comparable mockup options. Write references, teardowns, and options under `.bobby/design/` (the design pipeline's home — `bobby run design` can resume from them later). +4. **Your FINAL message is the gate:** present the options built from THEIR artifacts and ask for a pick — **or "skip"**. + - **On a pick:** write `.bobby/product/mockups.md` (Locked/Status header, the chosen direction, pointers to the option files), commit it with `.bobby/product/`, comment on the epic (`--by bobby-define-mockups`), and move on. + - **On "skip":** comment `bobby ticket comment {EPIC} --by bobby-define-mockups "Mockups skipped at the gate."` and move on. No `mockups.md` is written; the blueprint tolerates its absence. Design is a stage, not a toll. + +## Hard rules + +- Every mockup value comes from a teardown. A value you invented is drift. +- Use the product's real content, **identical across options**, so only the design varies. +- Present on neutral ground — never frame an option in a colour you are asking the human to judge. +- Score against `slop_checklist.md` before presenting; unexempted hits are not ready to show. + +## Completing Work + +Move the epic to the stage the calling prompt names — the workflow decides the target, not this file. If run standalone with no named stage, `bobby ticket move {EPIC} blueprint`. + +## Project overrides + +If `.claude/agents/bobby-define-mockups.local.md` exists, read it and follow it. It wins wherever it conflicts with anything above. This file is regenerated on upgrade; that one never is. diff --git a/.claude/commands/bobby-define.md b/.claude/commands/bobby-define.md index 3b23918..162f9ee 100644 --- a/.claude/commands/bobby-define.md +++ b/.claude/commands/bobby-define.md @@ -1,6 +1,6 @@ --- -description: "Define the product — brief → personas → journeys → feature map, with a gate at each stage" +description: "Define the product — brief → personas → journeys → data model → architecture (both optional) → feature map → mockups (optional) → blueprint, with a gate at each stage" argument-hint: "" --- -Load and follow the skill in `.claude/skills/bobby-define/SKILL.md` to take the specified epic through product definition (brief → personas → journeys → feature map → blueprint). Resume from whatever artifacts already exist in `.bobby/product/`. +Load and follow the skill in `.claude/skills/bobby-define/SKILL.md` to take the specified epic through product definition (brief → personas → journeys → data model → architecture (both optional) → feature map → mockups (optional) → blueprint). Resume from whatever artifacts already exist in `.bobby/product/`. diff --git a/.claude/skills/bobby-define/SKILL.md b/.claude/skills/bobby-define/SKILL.md index 1c197c3..d458dfc 100644 --- a/.claude/skills/bobby-define/SKILL.md +++ b/.claude/skills/bobby-define/SKILL.md @@ -1,22 +1,24 @@ --- name: define-product -description: "Product Definition Skill: Takes a committed idea through the artifact chain the pros run — brief → personas → journeys → feature map → blueprint — so decomposition builds the right thing instead of guessing, and you see the whole plan before a line is written. Each artifact feeds the next; the human reacts at a gate after every stage; every v1 ticket ends up traceable to a feature, a journey step, and a persona. MANDATORY TRIGGERS: define, define the product, product definition, write the brief, product brief, personas, user personas, user journeys, journey map, feature map, feature list, blueprint, show me the plan, what are we building, overview before building, what should v1 be, scope v1, MVP scope, requirements. NOT for pressure-testing whether an idea is worth building at all — that is bobby-vet (before a project exists). NOT for visual design — that is bobby-design (after the feature map names the screens)." +description: "Product Definition Skill: Takes a committed idea through the artifact chain the pros run — brief → personas → journeys → data model → architecture → feature map → mockups → blueprint (data model, architecture and mockups optional) — so decomposition builds the right thing instead of guessing, and you see the whole plan before a line is written. Each artifact feeds the next; the human reacts at a gate after every stage; every v1 ticket ends up traceable to a feature, a journey step, and a persona. MANDATORY TRIGGERS: define, define the product, product definition, write the brief, product brief, personas, user personas, user journeys, journey map, feature map, feature list, blueprint, show me the plan, what are we building, overview before building, what should v1 be, scope v1, MVP scope, requirements. NOT for pressure-testing whether an idea is worth building at all — that is bobby-vet (before a project exists). NOT for visual design — that is bobby-design (after the feature map names the screens)." argument-hint: "" --- # Bobby Define Skill > Product definer — takes the MVP epic from a one-line idea to a locked product -> definition: brief, personas, journeys, feature map, and a blueprint you can +> definition: brief, personas, journeys, optionally a data model and a forward +> architecture with its ADRs, the feature map, optionally mockups of the +> v1 screens, and a blueprint you can > read on one page. Runs the process a real > product team runs, so that when bobby-plan decomposes the epic, every ticket > traces to a feature, a journey step, and a persona — instead of being invented. ## Scope -**This skill DEFINES the product.** It produces `.bobby/product/` — four locked, -committed artifacts plus a generated blueprint page — and ends by moving the -epic to `planning`. +**This skill DEFINES the product.** It produces `.bobby/product/` — the locked, +committed artifacts (six when nothing is skipped) plus a generated blueprint +page — and ends by moving the epic to `planning`. - Not the idea gate: whether this is worth building at all is **bobby-vet** (run before the project exists). By the time define runs, the idea is committed. @@ -33,7 +35,7 @@ Read, in parallel: 1. `.claude/skills/bobby-define/learnings.md` + `.claude/skills/bobby-define/learnings.local.md` and `.claude/skills/bobby-shared/learnings.md` + `.claude/skills/bobby-shared/learnings.local.md` 2. The epic's `ticket.md` — its Description carries the idea, verbatim. Quote it; never paraphrase it into something you'd rather build. -3. **Any existing `.bobby/product/*.md` — resume, don't restart.** If brief.md exists and is approved, start at Step 2. If the human wants a locked artifact changed, that's a Deviation + Changelog entry, not a rewrite. +3. **Any existing `.bobby/product/*.md` — resume, don't restart.** If brief.md exists and is approved, start at the Personas step (Step 2). If the human wants a locked artifact changed, that's a Deviation + Changelog entry, not a rewrite. 4. `.bobby/architecture-wakeup.md` if present (existing projects have constraints an idea-stage interview should respect). ## The Three Rules (read before anything else) @@ -166,17 +168,121 @@ _none_ Whatever step they name gets rethought before locking. Comment on the epic (`--by bobby-define-journeys`). -## Step 4: Feature Map — `.bobby/product/feature-map.md` +## Step 4: Data Model — `.bobby/product/DATA-MODEL.md` (optional) + +**Tags:** [Entity] [Relation] [Truth] · **Budget:** 3–5 questions (only where ownership is genuinely unclear). + +Runs after the Journeys step, before the Forward Architecture step — so the +Feature Map step cuts the map against a stated data model instead of every +build ticket rediscovering one. **Entities are derived from what journey steps +store and show, never brainstormed** — an entity no journey step touches +doesn't exist; it goes back to the Journeys step (Step 3) as a proposed step, +or out. Per entity: the journey-cited fields, the relations (entity → entity, +with cardinality), and the **source-of-truth call** — this system, a named +external system, or the user. If every truth call feels low-risk, the model +hasn't been thought about; that column is where the honest calls live. + +```markdown +# Data Model — + +**Locked:** YYYY-MM-DD · **Status:** approved +**Source:** journeys.md + +## +- **Fields:** +- **Relations:** → (<1:1 | 1:n | n:m>) +- **Source of truth:** | the user> + +## Vetted — from the human +- The truth call they corrected: — +## Deviations (each needs a reason) +_none_ +## Changelog +``` + +⛳ **Gate — your final message**, verbatim: +> "Here is every entity and who owns the truth for it. Point at the one **source-of-truth** call that is wrong — or name the entity that is missing." + +Comment on the epic (`--by bobby-define-data-model`). + +**Skipping is first-class, two ways:** `bobby run define --no-data-model` +never enters this stage (the chain recomputes its handoffs), and "skip" at the +gate exits it — comment `bobby ticket comment --by bobby-define-data-model +"Data model skipped at the gate."`, write no artifact, and move on. Every +downstream reader treats `DATA-MODEL.md` as "when present", so skipping costs +nothing. + +## Step 5: Forward Architecture — `.bobby/product/ARCHITECTURE.md` + ADRs (optional) + +**Tags:** [Component] [Boundary] [Decision] · **Budget:** 3–5 questions. + +The **forward view**: the components and boundaries that WILL exist, where +each entity lives, the integration seams. Explicitly distinct from `bobby run +arch`'s `.bobby/architecture.md`, the **backward view** of what DOES exist — +the file's header states that distinction; when the two disagree after +building, re-run arch and amend this file with a Changelog line. Inputs: +`DATA-MODEL.md` **when present** (its stage can be skipped), `journeys.md`, +and `.bobby/architecture-wakeup.md` if the repo already exists — a conflict +with the backward view is an ADR, never a silent contradiction. + +Each load-bearing call becomes an ADR through the existing command — **never +by hand-editing `.bobby/decisions.yaml`**: + +``` +bobby decision add --id --fact "" --why "" --ticket +``` + +`bobby decision add` deliberately doesn't commit; the entries land with the +Feature Map step's lock commit. bobby-review already runs `bobby decision +list` — the ADRs bind every ticket from day one with no review-side change. + +```markdown +# Forward Architecture — + +**Locked:** YYYY-MM-DD · **Status:** approved +**Source:** DATA-MODEL.md (when present) · journeys.md + +> This file is INTENT — what will exist. `bobby run arch` writes +> `.bobby/architecture.md`, the discovery of what does exist. When they +> disagree after building, re-run arch and amend this file (Changelog line). + +## Components +- **** — ; owns: +## Integration seams +- + +## Decisions (ids in `.bobby/decisions.yaml`) +- `` — cite the id only; the decision itself lives in the log. + +## Vetted — from the human +- Vetoed at the gate: +## Deviations (each needs a reason) +_none_ +## Changelog +``` + +⛳ **Gate — your final message**, verbatim: +> "These are the decisions bobby-review will hold every ticket to from day one. Veto one now — or say they stand." + +Comment on the epic (`--by bobby-define-architecture`). + +**Skipping is first-class, two ways:** `bobby run define --no-architecture` +never enters this stage, and "skip" at the gate exits it — comment +`bobby ticket comment --by bobby-define-architecture "Architecture +skipped at the gate."`, write no artifact, and move on. `ARCHITECTURE.md` is +"when present" everywhere downstream. + +## Step 6: Feature Map — `.bobby/product/feature-map.md` **Tags:** [Must] [Later] [Never] · **Budget:** 3–5 questions (this stage is mostly derivation, not interview). -Walk every journey step and derive the capabilities it needs. **Features are derived from journeys, never brainstormed** — a feature that serves no step doesn't go in the map, it goes back to Step 3 as a proposed new step or out entirely. IDs: `F.` keyed to journey `J`; `F0.x` is reserved for Never/cross-cutting rows. Every Never row cites a brief Non-goal. +Walk every journey step and derive the capabilities it needs. **Features are derived from journeys, never brainstormed** — a feature that serves no step doesn't go in the map, it goes back to the Journeys step (Step 3) as a proposed new step or out entirely. **Cut the map against `DATA-MODEL.md` when present** — a Must feature that needs an entity absent from the model amends the model (with a Changelog line), or goes out; it never invents an entity locally. IDs: `F.` keyed to journey `J`; `F0.x` is reserved for Never/cross-cutting rows. Every Never row cites a brief Non-goal. ```markdown # Feature Map — **Locked:** YYYY-MM-DD · **Status:** approved -**Source:** journeys.md · brief.md (Non-goals bind the Never column) +**Source:** journeys.md · DATA-MODEL.md (when present) · brief.md (Non-goals bind the Never column) | ID | Feature | Serves journey step(s) | Persona | MoSCoW | Notes | |---|---|---|---|---|---| @@ -205,15 +311,60 @@ _none_ > "The **Must** column is the whole of v1. Strike one thing from it — or tell me why nothing can go." **After the gate — lock and hand off:** -1. Set `**Locked:** · **Status:** approved` on all four artifacts. -2. Commit: `git add .bobby/product && git commit -m "product: definition locked for "` +1. Set `**Locked:** · **Status:** approved` on every artifact present in `.bobby/product/` (six when nothing was skipped). +2. Commit: `git add .bobby/product .bobby/decisions.yaml && git commit -m "product: definition locked for "` — the ADRs land with the definition (`bobby decision add` deliberately doesn't commit). 3. Move the epic: `bobby ticket move plan` 4. Comment: `bobby ticket comment --by bobby-define-features "Definition locked: Must features across journeys for ."` 5. Print the handoff: *"Definition locked. Next: `bobby go` — bobby-plan will decompose the epic against the feature map, one ticket per Must row, each carrying its feature ref."* --- -## Step 5: The Blueprint — the glimpse before building +## Step 7: Mockups — the v1 screens, from YOUR artifacts (optional) + +**Tags:** [Structure] [References] [Fidelity] · **Budget:** 3–5 questions. + +Runs after the feature map locks, before the Blueprint step. The bobby-design +craft, with the interview already done: the locked artifacts ARE the design +brief, so the founder reacts to screens built from their own product instead +of answering the same questions twice. + +**The brief, derived — never asked:** + +- **Audience** = the PRIMARY persona (personas.md), verbatim. +- **The page's job** = the headline journey's Success line (journeys.md). +- **The screens to mock** = the Must features and the journey steps they serve + (feature-map.md). +- **Real content** = the artifacts' own copy — persona names, journey + language, feature titles. Never lorem, never invented. + +Never re-ask what the artifacts answer. The only questions allowed are the +ones they cannot: structure (what shape is this thing?), references (whose +look do you like?), fidelity (how closely to follow them?) — the design +skill's own gates, within the budget above. + +Then run the design skill's arc (`.claude/skills/bobby-design/SKILL.md` +steps 1b–3 and its `references/slop_checklist.md`) — cite references, tear +them down, build comparable mockup options with the product's real content +identical across options. Everything lands under `.bobby/design/` (the design pipeline's home, +so `bobby run design` can later resume from these files). + +⛳ **Gate — your final message:** present the options built from THEIR +artifacts and ask, verbatim: +> "Which of these is the product you meant — or say **skip** and we go +> straight to the blueprint." + +- **On a pick:** write `.bobby/product/mockups.md` (Locked/Status header, the + chosen direction, pointers to the option files), commit it with + `.bobby/product/`, comment on the epic (`--by bobby-define-mockups`). +- **On "skip":** comment `bobby ticket comment --by bobby-define-mockups + "Mockups skipped at the gate."` and move on. No `mockups.md` is written; the + blueprint tolerates its absence. + +**Skipping is first-class, two ways:** `bobby run define --no-mockups` +never enters this stage at all (the chain hands feature map straight to +blueprint), and "skip" at the gate exits it. Design is a stage, not a toll. + +## Step 8: The Blueprint — the glimpse before building **No interview.** This stage generates, explains, and gates. @@ -229,7 +380,9 @@ map. If it reports drift, fix the cause and re-run — never explain it away. Walk the human through it in this order: **the crux** (what the product resolves to), **the tracks** (what unlocks what), **what's out** (Later and -Never). Then commit the page alongside the artifacts. +Never) — and, if `.bobby/product/mockups.md` exists, **the design direction** +the page now carries. If it does not (the Mockups step was skipped), say so +and move on. Then commit the page alongside the artifacts. ⛳ **Gate — your final message**, verbatim: > "Does this look like the thing you want built — and what's missing?" @@ -241,7 +394,7 @@ the next regeneration would erase it. Then hand off: `bobby ticket move plan`. -## Locking rules (apply to all four artifacts) +## Locking rules (apply to every artifact in `.bobby/product/`) 1. **Decomposition reads the artifacts first** — never memory, never taste. 2. **Values and IDs are copied from the files, never retyped.** @@ -250,11 +403,11 @@ Then hand off: `bobby ticket move plan`. ## Ticket Integration -Each stage comments on the epic when it reaches its gate (commands shown per step). The epic's stage tracks progress through the pipeline (`define-brief` → … → `define-features` → `planning`), so `bobby brief` and the app always know where definition stands. +Each stage comments on the epic when it reaches its gate (commands shown per step). The epic's stage tracks progress through the pipeline (`define-brief` → `define-personas` → `define-journeys` → `define-data-model` → `define-architecture` → `define-features` → `define-mockups` → `define-blueprint` → `planning`), so `bobby brief` and the app always know where definition stands. ## Completing Work -You are done only when: all four artifacts exist with `Status: approved`, `.bobby/product/` is committed, the epic sits in `planning`, and the handoff message has been printed. If the human parked mid-pipeline, say exactly which stage is next and that `bobby run define ` resumes there. +You are done only when: every artifact whose stage ran exists with `Status: approved` (brief, personas, journeys and feature map always; DATA-MODEL.md and ARCHITECTURE.md unless their stages were skipped), `.bobby/product/` is committed, the epic sits in `planning`, and the handoff message has been printed. If the human parked mid-pipeline, say exactly which stage is next and that `bobby run define ` resumes there. ## Project overrides diff --git a/.claude/skills/bobby-define/learnings.md b/.claude/skills/bobby-define/learnings.md index efc0a17..401f226 100644 --- a/.claude/skills/bobby-define/learnings.md +++ b/.claude/skills/bobby-define/learnings.md @@ -18,7 +18,7 @@ learnings go in `learnings.local.md` (never overwritten on upgrade). - Quote the idea verbatim from the epic — paraphrase drifts toward what you'd rather build. -- The Non-goals written in Step 1 do their real work in Step 4: every Never row +- The Non-goals written in Step 1 do their real work in Step 6: every Never row cites one. - The drop-off column in journeys is where honest product thinking lives — if every step says "low risk", the journey hasn't been thought about. diff --git a/.claude/skills/bobby-plan/SKILL.md b/.claude/skills/bobby-plan/SKILL.md index b447e5d..c8662f0 100644 --- a/.claude/skills/bobby-plan/SKILL.md +++ b/.claude/skills/bobby-plan/SKILL.md @@ -49,6 +49,11 @@ HOW. outranks your taste. 2. `.bobby/product/journeys.md` — the steps each feature serves. 3. `.bobby/product/personas.md` and `.bobby/product/brief.md` — who and why. +4. `.bobby/product/DATA-MODEL.md` and `.bobby/product/ARCHITECTURE.md` — + **when present** (their stages are optional): a ticket that creates or + mutates an entity respects its source-of-truth call and copies entity + names from the model, never invents them; and run `bobby decision list` — + the active decisions constrain every technical approach you score. ### Step 1: Write the epic spec (`plan.md`) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf8c508..16b7fe8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,51 @@ All notable changes to Bobby are documented here. The format is based on ### Added +- **The define pipeline gains data-model and forward-architecture stages.** + `bobby run define` now runs brief → personas → journeys → **data model** → + **architecture** → feature map → mockups → blueprint: after the journeys + lock and before the feature map is cut, `bobby-define-data-model` derives + the entities from what journey steps store and show — never a brainstorm — + and makes a **source-of-truth call** per entity (this system, a named + external system, or the user), writing `.bobby/product/DATA-MODEL.md`; then + `bobby-define-architecture` records the **forward view** in + `.bobby/product/ARCHITECTURE.md` — the components that WILL exist, kept + deliberately distinct from `bobby run arch`'s `.bobby/architecture.md`, + which discovers what DOES exist — and turns every load-bearing call into an + ADR through the existing `bobby decision add`, so `bobby-review` holds every + ticket to those decisions from day one with no new machinery and no + review-side change (ARCHITECTURE.md cites the decision ids; the entries + land with the feature-map lock commit, which now stages + `.bobby/decisions.yaml` too). The feature map is cut against the data + model, not the other way around — a Must feature needing an entity the + model lacks amends the model, never invents one locally — and bobby-plan's + Product-Aware decomposition reads both files when present. Both stages are + optional the same two ways mockups is: `--no-data-model` / + `--no-architecture` filter the chain via `omitStage` (handoffs recompute, + so with both flags journeys hands straight to features), and "skip" at + either gate moves on with a comment and no artifact — every downstream + reader treats the artifacts as "when present", so skipping costs nothing. + `bobby ticket move data-model` and `… architecture` reach the stages + (full words on purpose: `arch` stays `bobby run arch`'s name). + +- **The define pipeline gains an optional mockups stage.** `bobby run define` + now runs brief → personas → journeys → feature map → **mockups** → + blueprint: after the feature map locks, the new `bobby-define-mockups` + agent designs the v1 screens *from* the locked artifacts — the PRIMARY + persona is the audience, the headline journey's Success line is the page's + job, the Must rows are the screens — and presents options built with the + product's own copy. It never re-asks what the artifacts already answer; + the only questions allowed are the ones they can't (structure, references, + fidelity). Design is a stage, not a toll, so skipping is first-class two + ways: `--no-mockups` filters the stage out of the resolved chain (the new + `omitStage` helper — handoffs recompute automatically, so the feature map + hands straight to the blueprint), and answering "skip" at the gate exits + with a comment and no artifact. On a pick, `.bobby/product/mockups.md` + records the chosen direction and the blueprint page shows one + design-direction line; on a skip nothing is written and nothing downstream + changes. `bobby ticket move mockups` reaches the stage (`mockup`, + singular, still means the design pipeline's stage). + - **Codex CLI target + executor** (`target: codex`): scaffolds `AGENTS.md`, `.codex/skills/`, and prompt-referenced agents; the dashboard drives `codex exec --json` headlessly. Every convention verified against the diff --git a/commands/init.js b/commands/init.js index d93636c..dea6de5 100644 --- a/commands/init.js +++ b/commands/init.js @@ -163,7 +163,7 @@ export function scaffoldProject(rootDir, config, { writeConfig = true } = {}) { const agentsDir = path.join(rootDir, targetPaths.agents); fs.mkdirSync(agentsDir, { recursive: true }); - const agentFiles = ['bobby-plan', 'bobby-build', 'bobby-review', 'bobby-test', 'bobby-ship', 'bobby-ux', 'bobby-define-brief', 'bobby-define-personas', 'bobby-define-journeys', 'bobby-define-features', 'bobby-define-blueprint', 'bobby-design-research', 'bobby-design-analyze', 'bobby-design-mockup', 'bobby-design-spec', 'bobby-design-build', 'bobby-design-check', 'bobby-pm', 'bobby-qe', 'bobby-vet', 'bobby-strategy', 'bobby-security', 'bobby-debug', 'bobby-docs', 'bobby-performance', 'bobby-lighthouse', 'bobby-watchdog', 'bobby-arch', 'bobby-ticket-intake', 'bobby-freewill']; + const agentFiles = ['bobby-plan', 'bobby-build', 'bobby-review', 'bobby-test', 'bobby-ship', 'bobby-ux', 'bobby-define-brief', 'bobby-define-personas', 'bobby-define-journeys', 'bobby-define-data-model', 'bobby-define-architecture', 'bobby-define-features', 'bobby-define-blueprint', 'bobby-define-mockups', 'bobby-design-research', 'bobby-design-analyze', 'bobby-design-mockup', 'bobby-design-spec', 'bobby-design-build', 'bobby-design-check', 'bobby-pm', 'bobby-qe', 'bobby-vet', 'bobby-strategy', 'bobby-security', 'bobby-debug', 'bobby-docs', 'bobby-performance', 'bobby-lighthouse', 'bobby-watchdog', 'bobby-arch', 'bobby-ticket-intake', 'bobby-freewill']; for (const agent of agentFiles) { const agentTemplate = path.join(AGENT_TEMPLATES_DIR, `${agent}.md.ejs`); if (fs.existsSync(agentTemplate)) { diff --git a/commands/new.js b/commands/new.js index 53d2d87..58321e9 100644 --- a/commands/new.js +++ b/commands/new.js @@ -39,7 +39,7 @@ export function registerNew(program) { if (starter) { console.log(` ${bold(starter.dev)} ${dim(`# run it now → ${starter.url}`)}`); } - console.log(` ${bold(`bobby run define ${epic.id}`)} ${dim('# define the product: brief → personas → journeys → feature map')}`); + console.log(` ${bold(`bobby run define ${epic.id}`)} ${dim('# define the product: brief → personas → journeys → data model → architecture → feature map → mockups → blueprint')}`); console.log(` ${bold(`bobby run plan ${epic.id}`)} ${dim('# …or skip straight to breaking it into MVP tickets')}`); console.log(''); console.log(` ${dim('More ideas: bobby idea "..." · see the board: bobby ticket list')}`); diff --git a/commands/run.js b/commands/run.js index a8e82f9..d08e1c8 100644 --- a/commands/run.js +++ b/commands/run.js @@ -4,7 +4,7 @@ import path from 'path'; import inquirer from 'inquirer'; import { readConfig, findProjectRoot, resolveTicketsDir, resolveSessionsDir, resolveProductDir } from '../lib/config.js'; import { getFeatureTickets, listEpics } from '../lib/tickets.js'; -import { DEFAULT_WORKFLOW, buildPromptFor, resolveWorkflow, listWorkflows, assertTicketFree } from '../lib/workflow.js'; +import { DEFAULT_WORKFLOW, buildPromptFor, resolveWorkflow, listWorkflows, assertTicketFree, omitStage } from '../lib/workflow.js'; import { findTicket } from '../lib/tickets.js'; import { VALID_AGENTS } from '../lib/agent-registry.js'; import { bold, dim, error } from '../lib/colors.js'; @@ -34,8 +34,8 @@ Modes: Batch: bobby run plan — runs agent on all tickets in matching stage Direct: bobby run plan|build|review|test|ship|ux|pm|qe Vet: bobby run vet [id] — interrogate design before planning - Define: bobby run define — product definition: brief → personas → journeys → features → blueprint (then: run plan) - bobby run define-brief|define-personas|define-journeys|define-features|define-blueprint + Define: bobby run define — product definition: brief → personas → journeys → data-model → architecture → features → mockups → blueprint (then: run plan; skip optional stages with --no-data-model, --no-architecture, --no-mockups) + bobby run define-brief|define-personas|define-journeys|define-data-model|define-architecture|define-features|define-mockups|define-blueprint Design: bobby run design — full chain: research → analyze → mockup → spec → build → check bobby run design-research|design-analyze|design-mockup|design-spec|design-build|design-check Strategy: bobby run strategy [id] — strategic validation gate @@ -47,6 +47,9 @@ Modes: .option('--max-retries ', 'Max retry loops on rejection per ticket', '3') .option('--max-iterations ', 'Max total agent invocations across all tickets') .option('--workflow ', 'Named workflow to use (built-in or from .bobbyrc.yml workflows)', 'default') + .option('--no-mockups', 'Skip the design mockups stage of the define workflow') + .option('--no-data-model', 'Skip the data-model stage of the define workflow') + .option('--no-architecture', 'Skip the forward-architecture stage of the define workflow') .action(async (agent, ticketIds, opts) => { try { const root = findProjectRoot(); @@ -115,7 +118,23 @@ Modes: } // Resolve workflow: explicit flag > ticket frontmatter > default - const pipeline = resolveWorkflow(config, opts.workflow || 'default', ticketPipeline); + let pipeline = resolveWorkflow(config, opts.workflow || 'default', ticketPipeline); + // --no-mockups: commander negation — opts.mockups is true by default, + // false when the flag is passed. Filtering the resolved chain is the + // whole mechanism: handoffs are computed from the array, so + // define-features now hands straight to define-blueprint. A no-op for + // every workflow without the stage. (An epic already PARKED in + // define-mockups is outside the filtered chain — the orchestrator's + // catch-all logs a warning; the gate's "skip" answer is the path for + // parked epics.) + if (opts.mockups === false) pipeline = omitStage(pipeline, 'define-mockups'); + // Same mechanism for the other two optional define stages. Each flag + // filters independently, so a user can keep the data model but skip + // the ADRs; with both passed, define-journeys hands straight to + // define-features. (Commander camelCases: --no-data-model → + // opts.dataModel === false.) + if (opts.dataModel === false) pipeline = omitStage(pipeline, 'define-data-model'); + if (opts.architecture === false) pipeline = omitStage(pipeline, 'define-architecture'); // Feature mode: if no epic id provided, let user pick interactively. // This is the only interactive step — the dashboard will always pass an explicit epicId. diff --git a/lib/agent-registry.js b/lib/agent-registry.js index eec02b6..b111f3a 100644 --- a/lib/agent-registry.js +++ b/lib/agent-registry.js @@ -197,6 +197,30 @@ export const AGENT_REGISTRY = { 'FINAL message: walk J1 with the founder as the persona — at which step would they give up?', ], }, + 'define-data-model': { + label: 'Define Data Model', + agentName: 'bobby-define-data-model', + cowork: true, + promptHeader: 'Run the bobby-define-data-model agent to state the entities, their relations, and who owns the truth for each — after journeys, before the feature map.', + promptSteps: [ + 'Read `.bobby/product/journeys.md` FIRST (plus `personas.md` and `brief.md`) — entities are derived from what journey steps store and show, never brainstormed. An entity no journey step touches does not exist.', + 'Write `.bobby/product/DATA-MODEL.md`: one section per entity — journey-cited fields, relations with cardinality, and a source-of-truth call per entity (this system / a named external system / the user).', + 'Interview only where ownership is genuinely unclear (tags: [Entity] [Relation] [Truth]; budget 3-5 questions).', + 'Your FINAL message is the gate: "Here is every entity and who owns the truth for it. Point at the one source-of-truth call that is wrong — or name the entity that is missing." The gate accepts "skip": comment the skip on the epic and move on without writing the artifact.', + ], + }, + 'define-architecture': { + label: 'Define Architecture', + agentName: 'bobby-define-architecture', + cowork: true, + promptHeader: 'Run the bobby-define-architecture agent to record the forward architecture — the components that WILL exist — and its load-bearing decisions as ADRs.', + promptSteps: [ + 'Read `.bobby/product/DATA-MODEL.md` when present (its stage can be skipped) plus `journeys.md`; read `.bobby/architecture-wakeup.md` if the repo already exists — a conflict with the backward view is an ADR, never a silent contradiction.', + 'Write `.bobby/product/ARCHITECTURE.md` as the FORWARD view — components, boundaries, where each entity lives, integration seams. This is intent (what WILL exist), explicitly distinct from `bobby run arch`\'s backward view (what DOES exist).', + 'Record each load-bearing call with `bobby decision add --id --fact "..." --why "..." --ticket {EPIC}` — never hand-edit `.bobby/decisions.yaml`. ARCHITECTURE.md cites the decision ids; it never restates them.', + 'Your FINAL message is the gate: "These are the decisions bobby-review will hold every ticket to from day one. Veto one now — or say they stand." The gate accepts "skip": comment the skip on the epic and move on.', + ], + }, 'define-features': { label: 'Define Features', agentName: 'bobby-define-features', @@ -204,9 +228,22 @@ export const AGENT_REGISTRY = { promptHeader: 'Run the bobby-define-features agent to derive the feature map and lock the definition.', promptSteps: [ 'Read `.bobby/product/journeys.md` — every feature cites the journey step(s) it serves; features are derived, not brainstormed.', + 'Read `.bobby/product/DATA-MODEL.md` when present and cut the map against it — a Must feature needing an entity absent from the model amends the model (with a Changelog line), never invents one locally.', 'Build the map: F. rows with journey step, persona, MoSCoW column. v1 = the Must rows. Never rows must cite a brief Non-goal.', 'Gate: the Must column is all of v1 — ask the founder to strike one thing, or say why nothing can go.', - 'Then LOCK all four artifacts (Locked/Status headers), commit `.bobby/product/`, and move the epic: `bobby ticket move {EPIC} plan`.', + 'Then LOCK every artifact present in `.bobby/product/` (Locked/Status headers; six when nothing was skipped), commit `git add .bobby/product .bobby/decisions.yaml`, and move the epic: `bobby ticket move {EPIC} plan`.', + ], + }, + 'define-mockups': { + label: 'Define Mockups', + agentName: 'bobby-define-mockups', + cowork: true, + promptHeader: 'Run the bobby-define-mockups agent to design the v1 screens from the locked product artifacts, and present options at a gate that accepts "skip".', + promptSteps: [ + 'Read `.bobby/product/brief.md`, `personas.md`, `journeys.md`, and `feature-map.md` FIRST — they ARE the design brief. The PRIMARY persona is the audience, the headline journey\'s Success line is the page\'s job, the Must features and the journey steps they serve are the screens to mock.', + 'Never re-ask what those artifacts already answer (audience, problem, journey steps). You may ask only what they cannot: structure, references, fidelity — budget 3-5 questions, tags [Structure] [References] [Fidelity].', + 'Follow the design skill\'s research → teardown → mockup-options arc with the product\'s REAL content (persona names, journey language) identical across options; write references/teardowns/options under `.bobby/design/`.', + 'Your FINAL message is the gate: present the options built from THEIR artifacts and ask for a pick — or "skip". On a pick, write `.bobby/product/mockups.md` and commit; on "skip", comment the skip on the epic and move on. Either way the pipeline continues.', ], }, 'define-blueprint': { diff --git a/lib/blueprint-html.js b/lib/blueprint-html.js index e2ace20..f3c1f3b 100644 --- a/lib/blueprint-html.js +++ b/lib/blueprint-html.js @@ -175,6 +175,7 @@ footer{margin-top:56px;padding-top:20px;border-top:1px solid var(--line);font-fa
Success — the one thing that must happen

${esc(bp.brief.metric)}

` : ''} + ${bp.mockups ? `

Design direction: ${esc(bp.mockups.direction || 'chosen — see .bobby/product/mockups.md')}

` : ''} ${bp.personas.length ? ` diff --git a/lib/blueprint.js b/lib/blueprint.js index 8eb8210..367506f 100644 --- a/lib/blueprint.js +++ b/lib/blueprint.js @@ -128,6 +128,20 @@ function parseJourneys(md) { return out; } +/** + * The OPTIONAL mockups artifact — written only when the human picked a + * direction at the mockups gate. Skipping that stage writes nothing, and this + * returns null: absence must cost the blueprint nothing. + */ +function parseMockups(md) { + if (!md) return null; + return { + direction: plain(field(md, 'Chosen direction') || field(md, 'Direction')), + locked: field(md, 'Locked'), + status: field(md, 'Status'), + }; +} + function parseFeatures(md) { if (!md) return []; return parseTable(md).map(r => ({ @@ -177,6 +191,7 @@ export function buildBlueprint(productDir, ticketsDir, epicId = null) { const personasMd = read(productDir, 'personas.md'); const journeysMd = read(productDir, 'journeys.md'); const featuresMd = read(productDir, 'feature-map.md'); + const mockupsMd = read(productDir, 'mockups.md'); if (!featuresMd) { throw new Error(`No feature map in ${productDir} — run \`bobby run define \` first.`); @@ -186,6 +201,7 @@ export function buildBlueprint(productDir, ticketsDir, epicId = null) { const personas = parsePersonas(personasMd); const journeys = parseJourneys(journeysMd); const features = parseFeatures(featuresMd); + const mockups = parseMockups(mockupsMd); const crux = findCrux(journeysMd, features); const epic = epicId ? findTicket(ticketsDir, epicId) : null; @@ -216,6 +232,7 @@ export function buildBlueprint(productDir, ticketsDir, epicId = null) { personas, journeys, features, + mockups, crux, tracks, later: features.filter(f => f.moscow === 'later' || f.moscow === 'should'), diff --git a/lib/brief.js b/lib/brief.js index 1d93618..eaf5ab0 100644 --- a/lib/brief.js +++ b/lib/brief.js @@ -10,7 +10,7 @@ import { findTicket } from './tickets.js'; // Pipeline stages, named so the maps below can't silently miss one. const DESIGN_STAGES = ['design-research', 'design-analyze', 'design-mockup', 'design-spec']; -const DEFINE_STAGES = ['define-brief', 'define-personas', 'define-journeys', 'define-features', 'define-blueprint']; +const DEFINE_STAGES = ['define-brief', 'define-personas', 'define-journeys', 'define-data-model', 'define-architecture', 'define-features', 'define-mockups', 'define-blueprint']; // Stages that count as "in flight" — started but not shipped. define-* stages // live on epics, which are routed by the epic logic, not the work-item list. diff --git a/lib/router.js b/lib/router.js index d0faaed..d99ff67 100644 --- a/lib/router.js +++ b/lib/router.js @@ -15,7 +15,7 @@ export const CAPABILITIES = [ { when: 'is this production-ready? / is it safe to launch / what\'s missing before customers / harden this / make it enterprise-grade', do: 'bobby audit', note: 'scores the codebase on security, reliability, operability, change safety; add --tickets to turn every gap into work' }, { when: 'remember this / note / idea for later / capture a thought', do: 'bobby idea ""', note: 'captures without touching the board' }, { when: 'where am I? / status / what\'s in flight / what\'s blocked / what\'s next', do: 'bobby brief', note: 'the where-was-I summary' }, - { when: 'define the product / write the brief / personas / user journeys / feature map / what should v1 be / scope the MVP', do: 'bobby run define ', note: 'brief → personas → journeys → feature map → blueprint, a human gate at each stage; then bobby-plan decomposes traceably' }, + { when: 'define the product / write the brief / personas / user journeys / feature map / what should v1 be / scope the MVP', do: 'bobby run define ', note: 'brief → personas → journeys → data model → architecture → feature map → mockups → blueprint, a human gate at each stage; then bobby-plan decomposes traceably' }, { when: 'design a page / landing page / make it look good / visual identity / it looks generic or AI-made', do: 'bobby run design ', note: 'the design pipeline: references → teardowns → mockups → locked spec → build → check' }, { when: 'show me the plan / what are we building / overview / blueprint / see it before we build', do: 'bobby blueprint', note: 'one page from the locked definition + the board: crux, tracks, every traceable ticket, what is out of scope' }, { when: 'plan / break down / refine a ticket or epic', do: 'bobby run plan ', note: 'loads bobby-plan; needs a ticket' }, diff --git a/lib/stages.js b/lib/stages.js index d49db89..a66e836 100644 --- a/lib/stages.js +++ b/lib/stages.js @@ -4,12 +4,16 @@ import chalk from 'chalk'; export const STAGES = [ 'backlog', // Product definition pipeline — used by the `define` workflow on the MVP - // epic. Brief → personas → journeys → feature map, then the epic moves to - // planning for traceable decomposition. Ordinary tickets skip these. + // epic. Brief → personas → journeys → data model → architecture → feature map + // → mockups → blueprint, then the epic moves to planning for traceable + // decomposition. Ordinary tickets skip these. 'define-brief', 'define-personas', 'define-journeys', + 'define-data-model', + 'define-architecture', 'define-features', + 'define-mockups', 'define-blueprint', 'planning', // Design pipeline — used by the `design` workflow. Ordinary tickets skip @@ -47,7 +51,14 @@ export const TRANSITIONS = { brief: 'define-brief', personas: 'define-personas', journeys: 'define-journeys', + // Full words on purpose: `arch` is `bobby run arch`'s name, and `model` + // invites the mockup/mockups class of near-neighbour trap. No shorthands. + 'data-model': 'define-data-model', + architecture: 'define-architecture', features: 'define-features', + // Plural on purpose: `mockup` (singular, above) is the design pipeline's + // stage. One letter apart, two pipelines — both must keep resolving. + mockups: 'define-mockups', blueprint: 'define-blueprint', // reject and block/unblock are handled specially in move.js }; @@ -70,7 +81,10 @@ export function stageColor(stage) { 'define-brief': chalk.magentaBright, 'define-personas': chalk.magentaBright, 'define-journeys': chalk.magentaBright, + 'define-data-model': chalk.magentaBright, + 'define-architecture': chalk.magentaBright, 'define-features': chalk.magentaBright, + 'define-mockups': chalk.magentaBright, 'define-blueprint': chalk.magentaBright, 'planning': chalk.cyan, 'design-research': chalk.magenta, diff --git a/lib/tickets.js b/lib/tickets.js index df5b492..d5186f8 100644 --- a/lib/tickets.js +++ b/lib/tickets.js @@ -17,8 +17,10 @@ export const STAGE_ORDER = { done: 0, shipping: 1, testing: 2, reviewing: 3, security: 4, building: 5, 'design-spec': 6, 'design-mockup': 7, 'design-analyze': 8, 'design-research': 9, planning: 10, - 'define-blueprint': 11, 'define-features': 12, 'define-journeys': 13, 'define-personas': 14, 'define-brief': 15, - backlog: 16, blocked: 17, + 'define-blueprint': 11, 'define-mockups': 12, 'define-features': 13, + 'define-architecture': 14, 'define-data-model': 15, + 'define-journeys': 16, 'define-personas': 17, 'define-brief': 18, + backlog: 19, blocked: 20, }; /** @@ -405,9 +407,11 @@ export function getFeatureTickets(ticketsDir, epicId) { const children = listTickets(ticketsDir, { epic: epicId }); - // Sort: stage progress (further along first), then priority, then ID + // Sort: stage progress (further along first), then priority, then ID. + // An unknown stage sorts with backlog (derived, not pinned — a literal here + // silently drifted when a new stage renumbered the ranks). children.sort((a, b) => { - const stageDiff = (STAGE_ORDER[a.stage] ?? 16) - (STAGE_ORDER[b.stage] ?? 16); + const stageDiff = (STAGE_ORDER[a.stage] ?? STAGE_ORDER.backlog) - (STAGE_ORDER[b.stage] ?? STAGE_ORDER.backlog); if (stageDiff !== 0) return stageDiff; const priDiff = (PRIORITY_ORDER[a.priority] ?? 3) - (PRIORITY_ORDER[b.priority] ?? 3); if (priDiff !== 0) return priDiff; diff --git a/lib/workflow.js b/lib/workflow.js index b79b04f..7bdd77a 100644 --- a/lib/workflow.js +++ b/lib/workflow.js @@ -24,7 +24,14 @@ export const BUILT_IN_WORKFLOWS = { // Product definition: the artifact chain pros run between "committed to the // idea" and "a developer can pick up a ticket". Runs on the MVP epic; // terminates at planning (bobby-plan decomposes against the feature map). - define: ['define-brief', 'define-personas', 'define-journeys', 'define-features', 'define-blueprint'], + // The data-model and architecture steps (after journeys, before features — + // the feature map is cut against the data model, never the reverse) state + // the entities and the forward architectural intent; the mockups step + // (after the feature map locks, before blueprint) designs the v1 screens + // FROM the personas/journeys. All three are optional — `--no-data-model`, + // `--no-architecture` and `--no-mockups` filter them via omitStage, and + // each gate accepts "skip". + define: ['define-brief', 'define-personas', 'define-journeys', 'define-data-model', 'define-architecture', 'define-features', 'define-mockups', 'define-blueprint'], // One agent, whole ticket, few instructions. Built for Opus 5 / Fable 5, which // do better from a goal and its constraints than from a four-stage script. The // trade-off is real and deliberate: no fresh-eyes reviewer, so the skill sends @@ -55,10 +62,22 @@ export const STAGE_MAP = { 'design-mockup': 'design-mockup', 'design-spec': 'design-spec', 'design-build': 'building', 'design-check': 'reviewing', 'define-brief': 'define-brief', 'define-personas': 'define-personas', - 'define-journeys': 'define-journeys', 'define-features': 'define-features', - 'define-blueprint': 'define-blueprint', + 'define-journeys': 'define-journeys', + 'define-data-model': 'define-data-model', 'define-architecture': 'define-architecture', + 'define-features': 'define-features', + 'define-mockups': 'define-mockups', 'define-blueprint': 'define-blueprint', }; +/** + * A workflow without the named stage. Handoffs are computed from the returned + * array (nextStageName / nextStageForAgent), so filtering a stage out is ALL + * a skip flag has to do — the neighbouring steps hand to each other with no + * further change. A no-op for any workflow that never had the stage. + */ +export function omitStage(workflow, stageName) { + return workflow.filter(s => s.stage !== stageName); +} + /** * Parse a workflow definition (array of step names) into stage/agent objects. */ diff --git a/templates/CLAUDE.md.ejs b/templates/CLAUDE.md.ejs index a83ef90..a0ab4ec 100644 --- a/templates/CLAUDE.md.ejs +++ b/templates/CLAUDE.md.ejs @@ -142,7 +142,7 @@ it to the right capability below and run it; don't make them name the command. | add / fix / change / build a specific thing | **Create a ticket and build it:** `bobby go ""`. Pick the workflow — add `--workflow secure` for anything touching auth/payments/secrets/user-data, `--workflow quick` for a tiny low-risk change, else default. | | just keep going / what's next | `bobby go` | | start a new project / new app from an idea | `bobby new ""` | -| define the product / brief / personas / journeys / feature map / scope v1 | `bobby run define ` | +| define the product / brief / personas / journeys / data model / architecture / feature map / mockups / scope v1 | `bobby run define ` | | show me the plan / what are we building / overview before building | `bobby blueprint` | | design a page / make it look good / it looks generic | load `<%= paths.skills %>/bobby-design/SKILL.md` | | is this a good idea / should I build this / pressure-test it | `bobby vet ""` | @@ -170,10 +170,10 @@ routing if you ever want it explicitly. ## Agents -### Product Definer (bobby-define-brief → -personas → -journeys → -features) -When told to "define the product", "write the brief", "personas", "journeys", or -"feature map", load `<%= paths.skills %>/bobby-define/SKILL.md`. -- Brief → personas → journeys → feature map → blueprint, one ⛳ human gate per stage +### Product Definer (bobby-define-brief → -personas → -journeys → -data-model → -architecture → -features) +When told to "define the product", "write the brief", "personas", "journeys", +"data model", "architecture", or "feature map", load `<%= paths.skills %>/bobby-define/SKILL.md`. +- Brief → personas → journeys → data model → architecture (both optional — skip with `--no-data-model` / `--no-architecture` or "skip" at their gates; ADRs land via `bobby decision add`) → feature map → mockups (optional — skip with `--no-mockups` or "skip" at its gate) → blueprint, one ⛳ human gate per stage - Artifacts locked in `.bobby/product/`; the epic then moves to planning - Run the chain with `bobby run define ` (resumes mid-pipeline) @@ -283,7 +283,7 @@ Each skill has a `learnings.md` file that accumulates anti-patterns. Always chec ``` bobby ticket create -t "Title" --type bug -p high # Create ticket bobby ticket create -t "Epic" --epic # Create epic (breaks down) -bobby run define TKT-001 # Product definition: brief → personas → journeys → features → blueprint +bobby run define TKT-001 # Product definition: brief → personas → journeys → data model → architecture → features → mockups → blueprint (data model, architecture, mockups optional) bobby blueprint # See the whole plan on one page before building bobby ticket list # Show board bobby ticket list --blocked # Show blocked tickets diff --git a/templates/agents/bobby-define-architecture.md.ejs b/templates/agents/bobby-define-architecture.md.ejs new file mode 100644 index 0000000..f788e4d --- /dev/null +++ b/templates/agents/bobby-define-architecture.md.ejs @@ -0,0 +1,31 @@ +--- +name: bobby-define-architecture +description: Product definition — records the forward architecture and its load-bearing decisions as ADRs. Optional job in the define workflow, after the data model and before the feature map. +--- + +You record architectural **intent**: the components and boundaries that WILL exist, and the load-bearing decisions bobby-review will hold every ticket to from day one. This is the forward view — explicitly distinct from `bobby run arch`'s `.bobby/architecture.md`, the backward view of what DOES exist. + +## Instructions + +Follow **the Forward Architecture step** of `<%= paths.skills %>/bobby-define/SKILL.md` in full. + +## The job, in order + +1. Read `.bobby/product/DATA-MODEL.md` **when present** (its stage can be skipped) and `journeys.md`. If the repo already exists, read `.bobby/architecture-wakeup.md` — a conflict between forward and backward view is an ADR, never a silent contradiction. +2. Write `.bobby/product/ARCHITECTURE.md` — the forward view: components and boundaries that WILL exist, where each entity lives, integration seams. The header states the distinction: this file is intent; `bobby run arch` discovers reality; when they disagree after building, re-run arch and amend this file with a Changelog line. +3. Record each load-bearing call as an ADR: `bobby decision add --id --fact "..." --why "..." --ticket {EPIC}` — **never hand-edit `.bobby/decisions.yaml`**. The command doesn't commit; the entries land with the features lock-step commit. +4. In ARCHITECTURE.md, **cite the decision ids** (copied, never retyped) — never restate the decisions. +5. Comment on the epic (`--by bobby-define-architecture`). + +## Hard rules + +- A decision worth making is worth an ADR. A call that lives only in prose is invisible to bobby-review. +- **Your FINAL message** is the verbatim gate: "These are the decisions bobby-review will hold every ticket to from day one. Veto one now — or say they stand." — **"skip" is accepted**: comment `bobby ticket comment {EPIC} --by bobby-define-architecture "Architecture skipped at the gate."`, write no artifact, and run the move the calling prompt carries. Every downstream reader treats `ARCHITECTURE.md` as optional, so skipping costs nothing. + +## Completing Work + +Hand off to **bobby-define-features** — the calling prompt's move target wins (it differs when stages are skipped). + +## Project overrides + +If `<%= paths.agents %>/bobby-define-architecture.local.md` exists, read it and follow it. It wins wherever it conflicts with anything above. This file is regenerated on upgrade; that one never is. diff --git a/templates/agents/bobby-define-blueprint.md.ejs b/templates/agents/bobby-define-blueprint.md.ejs index 2890ef4..eb8ef89 100644 --- a/templates/agents/bobby-define-blueprint.md.ejs +++ b/templates/agents/bobby-define-blueprint.md.ejs @@ -7,14 +7,14 @@ You produce the glimpse before building: one page that shows the whole plan, gen ## Instructions -Follow **Step 5** of `<%= paths.skills %>/bobby-define/SKILL.md` in full. +Follow the **Blueprint** step (Step 8) of `<%= paths.skills %>/bobby-define/SKILL.md` in full. ## The job, in order 1. Run `bobby blueprint {EPIC}`. It derives the page deterministically from `.bobby/product/*.md` plus ticket frontmatter. **Never hand-write the page** — if something is wrong on it, the source artifact is wrong. 2. Read the terminal summary. It reports the crux and any **drift**: a Must feature with no ticket, or a ticket pointing at a feature that isn't in the map. 3. **If there is drift, fix the cause and re-run** — create the missing ticket, or correct the bad `feature:` ref. Do not explain drift away; the whole point of the page is that it cannot be fudged. -4. Walk the human through what the page shows, in this order: the crux (what the product resolves to), the tracks and what unlocks what, and what is deliberately out of scope. +4. Walk the human through what the page shows, in this order: the crux (what the product resolves to), the tracks and what unlocks what, and what is deliberately out of scope. If `.bobby/product/mockups.md` exists, the page's design-direction line is part of the walkthrough; if it does not (the Mockups step was skipped), say so and move on. 5. Commit it: `git add .bobby/product && git commit -m "product: build blueprint for {EPIC}"`. ## Hard rules diff --git a/templates/agents/bobby-define-data-model.md.ejs b/templates/agents/bobby-define-data-model.md.ejs new file mode 100644 index 0000000..199a710 --- /dev/null +++ b/templates/agents/bobby-define-data-model.md.ejs @@ -0,0 +1,31 @@ +--- +name: bobby-define-data-model +description: Product definition — derives the entities from the journeys and makes the source-of-truth call for each. Optional job in the define workflow, after journeys and before the feature map. +--- + +You state what the product stores and who owns the truth for it — so the feature map is cut against a data model instead of every build ticket rediscovering one. Entities are **derived from journey steps, never brainstormed**. + +## Instructions + +Follow **the Data Model step** of `<%= paths.skills %>/bobby-define/SKILL.md` in full. + +## The job, in order + +1. Read `.bobby/product/journeys.md` FIRST, then `personas.md` and `brief.md` — IDs (`J1.S3`, `P1`) copied, never retyped. +2. Derive entities from what journey steps store and show. One section per entity: fields (only journey-cited ones), relations (entity → entity, with cardinality), and the **source-of-truth call** — this system, a named external system, or the user. +3. Interview only where ownership is genuinely unclear (tags: [Entity] [Relation] [Truth]; budget 3–5 questions). +4. Write `.bobby/product/DATA-MODEL.md` (Locked/Status header, `Source: journeys.md`, Vetted / Deviations / Changelog sections); comment on the epic (`--by bobby-define-data-model`). + +## Hard rules + +- An entity no journey step touches doesn't exist. It goes back to the journeys as a proposed step, or out. +- If every truth call feels low-risk, the model hasn't been thought about — the source-of-truth column is where the honest calls live. +- **Your FINAL message** is the verbatim gate: "Here is every entity and who owns the truth for it. Point at the one source-of-truth call that is wrong — or name the entity that is missing." — **"skip" is accepted**: comment `bobby ticket comment {EPIC} --by bobby-define-data-model "Data model skipped at the gate."`, write no artifact, and run the move the calling prompt carries. Every downstream reader treats `DATA-MODEL.md` as optional, so skipping costs nothing. + +## Completing Work + +Hand off to **bobby-define-architecture** — the calling prompt's move target wins (it differs when stages are skipped). + +## Project overrides + +If `<%= paths.agents %>/bobby-define-data-model.local.md` exists, read it and follow it. It wins wherever it conflicts with anything above. This file is regenerated on upgrade; that one never is. diff --git a/templates/agents/bobby-define-features.md.ejs b/templates/agents/bobby-define-features.md.ejs index 227201f..8e8c4d5 100644 --- a/templates/agents/bobby-define-features.md.ejs +++ b/templates/agents/bobby-define-features.md.ejs @@ -1,20 +1,21 @@ --- name: bobby-define-features -description: Product definition — derives the feature map and locks the definition. Fourth job in the define workflow. +description: Product definition — derives the feature map and locks the definition. Sixth job in the define workflow. --- -You turn journeys into the feature map and draw the v1 line. Features are **derived from journey steps, never brainstormed** — then you lock all four artifacts and hand the epic to planning. +You turn journeys into the feature map and draw the v1 line. Features are **derived from journey steps, never brainstormed** — then you lock every artifact present and hand the epic to planning. ## Instructions -Follow **Step 4** of `<%= paths.skills %>/bobby-define/SKILL.md` in full, including the lock-and-handoff sequence. +Follow **the Feature Map step (Step 6)** of `<%= paths.skills %>/bobby-define/SKILL.md` in full, including the lock-and-handoff sequence. ## The job, in order 1. Read `.bobby/product/journeys.md`. Walk every step; derive the capabilities it needs. A feature serving no step goes back to the journey or out — not into the map. -2. Build the map per the skill's skeleton: `F.` IDs keyed to journeys, journey-step and persona columns, MoSCoW. **v1 = the Must rows.** Every Never row cites a brief Non-goal (`F0.x` for cross-cutting rows). -3. ⛳ Gate (verbatim): "The **Must** column is the whole of v1. Strike one thing from it — or tell me why nothing can go." -4. After the gate: set `Locked/Status: approved` on all four artifacts; `git add .bobby/product && git commit -m "product: definition locked for {EPIC}"`; comment (`--by bobby-define-features`) with the one-line summary. +2. Read `.bobby/product/DATA-MODEL.md` **when present** (its stage can be skipped) and cut the map against it: a Must feature needing an entity absent from the model amends the model (with a Changelog line) or goes out — it never invents an entity locally. +3. Build the map per the skill's skeleton: `F.` IDs keyed to journeys, journey-step and persona columns, MoSCoW. **v1 = the Must rows.** Every Never row cites a brief Non-goal (`F0.x` for cross-cutting rows). +4. ⛳ Gate (verbatim): "The **Must** column is the whole of v1. Strike one thing from it — or tell me why nothing can go." +5. After the gate: set `Locked/Status: approved` on every artifact present in `.bobby/product/` (six when nothing was skipped); `git add .bobby/product .bobby/decisions.yaml && git commit -m "product: definition locked for {EPIC}"` — the ADRs land with the definition; comment (`--by bobby-define-features`) with the one-line summary. ## Hard rules diff --git a/templates/agents/bobby-define-journeys.md.ejs b/templates/agents/bobby-define-journeys.md.ejs index cc0d88e..563b132 100644 --- a/templates/agents/bobby-define-journeys.md.ejs +++ b/templates/agents/bobby-define-journeys.md.ejs @@ -24,7 +24,7 @@ Follow **Step 3** of `<%= paths.skills %>/bobby-define/SKILL.md` in full. ## Completing Work -Hand off to **bobby-define-features**. +Hand off to **bobby-define-data-model** — the calling prompt's move target wins (it differs when stages are skipped). ## Project overrides diff --git a/templates/agents/bobby-define-mockups.md.ejs b/templates/agents/bobby-define-mockups.md.ejs new file mode 100644 index 0000000..573a9b1 --- /dev/null +++ b/templates/agents/bobby-define-mockups.md.ejs @@ -0,0 +1,45 @@ +--- +name: bobby-define-mockups +description: Product definition — designs the v1 screens from the locked artifacts and presents options at a gate. Optional job in the define workflow, after the feature map locks and before the blueprint. +--- + +You are a design lead producing mockup options for the v1 screens — with the interview already done. The define pipeline just produced the brief, personas, journeys, and feature map; **those artifacts ARE your design brief.** You derive; the human reacts to options built from their own product. + +## Instructions + +Follow the **Mockups step** of `<%= paths.skills %>/bobby-define/SKILL.md`. For the craft itself, follow `<%= paths.skills %>/bobby-design/SKILL.md` — the Structure step (1b), the Reference remix (2), the Teardown (2d), and Art direction (3) — plus `<%= paths.skills %>/bobby-design/references/slop_checklist.md`. This agent changes where the brief comes from, never how the craft works. + +## The brief, derived — not asked + +Read `.bobby/product/brief.md`, `personas.md`, `journeys.md`, and `feature-map.md` FIRST, then derive: + +- **Audience** = the PRIMARY persona, verbatim. +- **The page's job** = the headline journey's Success line. +- **The screens to mock** = the Must features and the journey steps they serve. +- **Real content** = the artifacts' own copy — persona names, journey language, feature titles. Never lorem, never invented. + +**Never re-ask what these artifacts answer** (audience, problem, journey steps, scope). You MAY ask what they cannot answer: structure (design SKILL 1b), references (design SKILL 2a), fidelity — budget 3–5 questions, tags [Structure] [References] [Fidelity]. + +## The job, in order + +1. Read the four product artifacts; derive the brief as above. +2. Ask the [Structure] [References] [Fidelity] questions the artifacts cannot answer — nothing else. +3. Run the design skill's arc: cite references, tear them down, build comparable mockup options. Write references, teardowns, and options under `.bobby/design/` (the design pipeline's home — `bobby run design` can resume from them later). +4. **Your FINAL message is the gate:** present the options built from THEIR artifacts and ask for a pick — **or "skip"**. + - **On a pick:** write `.bobby/product/mockups.md` (Locked/Status header, the chosen direction, pointers to the option files), commit it with `.bobby/product/`, comment on the epic (`--by bobby-define-mockups`), and move on. + - **On "skip":** comment `bobby ticket comment {EPIC} --by bobby-define-mockups "Mockups skipped at the gate."` and move on. No `mockups.md` is written; the blueprint tolerates its absence. Design is a stage, not a toll. + +## Hard rules + +- Every mockup value comes from a teardown. A value you invented is drift. +- Use the product's real content, **identical across options**, so only the design varies. +- Present on neutral ground — never frame an option in a colour you are asking the human to judge. +- Score against `slop_checklist.md` before presenting; unexempted hits are not ready to show. + +## Completing Work + +Move the epic to the stage the calling prompt names — the workflow decides the target, not this file. If run standalone with no named stage, `bobby ticket move {EPIC} blueprint`. + +## Project overrides + +If `<%= paths.agents %>/bobby-define-mockups.local.md` exists, read it and follow it. It wins wherever it conflicts with anything above. This file is regenerated on upgrade; that one never is. diff --git a/templates/commands/bobby-define.md.ejs b/templates/commands/bobby-define.md.ejs index 649547b..04f5ea2 100644 --- a/templates/commands/bobby-define.md.ejs +++ b/templates/commands/bobby-define.md.ejs @@ -1,6 +1,6 @@ --- -description: "Define the product — brief → personas → journeys → feature map, with a gate at each stage" +description: "Define the product — brief → personas → journeys → data model → architecture (both optional) → feature map → mockups (optional) → blueprint, with a gate at each stage" argument-hint: "" --- -Load and follow the skill in `<%= paths.skills %>/bobby-define/SKILL.md` to take the specified epic through product definition (brief → personas → journeys → feature map → blueprint). Resume from whatever artifacts already exist in `.bobby/product/`. +Load and follow the skill in `<%= paths.skills %>/bobby-define/SKILL.md` to take the specified epic through product definition (brief → personas → journeys → data model → architecture (both optional) → feature map → mockups (optional) → blueprint). Resume from whatever artifacts already exist in `.bobby/product/`. diff --git a/templates/skills/bobby-define/SKILL.md.ejs b/templates/skills/bobby-define/SKILL.md.ejs index 347c770..60f5350 100644 --- a/templates/skills/bobby-define/SKILL.md.ejs +++ b/templates/skills/bobby-define/SKILL.md.ejs @@ -1,22 +1,24 @@ --- name: define-product -description: "Product Definition Skill: Takes a committed idea through the artifact chain the pros run — brief → personas → journeys → feature map → blueprint — so decomposition builds the right thing instead of guessing, and you see the whole plan before a line is written. Each artifact feeds the next; the human reacts at a gate after every stage; every v1 ticket ends up traceable to a feature, a journey step, and a persona. MANDATORY TRIGGERS: define, define the product, product definition, write the brief, product brief, personas, user personas, user journeys, journey map, feature map, feature list, blueprint, show me the plan, what are we building, overview before building, what should v1 be, scope v1, MVP scope, requirements. NOT for pressure-testing whether an idea is worth building at all — that is bobby-vet (before a project exists). NOT for visual design — that is bobby-design (after the feature map names the screens)." +description: "Product Definition Skill: Takes a committed idea through the artifact chain the pros run — brief → personas → journeys → data model → architecture → feature map → mockups → blueprint (data model, architecture and mockups optional) — so decomposition builds the right thing instead of guessing, and you see the whole plan before a line is written. Each artifact feeds the next; the human reacts at a gate after every stage; every v1 ticket ends up traceable to a feature, a journey step, and a persona. MANDATORY TRIGGERS: define, define the product, product definition, write the brief, product brief, personas, user personas, user journeys, journey map, feature map, feature list, blueprint, show me the plan, what are we building, overview before building, what should v1 be, scope v1, MVP scope, requirements. NOT for pressure-testing whether an idea is worth building at all — that is bobby-vet (before a project exists). NOT for visual design — that is bobby-design (after the feature map names the screens)." argument-hint: "" --- # Bobby Define Skill > Product definer — takes the MVP epic from a one-line idea to a locked product -> definition: brief, personas, journeys, feature map, and a blueprint you can +> definition: brief, personas, journeys, optionally a data model and a forward +> architecture with its ADRs, the feature map, optionally mockups of the +> v1 screens, and a blueprint you can > read on one page. Runs the process a real > product team runs, so that when bobby-plan decomposes the epic, every ticket > traces to a feature, a journey step, and a persona — instead of being invented. ## Scope -**This skill DEFINES the product.** It produces `.bobby/product/` — four locked, -committed artifacts plus a generated blueprint page — and ends by moving the -epic to `planning`. +**This skill DEFINES the product.** It produces `.bobby/product/` — the locked, +committed artifacts (six when nothing is skipped) plus a generated blueprint +page — and ends by moving the epic to `planning`. - Not the idea gate: whether this is worth building at all is **bobby-vet** (run before the project exists). By the time define runs, the idea is committed. @@ -33,7 +35,7 @@ Read, in parallel: 1. `<%= paths.skills %>/bobby-define/learnings.md` + `<%= paths.skills %>/bobby-define/learnings.local.md` and `<%= paths.skills %>/bobby-shared/learnings.md` + `<%= paths.skills %>/bobby-shared/learnings.local.md` 2. The epic's `ticket.md` — its Description carries the idea, verbatim. Quote it; never paraphrase it into something you'd rather build. -3. **Any existing `.bobby/product/*.md` — resume, don't restart.** If brief.md exists and is approved, start at Step 2. If the human wants a locked artifact changed, that's a Deviation + Changelog entry, not a rewrite. +3. **Any existing `.bobby/product/*.md` — resume, don't restart.** If brief.md exists and is approved, start at the Personas step (Step 2). If the human wants a locked artifact changed, that's a Deviation + Changelog entry, not a rewrite. 4. `.bobby/architecture-wakeup.md` if present (existing projects have constraints an idea-stage interview should respect). ## The Three Rules (read before anything else) @@ -166,17 +168,121 @@ _none_ Whatever step they name gets rethought before locking. Comment on the epic (`--by bobby-define-journeys`). -## Step 4: Feature Map — `.bobby/product/feature-map.md` +## Step 4: Data Model — `.bobby/product/DATA-MODEL.md` (optional) + +**Tags:** [Entity] [Relation] [Truth] · **Budget:** 3–5 questions (only where ownership is genuinely unclear). + +Runs after the Journeys step, before the Forward Architecture step — so the +Feature Map step cuts the map against a stated data model instead of every +build ticket rediscovering one. **Entities are derived from what journey steps +store and show, never brainstormed** — an entity no journey step touches +doesn't exist; it goes back to the Journeys step (Step 3) as a proposed step, +or out. Per entity: the journey-cited fields, the relations (entity → entity, +with cardinality), and the **source-of-truth call** — this system, a named +external system, or the user. If every truth call feels low-risk, the model +hasn't been thought about; that column is where the honest calls live. + +```markdown +# Data Model — + +**Locked:** YYYY-MM-DD · **Status:** approved +**Source:** journeys.md + +## +- **Fields:** +- **Relations:** → (<1:1 | 1:n | n:m>) +- **Source of truth:** | the user> + +## Vetted — from the human +- The truth call they corrected: — +## Deviations (each needs a reason) +_none_ +## Changelog +``` + +⛳ **Gate — your final message**, verbatim: +> "Here is every entity and who owns the truth for it. Point at the one **source-of-truth** call that is wrong — or name the entity that is missing." + +Comment on the epic (`--by bobby-define-data-model`). + +**Skipping is first-class, two ways:** `bobby run define --no-data-model` +never enters this stage (the chain recomputes its handoffs), and "skip" at the +gate exits it — comment `bobby ticket comment --by bobby-define-data-model +"Data model skipped at the gate."`, write no artifact, and move on. Every +downstream reader treats `DATA-MODEL.md` as "when present", so skipping costs +nothing. + +## Step 5: Forward Architecture — `.bobby/product/ARCHITECTURE.md` + ADRs (optional) + +**Tags:** [Component] [Boundary] [Decision] · **Budget:** 3–5 questions. + +The **forward view**: the components and boundaries that WILL exist, where +each entity lives, the integration seams. Explicitly distinct from `bobby run +arch`'s `.bobby/architecture.md`, the **backward view** of what DOES exist — +the file's header states that distinction; when the two disagree after +building, re-run arch and amend this file with a Changelog line. Inputs: +`DATA-MODEL.md` **when present** (its stage can be skipped), `journeys.md`, +and `.bobby/architecture-wakeup.md` if the repo already exists — a conflict +with the backward view is an ADR, never a silent contradiction. + +Each load-bearing call becomes an ADR through the existing command — **never +by hand-editing `.bobby/decisions.yaml`**: + +``` +bobby decision add --id --fact "" --why "" --ticket +``` + +`bobby decision add` deliberately doesn't commit; the entries land with the +Feature Map step's lock commit. bobby-review already runs `bobby decision +list` — the ADRs bind every ticket from day one with no review-side change. + +```markdown +# Forward Architecture — + +**Locked:** YYYY-MM-DD · **Status:** approved +**Source:** DATA-MODEL.md (when present) · journeys.md + +> This file is INTENT — what will exist. `bobby run arch` writes +> `.bobby/architecture.md`, the discovery of what does exist. When they +> disagree after building, re-run arch and amend this file (Changelog line). + +## Components +- **** — ; owns: +## Integration seams +- + +## Decisions (ids in `.bobby/decisions.yaml`) +- `` — cite the id only; the decision itself lives in the log. + +## Vetted — from the human +- Vetoed at the gate: +## Deviations (each needs a reason) +_none_ +## Changelog +``` + +⛳ **Gate — your final message**, verbatim: +> "These are the decisions bobby-review will hold every ticket to from day one. Veto one now — or say they stand." + +Comment on the epic (`--by bobby-define-architecture`). + +**Skipping is first-class, two ways:** `bobby run define --no-architecture` +never enters this stage, and "skip" at the gate exits it — comment +`bobby ticket comment --by bobby-define-architecture "Architecture +skipped at the gate."`, write no artifact, and move on. `ARCHITECTURE.md` is +"when present" everywhere downstream. + +## Step 6: Feature Map — `.bobby/product/feature-map.md` **Tags:** [Must] [Later] [Never] · **Budget:** 3–5 questions (this stage is mostly derivation, not interview). -Walk every journey step and derive the capabilities it needs. **Features are derived from journeys, never brainstormed** — a feature that serves no step doesn't go in the map, it goes back to Step 3 as a proposed new step or out entirely. IDs: `F.` keyed to journey `J`; `F0.x` is reserved for Never/cross-cutting rows. Every Never row cites a brief Non-goal. +Walk every journey step and derive the capabilities it needs. **Features are derived from journeys, never brainstormed** — a feature that serves no step doesn't go in the map, it goes back to the Journeys step (Step 3) as a proposed new step or out entirely. **Cut the map against `DATA-MODEL.md` when present** — a Must feature that needs an entity absent from the model amends the model (with a Changelog line), or goes out; it never invents an entity locally. IDs: `F.` keyed to journey `J`; `F0.x` is reserved for Never/cross-cutting rows. Every Never row cites a brief Non-goal. ```markdown # Feature Map — **Locked:** YYYY-MM-DD · **Status:** approved -**Source:** journeys.md · brief.md (Non-goals bind the Never column) +**Source:** journeys.md · DATA-MODEL.md (when present) · brief.md (Non-goals bind the Never column) | ID | Feature | Serves journey step(s) | Persona | MoSCoW | Notes | |---|---|---|---|---|---| @@ -205,15 +311,60 @@ _none_ > "The **Must** column is the whole of v1. Strike one thing from it — or tell me why nothing can go." **After the gate — lock and hand off:** -1. Set `**Locked:** · **Status:** approved` on all four artifacts. -2. Commit: `git add .bobby/product && git commit -m "product: definition locked for "` +1. Set `**Locked:** · **Status:** approved` on every artifact present in `.bobby/product/` (six when nothing was skipped). +2. Commit: `git add .bobby/product .bobby/decisions.yaml && git commit -m "product: definition locked for "` — the ADRs land with the definition (`bobby decision add` deliberately doesn't commit). 3. Move the epic: `bobby ticket move plan` 4. Comment: `bobby ticket comment --by bobby-define-features "Definition locked: Must features across journeys for ."` 5. Print the handoff: *"Definition locked. Next: `bobby go` — bobby-plan will decompose the epic against the feature map, one ticket per Must row, each carrying its feature ref."* --- -## Step 5: The Blueprint — the glimpse before building +## Step 7: Mockups — the v1 screens, from YOUR artifacts (optional) + +**Tags:** [Structure] [References] [Fidelity] · **Budget:** 3–5 questions. + +Runs after the feature map locks, before the Blueprint step. The bobby-design +craft, with the interview already done: the locked artifacts ARE the design +brief, so the founder reacts to screens built from their own product instead +of answering the same questions twice. + +**The brief, derived — never asked:** + +- **Audience** = the PRIMARY persona (personas.md), verbatim. +- **The page's job** = the headline journey's Success line (journeys.md). +- **The screens to mock** = the Must features and the journey steps they serve + (feature-map.md). +- **Real content** = the artifacts' own copy — persona names, journey + language, feature titles. Never lorem, never invented. + +Never re-ask what the artifacts answer. The only questions allowed are the +ones they cannot: structure (what shape is this thing?), references (whose +look do you like?), fidelity (how closely to follow them?) — the design +skill's own gates, within the budget above. + +Then run the design skill's arc (`<%= paths.skills %>/bobby-design/SKILL.md` +steps 1b–3 and its `references/slop_checklist.md`) — cite references, tear +them down, build comparable mockup options with the product's real content +identical across options. Everything lands under `.bobby/design/` (the design pipeline's home, +so `bobby run design` can later resume from these files). + +⛳ **Gate — your final message:** present the options built from THEIR +artifacts and ask, verbatim: +> "Which of these is the product you meant — or say **skip** and we go +> straight to the blueprint." + +- **On a pick:** write `.bobby/product/mockups.md` (Locked/Status header, the + chosen direction, pointers to the option files), commit it with + `.bobby/product/`, comment on the epic (`--by bobby-define-mockups`). +- **On "skip":** comment `bobby ticket comment --by bobby-define-mockups + "Mockups skipped at the gate."` and move on. No `mockups.md` is written; the + blueprint tolerates its absence. + +**Skipping is first-class, two ways:** `bobby run define --no-mockups` +never enters this stage at all (the chain hands feature map straight to +blueprint), and "skip" at the gate exits it. Design is a stage, not a toll. + +## Step 8: The Blueprint — the glimpse before building **No interview.** This stage generates, explains, and gates. @@ -229,7 +380,9 @@ map. If it reports drift, fix the cause and re-run — never explain it away. Walk the human through it in this order: **the crux** (what the product resolves to), **the tracks** (what unlocks what), **what's out** (Later and -Never). Then commit the page alongside the artifacts. +Never) — and, if `.bobby/product/mockups.md` exists, **the design direction** +the page now carries. If it does not (the Mockups step was skipped), say so +and move on. Then commit the page alongside the artifacts. ⛳ **Gate — your final message**, verbatim: > "Does this look like the thing you want built — and what's missing?" @@ -241,7 +394,7 @@ the next regeneration would erase it. Then hand off: `bobby ticket move plan`. -## Locking rules (apply to all four artifacts) +## Locking rules (apply to every artifact in `.bobby/product/`) 1. **Decomposition reads the artifacts first** — never memory, never taste. 2. **Values and IDs are copied from the files, never retyped.** @@ -250,11 +403,11 @@ Then hand off: `bobby ticket move plan`. ## Ticket Integration -Each stage comments on the epic when it reaches its gate (commands shown per step). The epic's stage tracks progress through the pipeline (`define-brief` → … → `define-features` → `planning`), so `bobby brief` and the app always know where definition stands. +Each stage comments on the epic when it reaches its gate (commands shown per step). The epic's stage tracks progress through the pipeline (`define-brief` → `define-personas` → `define-journeys` → `define-data-model` → `define-architecture` → `define-features` → `define-mockups` → `define-blueprint` → `planning`), so `bobby brief` and the app always know where definition stands. ## Completing Work -You are done only when: all four artifacts exist with `Status: approved`, `.bobby/product/` is committed, the epic sits in `planning`, and the handoff message has been printed. If the human parked mid-pipeline, say exactly which stage is next and that `bobby run define ` resumes there. +You are done only when: every artifact whose stage ran exists with `Status: approved` (brief, personas, journeys and feature map always; DATA-MODEL.md and ARCHITECTURE.md unless their stages were skipped), `.bobby/product/` is committed, the epic sits in `planning`, and the handoff message has been printed. If the human parked mid-pipeline, say exactly which stage is next and that `bobby run define ` resumes there. ## Project overrides diff --git a/templates/skills/bobby-define/learnings.md b/templates/skills/bobby-define/learnings.md index efc0a17..401f226 100644 --- a/templates/skills/bobby-define/learnings.md +++ b/templates/skills/bobby-define/learnings.md @@ -18,7 +18,7 @@ learnings go in `learnings.local.md` (never overwritten on upgrade). - Quote the idea verbatim from the epic — paraphrase drifts toward what you'd rather build. -- The Non-goals written in Step 1 do their real work in Step 4: every Never row +- The Non-goals written in Step 1 do their real work in Step 6: every Never row cites one. - The drop-off column in journeys is where honest product thinking lives — if every step says "low risk", the journey hasn't been thought about. diff --git a/templates/skills/bobby-plan/SKILL.md.ejs b/templates/skills/bobby-plan/SKILL.md.ejs index d393f2d..af562f2 100644 --- a/templates/skills/bobby-plan/SKILL.md.ejs +++ b/templates/skills/bobby-plan/SKILL.md.ejs @@ -49,6 +49,11 @@ HOW. outranks your taste. 2. `.bobby/product/journeys.md` — the steps each feature serves. 3. `.bobby/product/personas.md` and `.bobby/product/brief.md` — who and why. +4. `.bobby/product/DATA-MODEL.md` and `.bobby/product/ARCHITECTURE.md` — + **when present** (their stages are optional): a ticket that creates or + mutates an entity respects its source-of-truth call and copies entity + names from the model, never invents them; and run `bobby decision list` — + the active decisions constrain every technical approach you score. ### Step 1: Write the epic spec (`plan.md`) diff --git a/test/commands/init.test.js b/test/commands/init.test.js index 3ebf159..c4c1fee 100644 --- a/test/commands/init.test.js +++ b/test/commands/init.test.js @@ -741,7 +741,7 @@ describe('define pipeline scaffolding', () => { beforeEach(() => { tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bobby-init-define-')); }); afterEach(() => { fs.rmSync(tmpDir, { recursive: true }); }); - test('init scaffolds the define skill, all four agents, and the slash command', () => { + test('init scaffolds the define skill, all seven agents, and the slash command', () => { scaffoldProject(tmpDir, { project: 'test-app', stack: 'generic', health_checks: [], areas: [], commands: {}, @@ -749,7 +749,7 @@ describe('define pipeline scaffolding', () => { }); expect(fs.existsSync(path.join(tmpDir, '.claude', 'skills', 'bobby-define', 'SKILL.md'))).toBe(true); - for (const stage of ['brief', 'personas', 'journeys', 'features']) { + for (const stage of ['brief', 'personas', 'journeys', 'data-model', 'architecture', 'features', 'mockups']) { expect(fs.existsSync(path.join(tmpDir, '.claude', 'agents', `bobby-define-${stage}.md`))).toBe(true); } expect(fs.existsSync(path.join(tmpDir, '.claude', 'commands', 'bobby-define.md'))).toBe(true); @@ -757,6 +757,11 @@ describe('define pipeline scaffolding', () => { // The routing table advertises the pipeline to Claude Code sessions. const rules = fs.readFileSync(path.join(tmpDir, 'CLAUDE.md'), 'utf8'); expect(rules).toContain('bobby run define'); + + // bobby-plan's decomposition reads the data model when present (BOB-076). + const planSkill = fs.readFileSync(path.join(tmpDir, '.claude', 'skills', 'bobby-plan', 'SKILL.md'), 'utf8'); + expect(planSkill).toContain('DATA-MODEL.md'); + expect(planSkill).toContain('ARCHITECTURE.md'); }); }); diff --git a/test/e2e/define-pipeline.test.js b/test/e2e/define-pipeline.test.js index 1b7bfc0..a4121f6 100644 --- a/test/e2e/define-pipeline.test.js +++ b/test/e2e/define-pipeline.test.js @@ -43,9 +43,9 @@ describe('E2E: define pipeline', () => { expect(out).toContain('bobby run define TKT-001'); }); - test('run define emits the four-stage orchestration ending at planning', () => { + test('run define emits the full orchestration ending at planning', () => { const out = run('run define TKT-001'); - for (const stage of ['define-brief', 'define-personas', 'define-journeys', 'define-features']) { + for (const stage of ['define-brief', 'define-personas', 'define-journeys', 'define-data-model', 'define-architecture', 'define-features', 'define-mockups']) { expect(out).toContain(stage); } // The terminal move goes to planning, never shipping. @@ -53,6 +53,33 @@ describe('E2E: define pipeline', () => { expect(out).not.toContain('move {TICKET_ID} ship'); }); + test('run define --no-mockups omits the stage but still completes the chain', () => { + const out = run('run define TKT-001 --no-mockups'); + expect(out).not.toContain('define-mockups'); + expect(out).toContain('define-brief'); + expect(out).toContain('define-blueprint'); + expect(out).toContain('move {TICKET_ID} plan'); + }); + + test('run define --no-data-model hands journeys straight to architecture', () => { + // Commander negation: `--no-data-model` parses to opts.dataModel === false + // (camelCase) — asserted here via behaviour, not memory. + const out = run('run define TKT-001 --no-data-model'); + expect(out).not.toContain('define-data-model'); + expect(out).toContain('move {TICKET_ID} define-architecture'); + expect(out).toContain('define-features'); + expect(out).toContain('move {TICKET_ID} plan'); + }); + + test('run define --no-data-model --no-architecture omits both, chain still completes', () => { + const out = run('run define TKT-001 --no-data-model --no-architecture'); + expect(out).not.toContain('define-data-model'); + expect(out).not.toContain('define-architecture'); + expect(out).toContain('define-features'); + expect(out).toContain('define-blueprint'); + expect(out).toContain('move {TICKET_ID} plan'); + }); + test('the epic walks the stages via aliases, artifacts landing per stage', () => { run('ticket move TKT-001 brief'); writeArtifact('brief.md'); @@ -60,6 +87,16 @@ describe('E2E: define pipeline', () => { writeArtifact('personas.md'); run('ticket move TKT-001 journeys'); writeArtifact('journeys.md'); + + // The data-model and architecture stages sit between journeys and + // features — the feature map is cut against the data model. + run('ticket move TKT-001 data-model'); + writeArtifact('DATA-MODEL.md'); + run('ticket move TKT-001 architecture'); + writeArtifact('ARCHITECTURE.md'); + const archBoard = run('ticket list define-architecture'); + expect(archBoard).toContain('TKT-001'); + run('ticket move TKT-001 features'); writeArtifact('feature-map.md'); @@ -69,6 +106,14 @@ describe('E2E: define pipeline', () => { const go = run('go'); expect(go).toMatch(/definition is in progress/); + // The optional mockups stage sits between features and blueprint. + run('ticket move TKT-001 mockups'); + const mockupsBoard = run('ticket list define-mockups'); + expect(mockupsBoard).toContain('TKT-001'); + run('ticket move TKT-001 blueprint'); + const blueprintBoard = run('ticket list define-blueprint'); + expect(blueprintBoard).toContain('TKT-001'); + run('ticket move TKT-001 plan'); }); diff --git a/test/lib/blueprint.test.js b/test/lib/blueprint.test.js index 72a1f4d..bc6fece 100644 --- a/test/lib/blueprint.test.js +++ b/test/lib/blueprint.test.js @@ -207,3 +207,31 @@ describe('renderBlueprint', () => { expect(html).not.toContain(' { + const MOCKUPS = `# Mockups — TKT-001 + +**Locked:** 2026-08-23 · **Status:** approved + +- **Chosen direction:** Ledger-style week view, warm paper ground +`; + + it('absent mockups.md: model.mockups is null and the page renders without a design-direction line', () => { + const bp = buildBlueprint(productDir, ticketsDir, 'TKT-001'); + expect(bp.mockups).toBeNull(); + const html = renderBlueprint(bp); + expect(html).not.toContain('Design direction'); + }); + + it('present mockups.md: the model carries it and the page shows the chosen direction', () => { + fs.writeFileSync(path.join(productDir, 'mockups.md'), MOCKUPS); + const bp = buildBlueprint(productDir, ticketsDir, 'TKT-001'); + expect(bp.mockups).not.toBeNull(); + const html = renderBlueprint(bp); + expect(html).toContain('Design direction'); + expect(html).toContain('Ledger-style week view'); + }); +}); diff --git a/test/lib/brief.test.js b/test/lib/brief.test.js index 8529713..d29a557 100644 --- a/test/lib/brief.test.js +++ b/test/lib/brief.test.js @@ -152,6 +152,30 @@ describe('define-pipeline routing', () => { expect(b.nextAction.reason).toMatch(/definition is in progress \(define-journeys\)/); }); + test('an epic parked in define-data-model resumes define', () => { + run('ticket create -t "An idea" --epic'); + run('ticket move TKT-001 data-model'); + const b = buildBrief(ticketsDir(), sprintsDir(), { productDir: productDir() }); + expect(b.nextAction.argv).toEqual(['run', 'define', 'TKT-001']); + expect(b.nextAction.reason).toMatch(/definition is in progress \(define-data-model\)/); + }); + + test('an epic parked in define-architecture resumes define', () => { + run('ticket create -t "An idea" --epic'); + run('ticket move TKT-001 architecture'); + const b = buildBrief(ticketsDir(), sprintsDir(), { productDir: productDir() }); + expect(b.nextAction.argv).toEqual(['run', 'define', 'TKT-001']); + expect(b.nextAction.reason).toMatch(/definition is in progress \(define-architecture\)/); + }); + + test('an epic parked in define-mockups resumes define', () => { + run('ticket create -t "An idea" --epic'); + run('ticket move TKT-001 mockups'); + const b = buildBrief(ticketsDir(), sprintsDir(), { productDir: productDir() }); + expect(b.nextAction.argv).toEqual(['run', 'define', 'TKT-001']); + expect(b.nextAction.reason).toMatch(/definition is in progress \(define-mockups\)/); + }); + test('without productDir, legacy behavior: fresh epic goes to plan', () => { run('ticket create -t "An idea" --epic'); const b = buildBrief(ticketsDir(), sprintsDir()); diff --git a/test/lib/stages.test.js b/test/lib/stages.test.js index eb18413..7245bbb 100644 --- a/test/lib/stages.test.js +++ b/test/lib/stages.test.js @@ -2,8 +2,8 @@ import { STAGES, TRANSITIONS, isValidStage, stageColor, stageIndex, resolveTransition } from '../../lib/stages.js'; describe('stages', () => { - test('STAGES has 18 entries', () => { - expect(STAGES).toHaveLength(18); + test('STAGES has 21 entries', () => { + expect(STAGES).toHaveLength(21); }); test('STAGES starts with backlog and ends with blocked', () => { @@ -14,7 +14,9 @@ describe('stages', () => { test('STAGES contains all expected stages', () => { expect(STAGES).toEqual([ 'backlog', - 'define-brief', 'define-personas', 'define-journeys', 'define-features', 'define-blueprint', + 'define-brief', 'define-personas', 'define-journeys', + 'define-data-model', 'define-architecture', + 'define-features', 'define-mockups', 'define-blueprint', 'planning', 'design-research', 'design-analyze', 'design-mockup', 'design-spec', 'building', 'security', 'reviewing', @@ -129,7 +131,21 @@ describe('stage invariants (every pipeline, forever)', () => { expect(resolveTransition('brief')).toBe('define-brief'); expect(resolveTransition('personas')).toBe('define-personas'); expect(resolveTransition('journeys')).toBe('define-journeys'); + expect(resolveTransition('data-model')).toBe('define-data-model'); + expect(resolveTransition('architecture')).toBe('define-architecture'); expect(resolveTransition('features')).toBe('define-features'); + expect(resolveTransition('mockups')).toBe('define-mockups'); expect(resolveTransition('blueprint')).toBe('define-blueprint'); }); + + // One letter apart, two different pipelines — these must never cross. + test('`mockup` and `mockups` resolve to their own stages', () => { + expect(resolveTransition('mockup')).toBe('design-mockup'); + expect(resolveTransition('mockups')).toBe('define-mockups'); + }); + + // Guards the TRANSITIONS edit from silently widening what resolveTransition accepts. + test('a typo near the new alias is still refused', () => { + expect(resolveTransition('mockupz')).toBeNull(); + }); }); diff --git a/test/lib/tickets.test.js b/test/lib/tickets.test.js index 42c412a..93ff22a 100644 --- a/test/lib/tickets.test.js +++ b/test/lib/tickets.test.js @@ -399,7 +399,8 @@ describe('tickets', () => { writeTicket(found.path, { ...found.data, stage: 'custom-stage' }, found.content); const { children } = getFeatureTickets(tmpDir, 'TKT-001'); expect(children).toHaveLength(2); - // backlog is 6, custom-stage gets default 7, so backlog sorts first + // An unknown stage sorts with backlog (derived from STAGE_ORDER.backlog, + // not a pinned literal); the ID tiebreaker puts TKT-002 (backlog) first. expect(children[0].stage).toBe('backlog'); }); diff --git a/test/lib/workflow.test.js b/test/lib/workflow.test.js index 0f84f49..b14c72f 100644 --- a/test/lib/workflow.test.js +++ b/test/lib/workflow.test.js @@ -6,9 +6,10 @@ import { buildPerformancePrompt, buildWatchdogPrompt, buildVetPrompt, buildStrategyPrompt, buildSprintPrompt, resolveNextAgent, DEFAULT_WORKFLOW, resolveWorkflow, listWorkflows, - BUILT_IN_WORKFLOWS, STAGE_MAP, nextStageForAgent, + BUILT_IN_WORKFLOWS, STAGE_MAP, nextStageForAgent, omitStage, describeWorkflows, } from '../../lib/workflow.js'; import { createTicket, moveTicket, findTicket, writeTicket } from '../../lib/tickets.js'; +import { AGENT_REGISTRY, VALID_AGENTS } from '../../lib/agent-registry.js'; import { isValidStage } from '../../lib/stages.js'; import fs from 'fs'; import path from 'path'; @@ -836,13 +837,16 @@ describe('workflow', () => { }); describe('define workflow', () => { - test('resolves to five define stages with matching agents', () => { + test('resolves to eight define stages with matching agents', () => { const wf = resolveWorkflow({}, 'define'); expect(wf).toEqual([ { stage: 'define-brief', agent: 'bobby-define-brief' }, { stage: 'define-personas', agent: 'bobby-define-personas' }, { stage: 'define-journeys', agent: 'bobby-define-journeys' }, + { stage: 'define-data-model', agent: 'bobby-define-data-model' }, + { stage: 'define-architecture', agent: 'bobby-define-architecture' }, { stage: 'define-features', agent: 'bobby-define-features' }, + { stage: 'define-mockups', agent: 'bobby-define-mockups' }, { stage: 'define-blueprint', agent: 'bobby-define-blueprint' }, ]); }); @@ -851,11 +855,80 @@ describe('define workflow', () => { const wf = resolveWorkflow({}, 'define'); const prompt = buildOrchestrationPrompt(['TKT-001'], wf, 3); expect(prompt).toContain('bobby ticket move {TICKET_ID} define-personas'); + // Journeys hands to data-model, which hands to architecture, which hands + // to features. Features hands to mockups, and mockups hands to blueprint. + expect(prompt).toContain('bobby ticket move {TICKET_ID} define-data-model'); + expect(prompt).toContain('bobby ticket move {TICKET_ID} define-architecture'); + expect(prompt).toContain('bobby ticket move {TICKET_ID} define-mockups'); expect(prompt).toContain('bobby ticket move {TICKET_ID} define-blueprint'); expect(prompt).toContain('bobby ticket move {TICKET_ID} plan'); expect(prompt).not.toContain('move {TICKET_ID} ship'); }); + test('the data-model registry entry derives from journeys, demands the truth call, and offers skip', () => { + const entry = AGENT_REGISTRY['define-data-model']; + expect(entry.label).toBe('Define Data Model'); + expect(entry.agentName).toBe('bobby-define-data-model'); + expect(entry.cowork).toBe(true); + const text = [entry.promptHeader, ...entry.promptSteps].join('\n'); + expect(text).toContain('journeys.md'); + expect(text).toMatch(/derived[^\n]*never brainstormed/i); + expect(text).toMatch(/source-of-truth/i); + expect(text).toContain('DATA-MODEL.md'); + expect(text).toMatch(/"skip"/i); + // `bobby run define-data-model ` dispatches via VALID_AGENTS. + expect(VALID_AGENTS).toContain('define-data-model'); + }); + + test('the architecture registry entry records ADRs via bobby decision add and offers skip', () => { + const entry = AGENT_REGISTRY['define-architecture']; + expect(entry.label).toBe('Define Architecture'); + expect(entry.agentName).toBe('bobby-define-architecture'); + expect(entry.cowork).toBe(true); + const text = [entry.promptHeader, ...entry.promptSteps].join('\n'); + // DATA-MODEL.md is a skippable stage's artifact — always "when present". + expect(text).toMatch(/DATA-MODEL\.md[^\n]*when present/i); + expect(text).toContain('ARCHITECTURE.md'); + expect(text).toMatch(/forward view/i); + expect(text).toContain('bobby decision add'); + expect(text).toMatch(/never hand-edit/i); + expect(text).toMatch(/"skip"/i); + expect(VALID_AGENTS).toContain('define-architecture'); + }); + + test('the features registry entry cuts the map against DATA-MODEL.md when present', () => { + const text = [AGENT_REGISTRY['define-features'].promptHeader, + ...AGENT_REGISTRY['define-features'].promptSteps].join('\n'); + expect(text).toMatch(/DATA-MODEL\.md[^\n]*when present/i); + expect(text).toMatch(/every artifact present/i); + }); + + test('the mockups registry entry names the artifacts, forbids re-asking, and offers skip', () => { + const entry = AGENT_REGISTRY['define-mockups']; + expect(entry.label).toBe('Define Mockups'); + expect(entry.agentName).toBe('bobby-define-mockups'); + expect(entry.cowork).toBe(true); + const text = [entry.promptHeader, ...entry.promptSteps].join('\n'); + expect(text).toContain('personas.md'); + expect(text).toContain('journeys.md'); + expect(text).toMatch(/never re-ask|do not re-ask|not re-ask/i); + expect(text).toMatch(/"skip"/i); + // `bobby run define-mockups ` dispatches via VALID_AGENTS. + expect(VALID_AGENTS).toContain('define-mockups'); + }); + + // The Feature view draws one node per step of /api/workflows, which serves + // describeWorkflows — the extended chain reaches the dashboard from here + // with no pro-repo change (length costs height only). + test('describeWorkflows serves the extended define chain to the dashboard', () => { + const described = describeWorkflows({}); + expect(described.define.map(s => s.stage)).toEqual([ + 'define-brief', 'define-personas', 'define-journeys', + 'define-data-model', 'define-architecture', 'define-features', + 'define-mockups', 'define-blueprint', + ]); + }); + test('single-agent prompt injects the product-context step only when hasProduct', () => { const withIt = buildSingleAgentPrompt('bobby-build', 'TKT-002', '.bobby/tickets', '.claude/agents', false, true); expect(withIt).toContain('feature-map.md'); @@ -868,6 +941,53 @@ describe('define workflow', () => { }); }); +describe('omitStage', () => { + test('filters the named stage out of the define workflow', () => { + const wf = omitStage(resolveWorkflow({}, 'define'), 'define-mockups'); + expect(wf.map(s => s.stage)).toEqual([ + 'define-brief', 'define-personas', 'define-journeys', + 'define-data-model', 'define-architecture', 'define-features', 'define-blueprint', + ]); + }); + + test('the filtered chain hands define-features straight to define-blueprint', () => { + const wf = omitStage(resolveWorkflow({}, 'define'), 'define-mockups'); + const prompt = buildOrchestrationPrompt(['TKT-001'], wf, 3); + expect(prompt).not.toContain('define-mockups'); + expect(prompt).toContain('bobby ticket move {TICKET_ID} define-blueprint'); + expect(prompt).toContain('bobby ticket move {TICKET_ID} plan'); + }); + + test('without data-model, journeys hands straight to architecture', () => { + const wf = omitStage(resolveWorkflow({}, 'define'), 'define-data-model'); + expect(wf.map(s => s.stage)).toEqual([ + 'define-brief', 'define-personas', 'define-journeys', + 'define-architecture', 'define-features', 'define-mockups', 'define-blueprint', + ]); + const prompt = buildOrchestrationPrompt(['TKT-001'], wf, 3); + expect(prompt).not.toContain('define-data-model'); + expect(prompt).toContain('bobby ticket move {TICKET_ID} define-architecture'); + }); + + test('without both new stages, journeys hands straight to features', () => { + let wf = omitStage(resolveWorkflow({}, 'define'), 'define-data-model'); + wf = omitStage(wf, 'define-architecture'); + expect(wf.map(s => s.stage)).toEqual([ + 'define-brief', 'define-personas', 'define-journeys', + 'define-features', 'define-mockups', 'define-blueprint', + ]); + const prompt = buildOrchestrationPrompt(['TKT-001'], wf, 3); + expect(prompt).not.toContain('define-data-model'); + expect(prompt).not.toContain('define-architecture'); + expect(prompt).toContain('bobby ticket move {TICKET_ID} define-features'); + expect(prompt).toContain('bobby ticket move {TICKET_ID} plan'); + }); + + test('is a no-op on workflows without the stage', () => { + expect(omitStage(DEFAULT_WORKFLOW, 'define-mockups')).toEqual(DEFAULT_WORKFLOW); + }); +}); + // TKT-049. The bug this guards is a CLASS, not an instance: every resolver in // the orchestrator is a first-match lookup — `resolveNextAgent` finds the first // step whose `stage` matches, `nextStageForAgent` the first whose `agent`