447 lines
18 KiB
Markdown
447 lines
18 KiB
Markdown
# Discovery & Scoping Playbook
|
||
|
||
A repeatable method for going from "handover document + a folder of legacy
|
||
spreadsheets" to "buildable platform with locked decisions, persisted memory,
|
||
and a vetted business-owner sign-off". Designed to be driven by a human
|
||
with an AI coding assistant (Claude Code, Cursor, etc.) — not by the AI
|
||
alone.
|
||
|
||
The method works because each phase produces a **durable artifact** that
|
||
the next phase consumes. No phase relies on conversation history; if a
|
||
session ends, the next one resumes from the artifacts.
|
||
|
||
A worked example using this method is in `seminarhof-walkthrough.md`.
|
||
|
||
---
|
||
|
||
## Phase 0 — Repo init from template
|
||
|
||
**Goal.** Get a working repo on disk with the template's stack already
|
||
wired, plus a private working tree where production data can land
|
||
without ever touching git.
|
||
|
||
**Steps**
|
||
|
||
1. Create the empty remote repo (org-scoped, private by default).
|
||
2. Copy the template into a temp dir, init git, push to the new remote.
|
||
3. Move the working copy out of the temp dir into a permanent location.
|
||
4. **Before anything else**: extend `.gitignore` to cover all paths that
|
||
will hold production data, handover documents, or scrubbed data
|
||
derived from production data:
|
||
```
|
||
data/seed/
|
||
data/seed-scrubbed/
|
||
docs/handover/
|
||
```
|
||
5. Create empty `data/seed/` and `data/seed-scrubbed/` directories so
|
||
the AI can drop scripts referencing them without errors.
|
||
|
||
**Artifacts.** A pushed-to-main empty-template repo + gitignore lockdown.
|
||
|
||
**Anti-pattern.** Letting the AI commit a CSV before gitignore is set up.
|
||
Once production data hits a public remote, you can't un-leak it — even
|
||
after a force-push, the data is in clones and CI logs.
|
||
|
||
---
|
||
|
||
## Phase 1 — Inventory the inputs
|
||
|
||
**Goal.** Convert every source document (handover PDF, sample
|
||
spreadsheets, screenshots, hand-drawn diagrams) into a single
|
||
**structured inventory document** before designing anything.
|
||
|
||
**Why first.** Every later phase references the inventory. If you skip
|
||
straight to schema design, you'll model what you assume the source data
|
||
looks like rather than what it actually contains, and you'll re-discover
|
||
columns by accident two weeks in.
|
||
|
||
**Steps**
|
||
|
||
1. For each spreadsheet / source workbook, capture:
|
||
- Tab names and tab purposes
|
||
- Per-tab column list with your best-guess interpretation
|
||
- Cell value vocabulary if the cells aren't simple values (codes,
|
||
bracketed conventions, abbreviations, etc.)
|
||
2. For each source PDF (floor plans, AGB, etc.), extract structure into
|
||
tables — e.g. one row per room with whatever attributes are visible.
|
||
3. For each artifact, record what's **confirmed** vs **best-guess**.
|
||
Best-guesses become open questions later.
|
||
4. Cross-reference between artifacts to surface contradictions (e.g.
|
||
"this name appears here but not on the floor plan").
|
||
|
||
**Artifacts.**
|
||
- `docs/handover/csv-inventory.md` — structure of every source spreadsheet
|
||
- `docs/handover/rooms-inventory.md` (or similar per major artifact)
|
||
- Scribbled best-guess tables marked clearly as such
|
||
|
||
**Tip — anchoring.** When you later ask the business owner about a
|
||
column or value, anchor every question to its source artifact ("In
|
||
**Belegungsplan** the column `auto-renewal-2`…"). It eliminates
|
||
ambiguity for the owner and lets you trace each decision back to its
|
||
trigger.
|
||
|
||
---
|
||
|
||
## Phase 2 — Privacy-first data handling
|
||
|
||
**Goal.** Get useful structural insight from production data without
|
||
ever letting raw personal data reach an LLM API.
|
||
|
||
**Why this matters.** Most LLM APIs retain inputs for ~30 days for
|
||
trust & safety. A single conversational `cat` of a customer CSV
|
||
becomes a GDPR Art. 9 (special-category) breach if dietary, allergy,
|
||
or health data is in it. The fix is structural, not policy: the AI
|
||
cannot leak data it never saw.
|
||
|
||
**Method**
|
||
|
||
1. Owner exports CSVs / Excel from source systems and drops them in
|
||
`data/seed/` (gitignored).
|
||
2. AI writes a **scrubber script** — pure stdlib, no deps — that:
|
||
- Reads every CSV in `data/seed/`
|
||
- Detects PII columns by header heuristics in the local language
|
||
(German + English in our case)
|
||
- Detects PII patterns regardless of column (email, phone, IBAN
|
||
regex)
|
||
- Replaces values with **deterministic fakes** (hash → fake, so the
|
||
same input maps to the same fake — preserves foreign-key integrity
|
||
and repeated-organisation grouping)
|
||
- Blanks notes/comments columns by default (highest-risk free text)
|
||
- Outputs to `data/seed-scrubbed/` (also gitignored)
|
||
3. AI never reads `data/seed/`. Only ever reads `data/seed-scrubbed/`.
|
||
|
||
**What survives in scrubbed output:** column names, row counts, value
|
||
patterns, dates, statuses, room numbers, dietary text values, prices.
|
||
Enough to design schema honestly.
|
||
|
||
**What never reaches the AI:** real names, emails, phones, addresses,
|
||
IBANs, free-text notes.
|
||
|
||
**Artifacts.**
|
||
- `scripts/scrub-csvs.mjs` (or equivalent)
|
||
- `data/seed/` and `data/seed-scrubbed/` populated locally; both
|
||
gitignored
|
||
|
||
**Anti-pattern.** "I'll just paste a few rows in chat to show structure."
|
||
A few rows is enough to identify individuals via correlation; resist.
|
||
Structure can always be conveyed via column names + 2 obviously-fake
|
||
sample rows (`Max Mustermann`).
|
||
|
||
**Edge case the owner will raise.** "But the structure is messy and you
|
||
need to see real values to make sense of it." This is when the scrubber
|
||
earns its keep — they get to keep the messiness, you get to read the
|
||
output safely.
|
||
|
||
---
|
||
|
||
## Phase 3 — Question discipline
|
||
|
||
**Goal.** Convert ambiguity into locked decisions one at a time, with
|
||
rationale, anchored to source.
|
||
|
||
**The core rule.** Ask **one** question per turn. Multi-question batches
|
||
encourage thin answers and mask cross-question contradictions.
|
||
|
||
**Question shape that works**
|
||
|
||
```
|
||
In <source artifact>, <specific anchor> — <plain question>?
|
||
[2–4 options, each with a one-line consequence]
|
||
```
|
||
|
||
Each option must spell out what *changes in the platform* if it's
|
||
chosen. "Cleaner" is not a consequence; "no `house_half` column on
|
||
rooms" is.
|
||
|
||
**When the owner picks an option** → lock it. Move on.
|
||
|
||
**When the owner says "I don't know"** → log to a single deferred-items
|
||
file with the consequence, and move on. Don't argue.
|
||
|
||
**When the owner gives a free-text answer that doesn't match the
|
||
options** → restate it in one line ("Got it — X."), confirm, lock,
|
||
move on. The options were a forcing function, not a constraint.
|
||
|
||
**When the owner asks "what would you recommend?"** → answer in one
|
||
sentence with the trade-off, then bring them back to deciding. Don't
|
||
make the decision for them.
|
||
|
||
**Artifacts.**
|
||
- Running `docs/handover/open-questions.md` — every deferred question
|
||
with its rationale (so anyone can pick it up later without re-deriving
|
||
the context)
|
||
- Memory entries for locked decisions (Phase 4)
|
||
|
||
**Anti-pattern.** Asking "are you sure?" after every answer. Lock the
|
||
decision and move forward; if it turns out wrong, the audit log of
|
||
locked decisions makes it cheap to revisit.
|
||
|
||
**Anti-pattern.** Asking infrastructure questions (DB engine, hosting,
|
||
file storage) when the chosen framework already provides them. Skip
|
||
those entirely; they aren't business decisions.
|
||
|
||
---
|
||
|
||
## Phase 4 — Lock decisions in memory
|
||
|
||
**Goal.** Make decisions survive session boundaries.
|
||
|
||
**Why.** Conversation context disappears when the session ends. A
|
||
locked decision that lives only in conversation is a decision that
|
||
will be re-litigated the next time someone (you, the AI, a teammate)
|
||
opens a new session.
|
||
|
||
**Steps**
|
||
|
||
1. As decisions are locked in Phase 3, mirror them into a memory file
|
||
under `~/.claude/projects/<project-key>/memory/`.
|
||
2. The memory file is a *living* artifact — append new locks, update
|
||
superseded ones (don't pile contradictions), remove decisions that
|
||
were rolled back.
|
||
3. Reference the memory file from `MEMORY.md` so future sessions
|
||
auto-load it.
|
||
4. Distinguish **business decisions** (locked, in memory) from
|
||
**infra decisions** (defer to framework, don't pretend to lock).
|
||
|
||
**Artifacts.**
|
||
- `~/.claude/projects/<project-key>/memory/project_<name>.md`
|
||
- `MEMORY.md` link
|
||
|
||
**What goes in memory.** Locked rules: status enums, pricing logic,
|
||
auth model, multi-tenancy stance, scope exclusions, deferred items,
|
||
privacy guardrails. The shape of decisions, not the implementation.
|
||
|
||
**What does *not* go in memory.** Code, file paths, line numbers, the
|
||
contents of any source data file. Those rot fast and you can re-read
|
||
them from disk any time.
|
||
|
||
---
|
||
|
||
## Phase 5 — Generate a business-owner interview prompt
|
||
|
||
**Goal.** Hand the open questions back to the non-technical business
|
||
owner in a form they can answer without a developer babysitting them.
|
||
|
||
**The artifact.** A single self-contained markdown file the owner
|
||
pastes into a fresh AI session. The file contains:
|
||
|
||
1. A how-to-use header (paste this whole file into Claude/ChatGPT, etc.)
|
||
2. Instructions to the LLM (interview tone, ask one at a time, restate
|
||
in plain language, push back on vagueness, don't accept "we'll
|
||
figure it out")
|
||
3. Background section: what the business is, what the platform replaces,
|
||
what's already decided (so the LLM doesn't re-open settled ground)
|
||
4. Each open question, complete with a *why-it-matters* rationale the
|
||
LLM reuses verbatim
|
||
5. Output spec — the LLM must produce a single answers document at the
|
||
end, with a specific structure, that the owner sends back
|
||
|
||
**Why this works.** The owner can answer at their own pace, in their
|
||
own language. The LLM acts as a patient interviewer rather than a
|
||
form. Vague answers get probed automatically. The output document is
|
||
mechanical to consume back into the project.
|
||
|
||
**Artifacts.**
|
||
- `docs/handover/business-requirements-prompt.md`
|
||
- (later) `docs/handover/business-requirements-answers.md`
|
||
|
||
**Anti-pattern.** Sending a 20-row spreadsheet to the owner and asking
|
||
them to fill it in. Spreadsheets get half-answered or skipped on the
|
||
hard items. A guided conversational interview surfaces edge cases the
|
||
owner wouldn't have thought to flag.
|
||
|
||
---
|
||
|
||
## Phase 6 — Receive answers, propagate, re-lock
|
||
|
||
**Goal.** Take the owner's answers and propagate them through every
|
||
artifact built so far.
|
||
|
||
**Steps**
|
||
|
||
1. Save the raw answers verbatim as `business-requirements-answers.md`.
|
||
2. For each answered question:
|
||
- If it overrides a best-guess in an inventory document, update the
|
||
inventory with the authoritative value (mark the source: "confirmed
|
||
by owner on <date>").
|
||
- Move the question out of `open-questions.md`.
|
||
- Add the locked rule to memory (Phase 4 file).
|
||
3. Items marked `DEFER` stay in `open-questions.md` with the next
|
||
decision-maker named. They are the only thing that file should
|
||
contain after Phase 6.
|
||
|
||
**Artifacts.**
|
||
- `business-requirements-answers.md` (verbatim)
|
||
- Updated inventories with `confirmed by` notes
|
||
- Slimmed-down `open-questions.md`
|
||
- Updated memory file
|
||
|
||
**The discipline test.** After Phase 6, a brand-new AI session opening
|
||
this repo should be able to design the schema purely from the
|
||
artifacts in the repo and memory — no conversation history needed. If
|
||
that's not true, an artifact is missing.
|
||
|
||
---
|
||
|
||
## Phase 7 — UI design before code
|
||
|
||
**Goal.** Decide what each portal *looks like* and *contains* before
|
||
writing components.
|
||
|
||
**Why this is its own phase.** The data model is now locked, but a
|
||
data model alone doesn't tell you what the UI is. Two valid designs
|
||
can sit on the same schema and feel like different products.
|
||
|
||
**Method**
|
||
|
||
1. Identify the surfaces (in this template's case: client portal, admin,
|
||
staff, public).
|
||
2. For each surface, decide:
|
||
- Audience and primary jobs-to-be-done
|
||
- Default landing screen (what they see when they log in)
|
||
- Density and tone (warm/dense/minimal/marketing)
|
||
- Mobile vs desktop priority
|
||
- Shared theme tokens vs surface-specific overrides
|
||
3. For the **highest-stakes surface** (usually admin), drill into the
|
||
landing screen's sections one at a time, the same way Phase 3 drills
|
||
into business questions.
|
||
|
||
**Artifacts.**
|
||
- `docs/design/<surface>.md` per portal — sections, components,
|
||
interactions
|
||
- Reference screenshots from the owner where they exist
|
||
|
||
**Anti-pattern.** "Same theme, same components everywhere." A
|
||
client-facing card-based dashboard makes admin Christina cry when she
|
||
needs to manage 200 bookings across 5 years. Same tokens; different
|
||
component vocabularies per surface.
|
||
|
||
---
|
||
|
||
## Phase 7.5 — Acceptance scenarios before code
|
||
|
||
**Goal.** Every workflow that's about to be built has a `.feature` file
|
||
*before* a line of implementation code is written.
|
||
|
||
**Why this is its own phase.** Acceptance scenarios written *after* the
|
||
code rationalise whatever shipped — they validate the implementation,
|
||
not the requirement. Written *before*, they pin the requirement and
|
||
double as the build's definition of done. They also surface
|
||
contradictions in the locked decisions that schema design and UI
|
||
mockups didn't catch (e.g. "the feature says clients can do X, but role
|
||
matrix in memory says only admins can").
|
||
|
||
**Method**
|
||
|
||
1. For each surface from Phase 7, list the workflows that have to work
|
||
end-to-end (login, view-own-bookings, room-assignment,
|
||
create-invoice, etc.).
|
||
2. For each workflow, write a Cucumber `.feature` file with at least
|
||
one happy-path scenario and one role-gating / permission scenario.
|
||
Use the generic step library from the e2e harness; project-specific
|
||
steps come later.
|
||
3. Auth is **always the first feature**. Without it the rest of the
|
||
scenarios can't even reach a logged-in state.
|
||
4. Don't write the step *implementations* yet beyond the generic ones
|
||
(`I visit`, `I log in as`, `I see`). Project-specific steps land in
|
||
Phase 8 alongside the code they exercise.
|
||
|
||
**Artifacts.**
|
||
- `e2e/tests/features/auth.feature` — first, always
|
||
- `e2e/tests/features/<workflow>.feature` — one per locked workflow
|
||
- A failing test run (red) — the baseline against which Phase 8
|
||
progress is measured
|
||
|
||
**Anti-pattern.** "We'll add tests once it's working." Tests added
|
||
after-the-fact tend to mirror the code's shape rather than the
|
||
requirement's shape, and they don't catch the gaps the requirement
|
||
process should have caught. The whole point of Phase 7.5 is that the
|
||
test is the requirement, in executable form.
|
||
|
||
**Anti-pattern.** Writing 40 detailed scenarios up front. Cover the
|
||
locked workflows with **one happy path + one critical edge each**.
|
||
Exhaustive scenario coverage is a Phase 9 activity (regression as the
|
||
product grows), not a Phase 7.5 one.
|
||
|
||
---
|
||
|
||
## Phase 8 — Build (vertical slice)
|
||
|
||
**Goal.** Ship a slice that touches every layer (DB → backend → API →
|
||
UI) for one real workflow, instead of completing a full layer at a
|
||
time.
|
||
|
||
**Why.** Layer-at-a-time builds discover integration problems last,
|
||
when they're most expensive. Vertical-slice builds discover them in
|
||
week one.
|
||
|
||
**Build auth first.** Before any feature work: real login, real
|
||
sessions, real role enforcement. Every later function takes its
|
||
`userId` and `role` from the session, never from a `DEMO_USER`
|
||
constant or an input parameter. Skipping this and "wiring auth at the
|
||
end" sounds cheap but isn't — every function signature, every UI page,
|
||
every permission check has to be retrofitted, contracts re-versioned,
|
||
seeds re-hashed, and route guards added across the whole app. Doing it
|
||
first costs a day; doing it last costs a week and leaves footguns in
|
||
every file you didn't revisit.
|
||
|
||
**What "auth first" means concretely:**
|
||
|
||
1. Sessions table + cookie middleware (or equivalent for the chosen
|
||
stack) live before the first feature function is written.
|
||
2. Password hashing uses a runtime-portable primitive (WebCrypto
|
||
PBKDF2, Argon2-wasm, etc.) — not bcrypt/node:crypto if the target
|
||
includes edge runtimes.
|
||
3. Seed data includes hashed passwords for every demo role from day
|
||
one. Demo passwords are documented in seed comments only, never in
|
||
memory or repo docs.
|
||
4. `pikkuFunc` (or the equivalent session-required handler) is the
|
||
default; sessionless handlers are the explicit, justified
|
||
exception.
|
||
5. UI has a `RequireAuth` wrapper with a `roles` prop *before* the
|
||
first protected page is built.
|
||
|
||
**Steps**
|
||
|
||
1. Land auth as the first vertical slice (login → cookie → `me` → role
|
||
guard on one trivial admin page).
|
||
2. Pick the next workflow that exercises the trickiest parts of the
|
||
schema (in our case: client logs in → views their booking → fills
|
||
room plan → admin sees the update → kitchen view reflects it).
|
||
3. Build only what that workflow needs at every layer.
|
||
4. Once it's working end-to-end, expand sideways.
|
||
|
||
**Artifacts.**
|
||
- A demoable URL
|
||
- A short loom-style "what works, what doesn't" note for the owner
|
||
|
||
**Anti-pattern.** "We'll wire real auth at the end, just hard-code
|
||
`DEMO_USER` for now." Every function written under that assumption
|
||
takes a `userId` input parameter that has to be deleted later, and
|
||
every UI mutation passes it explicitly. Retrofitting means touching
|
||
every file twice and re-versioning every contract.
|
||
|
||
---
|
||
|
||
## Cross-phase rules
|
||
|
||
**Auto mode.** When the owner has set the AI to "auto" / "execute
|
||
without confirming", still pause for irreversible actions: deleting
|
||
data, force-pushing, or actions that touch shared systems. Authorisation
|
||
to "go fast" is not authorisation to be careless.
|
||
|
||
**Memory hygiene.** When a locked decision is later overridden, *update*
|
||
the memory file rather than appending a contradiction. Stale memory is
|
||
worse than no memory.
|
||
|
||
**Confidentiality.** Treat any owner-shared spreadsheet, screenshot, or
|
||
PDF as containing data the org has not authorised to share with an AI
|
||
provider. Either scrub it (Phase 2) or extract structure manually before
|
||
ingesting.
|
||
|
||
**Documentation order.** Inventory → questions → answers → memory →
|
||
design → acceptance scenarios → code. Skipping inventory or questions
|
||
costs more downstream than the time saved. Skipping acceptance
|
||
scenarios means the build's definition of done is "feels right when I
|
||
click around" — which is how `DEMO_USER` constants ship to production.
|