lab-codebase.zipLearning objectives
By the end of this module, you will be able to:
- Adopt Spec Kit and SPECTRA in an existing repository from a reviewable baseline, and confirm from the diff exactly what initialization added.
- Write a constitution from rules that are already true in the code, and name cruft separately from conventions.
- Specify one bounded change — its outcome and its compatibility limits — without specifying the whole system.
- Run the full loop on existing code (clarify, plan against the repository, tasks, analyze, implement, converge) and review code and artifacts together.
- Decide, during validation, whether to fix the code or fix the spec.
- Choose how specs will age in this project and record the choice in the constitution.
Why this lab is the centerpiece
Greenfield SDD is the easy case. Tutorials and demo videos almost always show greenfield. Most real engineering work is brownfield — you have a codebase, it has conventions (some good, some weird), and you need to add a feature without breaking the rest of it.
The brownfield case is where most teams either give up on SDD because the tools “don't fit,” or generate giant codebase-summary specs that nobody reviews. There's a third path, and it's the one Spec Kit's own guidance for existing projects recommends: initialize in place, write down the rules that genuinely hold, and point the workflow at the next bounded change.
If you only do one lab in this course, do this one. Each agent step ends at a human gate from SPECTRA's agentic SDLC — agents draft, people decide — and in this lab every one of those people is you. Commands are shown in the Claude Code / GitHub Copilot trigger form (/speckit-specify); for other agents, see the command-spelling note in Module 1.
1.Run the starter app (15 min)
1.1 Get the lab codebase
Download lab-codebase.zip using the link above. Unzip somewhere you can work in:
unzip lab-codebase.zip -d ~/sdd-brownfield-lab
cd ~/sdd-brownfield-lab/lab-codebase
1.2 Install dependencies and run
You need Node.js 18.18 or newer.
npm install
npm run dev
Open http://localhost:3000. You should see “My To-Dos” — an input box, an “Add” button, and (after you add some) a list of tasks. Try it out:
- Add a few tasks.
- Mark some done. Notice the strikethrough.
- Refresh the page. Tasks should persist.
- Delete a task.
Leave a few tasks in the list: saved before due dates existed, they're exactly the data your compatibility requirement must protect. Stop the dev server with Ctrl+C.
1.3 Read the code
Spend 10 minutes reading the source. Don't skim. The whole codebase is about 200 lines across:
lib/storage.ts—Todotype and localStorage helpersapp/page.tsx— the main page; owns the state and effectsapp/components/TodoForm.tsx— input + Add buttonapp/components/TodoList.tsx— list rendering and empty stateapp/components/TodoItem.tsx— single task row
While you read, keep a small note open and write down what you see. Specifically:
- Things that look like conventions — consistent patterns, deliberately chosen, you'd want to preserve.
- Things that look like cruft — rough edges, probably not deliberate, you'd fix in passing.
This list is the heart of the brownfield work. Your constitution will encode the conventions. The cruft you'll either tolerate (out of scope), fix during the work (in scope), or note for later (a follow-up spec). But you have to decide, which means you have to name them first.
A hint, not a spoiler. The codebase is small but it does have at least three pieces of intentional cruft. If you find none, you're skimming. If you “find” ten, you may be classifying conventions as cruft. The right number is small.
2.Make a reviewable baseline (5 min)
Before any tool touches this repository, commit what's there. Everything that follows — initialization, the constitution, each spec, every line of generated code — then shows up as an ordinary diff you can read and revert.
The zip ships with no .git and no .gitignore, and npm install just created node_modules/ and .next/. Ignore those first, or your baseline will contain thousands of dependency files:
printf 'node_modules/\n.next/\nnext-env.d.ts\n*.tsbuildinfo\n' > .gitignore
git init && git add -A && git commit -m "baseline"
git status should now report a clean working tree.
3.Install SPECTRA and review the diff (10 min)
3.1 Initialize in place
spectra install
Same command as Module 3. It notices this folder isn't a Spec Kit project yet and offers to initialize it; accept. What it runs is exactly specify init --here --force, the command Spec Kit's guide prescribes for an existing repository. --here targets the current folder; --force allows a non-empty folder and may replace files at the paths Spec Kit manages. It won't delete your application, but that permission to overwrite is why you made the baseline first.
When prompted for an AI agent, pick the one you used in Module 3. SPECTRA then registers its catalog and installs its extension. Restart the agent afterwards. Everything in this lab except /speckit-spectra-domain-analyzer and the pull-request offer after implement is plain Spec Kit.
3.2 Review the diff
git status
Read what changed before going further. You should see only new, untracked folders:
.specify/— Spec Kit's templates, scripts, and extensions, plus.specify/memory/constitution.md, still an unfilled template.- Your agent's skill or command files — for Claude Code,
.claude/skills/speckit-*, including SPECTRA'sspeckit-spectra-*.
No modified files: app/, lib/, and package.json are untouched. And notice what's missing: no specs/ folder. Initialization infers no specs for the behaviour your app already has. Commit it:
git add -A && git commit -m "Add Spec Kit and SPECTRA"
Keep that habit: commit after each step you approve, so the next git diff shows exactly what the next agent run changed.
3.3 The new spec describes the change, not the system
Don't fill that gap with a spec of the whole to-do app. The existing code stays implementation context that every later command reads; your first spec describes only the change you're about to make. Documenting the entire system is the right first feature only when that inventory is itself the deliverable.
Your first job after init needn't be a feature at all: a bug fix (Module 6) or an idea assessment (Module 5) can start straight away. This lab takes the feature path.
4.Write the constitution from what's already true (20 min)
In a greenfield project, the constitution comes before the code. Here the code came first, so you extract the constitution from what you observe — the most distinctive brownfield activity, and the one most likely to go wrong if rushed.
4.1 Only rules that are true today, or explicitly agreed
Every rule has to pass one test: can you point at the code, README, config, or team decision that makes it true? Here your evidence is the source, package.json, tsconfig.json, the Tailwind config, and the README.
Don't invent standards to fill the template. The constitution governs everything downstream, and /speckit-analyze treats any conflict with it as critical. Write “every feature MUST have unit tests” into a repo with no test runner, and every plan either fails analysis or quietly grows a test framework nobody agreed to. That's noise, not a guardrail. If the team does agree to start testing, record that as the decision it is.
4.2 Let the agent gather evidence, not decide
Don't just ask your agent to “generate a constitution from this codebase.” It will pick up cruft as convention, because it can't tell deliberate from accidental, and it will over-include “just in case” — the markdown-monster failure mode from Module 2. Ask for evidence first, in plain chat (this isn't a Spec Kit command, and it shouldn't write anything):
Read app/page.tsx, app/components/*, lib/storage.ts, package.json and
README.md. List the patterns that repeat and look deliberate: file layout,
component conventions, styling, data shapes, dependencies. Cite the file
that shows each one. List separately anything that looks accidental.
Don't create or edit any files.
Compare its lists with your notes from step 1. Where you're unsure, ask:
- Is this pattern repeated, or is it in one place? Repeated patterns are more likely conventions; one-off oddities are more likely cruft.
- Is there an obvious cleaner alternative? If yes, it's probably cruft. If no, it might be a deliberate trade-off you don't yet understand.
- Would a careful code reviewer catch this? If yes, cruft. If it would slip past, more likely a convention you just haven't internalized.
You don't have to be right. The point is to decide, then revise as you learn more.
4.3 Run /speckit-constitution
Now hand over only the rules you accepted:
/speckit-constitution Record only rules that are already true in this
codebase: <the conventions you accepted>. Add a "Known cruft" section
listing <the cruft you named> as defects to fix when convenient, not
conventions to follow. Keep it short.
The agent fills in .specify/memory/constitution.md in Spec Kit's template layout (named principles, governance, a version line), so compare content with the sample below, not layout.
/speckit-spectra-domain-analyzer proposes candidate guardrails from the code, each citing its evidence, in .specify/memory/domain-analysis.md. All start unchecked; you tick the ones you accept and pass the file to /speckit-constitution. It never edits the constitution itself.4.4 Compare with a sample
Write your own version first, before peeking. Then compare.
# Todo App Constitution
## Stack
- Next.js 15 (App Router) with TypeScript in strict mode
- React 18
- Tailwind CSS for styling
- localStorage for persistence — no backend, no API routes
## Code organisation
- Page lives in `app/page.tsx`; it owns state and passes handlers down
- Reusable components live in `app/components/`
- Types and storage helpers live in `lib/storage.ts`
- Components using browser APIs declare `"use client"` at the top
## Styling conventions
- Tailwind utility classes only — no separate CSS files for layout
- `app/globals.css` is just the three Tailwind directives
- Default Tailwind palette; no custom theme extension yet
## Data conventions
- Todos shape: { id: string, text: string, completed: boolean, createdAt: string }
- `createdAt` is an ISO 8601 string
- Newest tasks render at the top of the list
- Completed tasks remain in the list with strikethrough; they are not removed
- Empty task text is rejected silently (no error message); no toasts
## State conventions
- Page owns state; components are presentational and receive callbacks as props
- localStorage is read once on mount inside a `useEffect`; written on every change
inside a separate `useEffect`. This is required to avoid hydration mismatch.
## Testing
- There is no automated test suite. Features are verified by hand against
the acceptance scenarios in their spec.
- Adding a test framework is a separate, explicit decision, never a side
effect of a feature.
## Known cruft (NOT conventions — fix when convenient)
- Magic string "todos" repeated in `lib/storage.ts` — extract a constant
- Leftover `console.log` in the page's mount effect — remove
- IDs generated with `Date.now().toString()` — switch to `crypto.randomUUID()`
(collision-safe; available in modern browsers)
Notice the last section: the constitution names the cruft as cruft. That stops the agent preserving it as if it were intentional — without it, an agent that sees Date.now() IDs would happily use them in the due-date feature — and it gives you a known list of follow-up work (the bonus exercise takes one item).
Notice, too, what's not there: no coverage target, no accessibility standard, no performance budget. Good ideas, perhaps, but none is true of this code or agreed by anyone. The Testing section records the honest state instead.
5./speckit-specify — one bounded change (20 min)
You are not specifying the whole to-do app. You are specifying the due-date addition: an outcome, plus the compatibility limits that must hold while you deliver it. The existing app is context, not subject.
5.1 Run /speckit-specify
/speckit-specify Add a due date to to-do items. When creating a task,
the user must pick a due date, and it must be in the future: tomorrow
or later, never today. Each task in the list shows its due date. When a
task's due date has passed and the task isn't completed, its due date
shows in red. Compatibility limits: adding, completing, deleting and
persisting tasks keep working exactly as they do today. To-dos saved
before this change have no due date; they must still load and display,
and are never shown as overdue. Nothing else about how to-dos are
stored changes.
The agent creates a feature directory such as specs/001-due-date/ with spec.md and a built-in quality checklist, checklists/requirements.md. Specify and clarify tick that checklist's items themselves, so read those ticks as the agent's claim, not your sign-off. The most common failure mode is over-scoping: sorting by due date, date-range filters, recurring to-dos, reminders, “due soon” colours. Push back hard on each one.
5.2 Review the draft
Apply the six-element checklist from Module 2. Spec Kit's template uses its own headings (user scenarios, requirements, success criteria, assumptions), so look for each element's content rather than its name. In this spec, check:
Outcomes — must be observable. Not “users can set due dates.” Closer to: “When creating a task, the user must pick a due date in the future. The due date is visible on each task in the list. Tasks whose due date has passed and that are not yet completed display the due date in red.”
In-scope — minimal, surgical. This feature adds a date field, validation that it's in the future, display of the date, and a red treatment when overdue. That's it.
Compatibility limits — the brownfield element. A brownfield spec also says what must not change. Check that each limit survived as its own numbered requirement, not just a sentence in the overview; otherwise nothing downstream can trace to it, and analysis can't flag a missing task.
Out-of-scope — write at least 5. This is the section that prevents an afternoon's feature from becoming a week. Suggested entries:
- Editing the due date after creation
- Sorting or filtering by due date
- Recurring or repeating tasks
- Notifications, reminders, or emails
- Time-of-day component (date only, no time)
- Time zones (local time only; cross-timezone is out)
- “Due soon” warnings (orange, yellow, etc.) — only past-due is red
- Optional due dates (the field is required for new tasks)
Constraints — reuse the constitution, don't repeat it. “Next.js + Tailwind + localStorage” is in the constitution; the spec doesn't restate it. Spec-level constraints are behaviour specific to this feature, such as “the date is picked with the browser's own date picker.” The stored format belongs in the plan.
Decisions already made. This is where you cut off rabbit holes. Examples:
- A due date is a date only — no time component.
- “In the future” means the picked date is strictly later than today's date in the user's local timezone.
- “Past due” means the due date is strictly earlier than today's local date AND the task is not completed.
- Validation runs on submit; if invalid, the task is not added and a visible inline error appears next to the date input.
- The overdue treatment is red text on the date only. No icon, no badge, no animation.
Verification criteria. At least 6, with at least 2 edge cases. In Spec Kit's template they're the acceptance scenarios under each user story.
- Submitting a task with a due date of tomorrow succeeds and the task appears in the list with that date.
- Submitting a task with today's date is rejected with an inline error; no task is added.
- Submitting a task with a date in the past is rejected with an inline error; no task is added.
- Submitting a task with no date set is rejected with an inline error; no task is added.
- A task with a due date in the past and
completed: falsedisplays the due date in red. - A task with a due date in the past and
completed: truedoes not display in red. - Edge: a task created today with tomorrow's due date displays in normal colour today, and would display in red two days from now (verify by adjusting system clock or using a far-past test date).
- Edge: refreshing the browser preserves all due dates; existing tasks (created before this change, with no
dueDatefield) load and display without errors and are never red.
The pattern: each line is a single, runnable check that passes or fails in the browser with no interpretation needed. None says “works correctly.”
5.3 The spec smell to watch for
If your spec mentions component file names, function signatures, Tailwind class strings, or the storage format, you've drifted into the plan. Pull those out. The Module 2 worked example shows the difference if you need a refresher.
6./speckit-clarify — settle what the spec leaves open (10 min)
/speckit-clarify
Clarify looks for decisions the spec leaves open and asks about them — one question at a time, at most five per pass, each with a recommended answer. Your answers are written into spec.md under ## Clarifications, and the affected requirements are updated. Plausible questions here (yours will differ):
- How should a to-do saved before this change — one with no due date — display? Blank, or a “No due date” label?
- In what format should the date appear in the list?
- If the page stays open past midnight, should a task turn red without a refresh?
- Does the inline error clear as soon as the user picks a valid date, or only on the next submit?
Three rules for answering:
- Answer from intent. Replying “yes” accepts the recommendation — fine when you've read it and agree, rubber-stamping when you haven't.
- Prefer the smallest behaviour that meets the outcome. “Turns red on the next page load” is a legitimate answer, and far smaller than a live midnight timer.
- Meet scope-expanding questions with an out-of-scope decision. That's a clarification too.
If an area still feels vague, run it again with a focus, e.g. /speckit-clarify Focus on how to-dos saved before this change behave. Then commit the spec.
7./speckit-plan — against the repository (15 min)
/speckit-plan Build on the existing codebase. Reuse its architecture,
dependencies, and conventions as recorded in the constitution, and add no
new dependencies.
Now you make implementation decisions, anchored to the code that exists. Besides plan.md, the agent writes supporting files such as research.md and data-model.md.
7.1 What the plan should describe
- Type change: an optional
dueDate?: string(ISOYYYY-MM-DD) onTodoinlib/storage.ts— optional because older to-dos lack it. - Compatibility: loading and saving don't change — same key, same list. A missing date is handled where dates are displayed (as your clarify answer decided; never overdue).
TodoFormchanges: an<input type="date">next to the text input, local state for the picked date, and validation on submit. If invalid, set and render an error message; don't callonAdd.- Validation logic: compare the input's
YYYY-MM-DDstring with today's local date in the same format. String comparison works for ISO dates (lexicographic order = chronological order). page.tsxchanges:addTodoaccepts and stores adueDate. No other state changes.TodoItemchanges: show the due date; compute “is overdue” with the same string comparison; applytext-red-600to the date when overdue and not completed.- No new dependencies.
7.2 Check that it reuses what exists
The brownfield question for every plan: does it build on this repository, or on a generic idea of a Next.js app?
- Architecture. The page still owns state; components stay presentational; storage stays in
lib/storage.ts. - Dependencies.
package.jsondoesn't change. - Conventions. Tailwind utilities,
"use client", the existing layout. The plan's Project Structure section should show the real tree (app/,app/components/,lib/), not a template placeholder likesrc/orbackend/+frontend/. - Constitution Check. This gate in the plan should pass with nothing to justify. A “justified” violation (“we need a date library”) is your cue to push back.
7.3 Traps to watch for
Dateobject trap. If the plan validates withnew Date(input) > new Date(), you have a hidden timezone bug: aYYYY-MM-DDstring parses as midnight UTC, so depending on your timezone and the hour, today can pass as the future or tomorrow can fail. Push back: compareYYYY-MM-DDstrings, building today's from the local calendar (getFullYear(),getMonth() + 1,getDate(), zero-padded). NottoISOString().split('T')[0]— that's the UTC date, the wrong day for part of every day anywhere outside UTC.- New storage key. If the plan adds a separate localStorage key for due dates (“efficiency”), reject it. Your spec says nothing else about storage changes.
- Migration overreach. If the plan rewrites every existing to-do to add a default
dueDate, push back. It's unnecessary and touches data the user didn't ask to modify. Handle a missingdueDateat the type level and the render level instead. - Custom date pickers or libraries. A new dependency (
react-datepicker,date-fns) is overkill. Native<input type="date">and string math are enough.
plan.md to someone who knows this codebase. Then commit.8./speckit-tasks, then /speckit-analyze (10 min)
8.1 Break the plan into tasks
/speckit-tasks
The agent writes a dependency-ordered tasks.md in phases: Setup, Foundational, one phase per user story in priority order, then Polish. A reasonable shape for this feature:
About 7–8 tasks for an afternoon's work. If your agent produces 4 mega-tasks or 25 micro-tasks, ask it to re-decompose. Also delete Setup tasks that scaffold a project that already exists (“initialize the project,” “configure linting”), test-framework tasks nobody asked for, and cruft fixes that crept in.
8.2 Check consistency before any code exists
/speckit-analyze
Analyze cross-checks spec.md, plan.md, and tasks.md against each other and the constitution, and reports findings by severity. It's read-only. On a brownfield change, look especially for a compatibility requirement with no task, a task with no requirement (often cruft cleanup or something you ruled out of scope), and plan choices that conflict with the constitution. Those conflicts are always critical — which is why an invented rule in step 4 would have turned into noise right here.
Fix each finding in the step that owns it: requirements with /speckit-specify or /speckit-clarify, design with /speckit-plan, the task list by re-running /speckit-tasks. Re-run /speckit-analyze until it comes back clean. (Spec Kit's full path also offers /speckit-checklist after plan, a reviewer-owned check of the spec's wording; this lab skips it to stay on time.)
9./speckit-implement — review code and artifacts together (30–40 min)
The longest phase. Watch for the same pitfalls as in Module 3, plus brownfield-specific ones.
9.1 Implement in stages
/speckit-implement Implement only the Setup, Foundational, and first
user-story phases, then stop and report progress.
First, implement counts checklist items; if any are unchecked it pauses to ask whether to continue. Read them before you answer — implement never changes a checkbox itself. When the run stops, restart the dev server and try:
- Add a task with a date in the future. It should appear in the list with the date shown.
- Try to add with today's date selected. It should show an inline error and not add.
- Try to add with no date selected. It should show an inline error and not add.
- Try to add with a past date. It should show an inline error.
Then run /speckit-implement again, without arguments, to finish. Once the display tasks are done, edit a task's dueDate in localStorage to a past date (devtools → Application → Local Storage) and refresh. The date should display in red; mark the task done and the red should disappear.
9.2 Brownfield-specific things to watch for
- The agent ignores existing patterns. A state-management library (Redux, Zustand) where the code uses plain
useStateis a regression. Stop it. - The agent rewrites things you didn't ask it to. Some agents helpfully “fix” the cruft you named. That's fine if you want it, but decide deliberately: mixing cleanup into feature work makes review harder.
- Files the plan didn't mention. If a
dateUtils.tsorhooks/folder appears that the plan never named, ask why. Datemath gone wrong. The plan may have dodged the step-7 trap and the code fallen into it anyway. Check both comparisons.- “Today” computed during render.
TodoFormis also rendered on the server. Compute today's date during render (say, for aminattribute) and server and browser can disagree, triggering a hydration mismatch. Compute it in the submit handler.TodoItemonly renders after the to-dos load in the browser, so it's safe there.
9.3 Review code and artifacts together
git status
git diff
Because you committed after each gate, this diff is exactly what implement did: source changes, tasks marked [X] in tasks.md, and any fixes you made in spec.md or plan.md. Review them as one change. Every code change should trace to a task and every task to a requirement — code with no task is scope creep, even when it's an improvement. Every [X] needs code behind it.
When implement finishes, SPECTRA offers to open a pull request (/speckit-spectra-create-pr). Decline: you haven't converged or validated, and this repo has no GitHub remote, which that command needs.
9.4 If something doesn't work
Ask two questions, in order. Does the spec describe what you actually want? If the behaviour surprises you, the spec was often vague: update it, then re-implement just the affected task. Does the code implement the spec? If the spec is right, fix the code. Never fix code without checking the spec first — that's how spec-anchored repos drift back into code-as-truth repos.
10./speckit-converge — until it reports Converged (10 min)
/speckit-converge
Converge compares the code with the spec, plan, and tasks and summarizes what's missing by severity. It's append-only: it never edits or deletes code, and the only thing it can write is new tasks at the end of tasks.md. It ends one of two ways:
- Converged — no gaps;
tasks.mdis untouched and you get a success message. - Tasks appended — gaps become new tasks under a Convergence phase. Run
/speckit-implement, then/speckit-convergeagain, until it reports Converged.
“Converged” means the code matches the artifacts. It can't tell you whether the artifacts match what you wanted — that's the next step, and it's yours.
11.Validate against the spec (10 min)
This is the part most teams skip. Don't.
11.1 Walk through the verification criteria
Go through each acceptance scenario in your spec and verify it holds in the browser. For edge cases, set up the precondition deliberately (edit localStorage, change the system clock if you can). The tasks you added in step 1 are genuine pre-change data — they should still be in the list, without a due date.
11.2 Spec-vs-implementation checklist
- Existing tasks (created before this change, with no
dueDate) still load and display without errors, and are never shown as overdue. - Adding a task with tomorrow's date succeeds.
- Adding a task with today's date is rejected with a visible inline error.
- Adding a task with a past date is rejected with a visible inline error.
- Adding a task with no date is rejected with a visible inline error.
- The due date appears on every task created after this change.
- An overdue, uncompleted task displays its due date in red.
- Marking the overdue task complete removes the red treatment.
- Refreshing the browser preserves due dates.
- No regressions: add (with valid date), mark done, delete still all work.
11.3 What if there's a gap?
If you find a behaviour your criteria don't cover, add the criterion to the spec, then verify. If the spec missed it, future you (or a teammate) would have missed it too.
If you find a bug, classify it. This is the habit sometimes called the SpecOps loop: treat every escaped bug as feedback on the harness — the spec and the checks around it — not only on the code. Module 9 goes deeper; the core is two categories:
- Spec-to-implementation gap — the spec was clear, the code diverged. Fix the code, and ask why neither your review nor
/speckit-convergecaught it. - Intent-to-spec gap — the spec was incomplete. Fix the spec first, then the code. Converge can't catch these: it measures code against artifacts, and here the artifacts are what's wrong.
The first kind means your validation is too loose; the second, that your spec-writing is missing something. Both are useful signals.
12.Decide how specs will age (5 min)
The feature works. One question remains that the tooling deliberately leaves to you: what happens to specs/001-due-date/ when requirements change next month? It's a team convention, not a CLI setting — exactly what the constitution is for. Pick one of the persistence models from Module 1:
- Flow-back — edit whichever artifact (or the code) comes first, then reconcile the rest by hand. Risk: silent divergence.
- Flow-forward — finished feature directories are records; a new requirement gets a new directory. Risk: scattered context.
- Living spec — edit
spec.mdfirst, then regenerate the plan and tasks. Risk: losing the reasoning in regenerated plans.
Then record your choice:
/speckit-constitution Add a spec lifecycle rule: <the model you chose, and
how this project applies it>.
## Spec lifecycle
- Model: flow-forward. A feature directory under specs/ can be edited
until its change merges; after that it is a record and is not edited.
- A later change to the same behaviour gets a new feature directory, and
its spec names the directory it builds on or supersedes.
- specs/ describes changes, not the whole system. Behaviour that predates
specs/001-due-date is documented by the code and the README.
Why flow-forward here. Every spec in this repo is a change on top of behaviour nobody specified, so a chain of small change records matches reality; the bonus exercise's cruft fix gets its own directory instead of editing this one. The cost is scattered context, which the linking rule pays down.
When you'd choose differently. Living spec, if you want one current contract per capability and will regenerate plans (keep key decisions somewhere durable, such as an ADR). Flow-back, for a small team that reliably reconciles artifacts at review. None is wrong; leaving the choice unrecorded is.
13.Reflection (10 min)
Same format as Module 3 — write REFLECTION.md answers honestly, don't share unless you want to.
- The constitution-from-existing-code activity — was it harder or easier than you expected? What did you almost include that wasn't actually true of the code?
- The cruft list — did you find the three pieces planted in the codebase? Did you find others that turned out to be conventions on closer inspection?
- How tempted were you to spec the entire app? How did you push back against that temptation?
- The agent's first-draft spec — what did it over-include? What did
/speckit-clarifyask that you hadn't thought about? - Did
/speckit-analyzeor/speckit-convergefind anything your own review had missed? What does that say about where your attention went? - The first time you had to choose between “fix the code” and “fix the spec” — what was the situation, and which did you pick? Why?
- What would have happened to this feature if you'd skipped the spec entirely and just told the agent “add a due date field”? What problems would you have caught at code review instead of spec review?
- What's the smallest change to your team's current workflow that would let you do this kind of brownfield SDD on a real ticket next week?
The last one is the most important. SDD is only worth learning if it changes what you do at work. Keep your answer: Module 9 closes with discussion prompts that ask the same question at team scale.
14.Bonus exercise (optional, 30 min)
If you finished early and want to push deeper:
Pick one piece of cruft from the codebase (the magic "todos" string, the leftover console.log, or the Date.now() ID generation) and write a small spec to fix it, following the lifecycle rule you just recorded. Cruft fixes are scope creep during feature work, but they become tractable when you spec them as their own focused changes.
Recommendation: start with Date.now() → crypto.randomUUID(). It has the most concrete benefit (collision safety) and forces you to think about whether existing IDs in localStorage need migration. (Spoiler: they don't, because IDs are opaque strings — only new IDs need the new generator. Say so in the spec's compatibility limits.)
Run it through the same loop, closing with the /speckit-converge check you met in Modules 1 and 3. This is how a brownfield codebase gains spec coverage without anyone writing a system-wide spec: each change adds a small one, frequently touched areas accumulate coverage, and over time the codebase starts behaving more like a greenfield project — not because you specced the whole thing, but because you specced each change.
What you should now know
- Brownfield SDD is incremental, not comprehensive. You spec the change, not the system — including what must not change.
- Start from a reviewable baseline. Initialization adds only
.specify/and your agent's command files and infers no specs; the diff proves it. - The constitution is extracted from existing code, not imposed on it. Only rules that are true today or explicitly agreed belong there, and naming cruft separately stops agents preserving it.
- Clarify, analyze, and converge catch different gaps, but only you can check the artifacts against intent. Skipping validation turns SDD back into vibe coding with extra ceremony.
- How specs age is a team decision. Record it in the constitution before the second feature arrives.
What's next
In Module 5 you step back to the question that comes before any spec: should this be built at all? You'll run Spec Kit's assess extension on an idea for the same to-do app — five stages, from intake to a recorded verdict of go, needs-clarification, or kill — and see how a go hands off to /speckit-specify.