Worked Example: Codex + Spec Kit
Spec-Driven Development explains the five commands. This page is the other kind of explanation — one feature, run for real, so you can see exactly what appears on screen at each step before you try it yourself. It uses Codex CLI ($speckit-*); everything here works identically in Claude Code with /speckit-* instead.
What we’re building
Section titled “What we’re building”A real page in the app itself — not a separate script. A new OSINT Check entry in the sidebar takes you to a page with a text box: type a username, click Check, and the page shows whether that username has a public profile on GitHub, Reddit, and Hacker News.
Under the hood that’s one new backend endpoint (behind the same login every other route already requires, making one plain GET request per platform to that platform’s public profile URL) and one new frontend page that calls it. No new database table — nothing about a check gets saved, it’s a live lookup every time. This is a deliberately small first feature: one endpoint, one page, but it exercises the same browser → API → UI shape as almost everything else you’ll build on this template.
1. Open a terminal and start Codex
Section titled “1. Open a terminal and start Codex”codexAssume /speckit-constitution has already been run once for this repo (see Spec-Driven Development if not) — you only need to do that once per project, not once per feature.
2. Describe the feature
Section titled “2. Describe the feature”$speckit-specify Add an "OSINT Check" page to the dashboard. A logged-inuser types a username and clicks Check. The page calls a new backendendpoint that checks whether that username has a public profile onGitHub, Reddit, and Hacker News (one plain, unauthenticated GET requestper platform to that platform's profile URL — no scraping behind login),and shows FOUND/NOT FOUND per platform. Nothing gets saved to thedatabase — every check is live.Codex creates a new branch — something like 004-osint-username-checker — and writes specs/004-osint-username-checker/spec.md. Trimmed down, it looks like this:
# Feature Specification: OSINT Username Checker
**Feature Branch**: `004-osint-username-checker`**Status**: Draft
## User Scenarios & Testing
### User Story 1 - Check a username from the dashboard (Priority: P1)
A logged-in user opens the OSINT Check page, types a username, andclicks Check. The page shows whether that username has a public profileon GitHub, Reddit, and Hacker News.
**Independent Test**: Log in, go to the OSINT Check page, enter aknown-existing username, click Check, confirm the page shows FOUND forthe platforms where that username is real.
**Acceptance Scenarios**:
1. **Given** a logged-in user on the OSINT Check page, **When** they enter a username that exists on all three platforms and click Check, **Then** the page shows FOUND next to each platform.2. **Given** a logged-in user, **When** they enter a username that exists nowhere and click Check, **Then** the page shows NOT FOUND next to each platform, with no error.
### Edge Cases
- What happens when a platform's request times out?- What does the page show while a check is in progress?- What happens if Check is clicked with an empty username field?
## Requirements
### Functional Requirements
- **FR-001**: System MUST provide an authenticated API endpoint that checks a single username against GitHub, Reddit, and Hacker News public profile URLs.- **FR-002**: System MUST provide a page, reachable from the sidebar, with a text input and a Check button.- **FR-003**: System MUST display a FOUND/NOT FOUND result per platform after a check completes.- **FR-004**: System MUST show a loading state while a check is in progress, and disable the Check button until it completes.
## Success Criteria
- **SC-001**: A logged-out user cannot reach the check endpoint directly.- **SC-002**: A real, existing username correctly shows FOUND for that platform.- **SC-003**: A full check (all three platforms) completes in under 10 seconds under normal network conditions.
## Assumptions
- No results are stored — every check is live, nothing persisted to the database.- Public profile pages only, no authentication or API keys against the checked platforms.3. Read the spec before moving on
Section titled “3. Read the spec before moving on”This is the step it’s easiest to skip and shouldn’t be. Read it looking specifically for scope that crept in on its own — here, a wrong version might have invented “save your check history” or “check 15 platforms,” neither of which was asked for. This one matches what was described: one endpoint, one page, three platforms, nothing saved. If it didn’t match, you’d fix the wording and run $speckit-specify again rather than let a wrong assumption ride into the plan.
4. Plan how to build it
Section titled “4. Plan how to build it”$speckit-plan Backend: a new GET /api/v1/osint/check/{username} endpointin app/api/routes/osint.py, behind the existing CurrentUser authdependency, using httpx (already a dependency) with a 5-second timeoutper platform. Response models go in app/models.py, following the samepattern as Message — plain SQLModel classes, no table=True, sincenothing gets persisted. Frontend: a new authenticated route at /osint(frontend/src/routes/_layout/osint.tsx) with a text input and a Checkbutton, calling the generated OsintService via TanStack Query. Add an"OSINT Check" entry to the sidebar infrontend/src/components/Sidebar/AppSidebar.tsx.This writes specs/004-osint-username-checker/plan.md — technical approach and file layout. There’s no data-model.md, because there’s no database entity involved, just response schemas.
5. Break it into tasks
Section titled “5. Break it into tasks”$speckit-tasksThis writes tasks.md, organized around the one user story from the spec. A real one looks roughly like:
## Phase 1: Setup- [ ] T001 Create app/api/routes/osint.py with an APIRouter (prefix "/osint", tag "osint")
## Phase 2: Foundational- [ ] T002 Add OsintPlatformResult and OsintCheckResponse response models to app/models.py (plain SQLModel, no table=True)- [ ] T003 Register osint.router in app/api/main.py
## Phase 3: User Story 1 - Check a username from the dashboard (P1)- [ ] T004 [P] Implement check_platform(username, url_template) in osint.py using httpx, with a 5-second timeout, returning a bool- [ ] T005 Implement GET /check/{username} behind CurrentUser, calling check_platform for GitHub, Reddit, and Hacker News- [ ] T006 Regenerate the frontend client: bash scripts/generate-client.sh- [ ] T007 [P] Create frontend/src/routes/_layout/osint.tsx with a text input, Check button, loading state, and a FOUND/NOT FOUND result list wired to OsintService via TanStack Query- [ ] T008 [P] Add "OSINT Check" to frontend/src/components/Sidebar/AppSidebar.tsx
**Checkpoint**: Logged in, the OSINT Check page lets you type ausername, click Check, and see FOUND/NOT FOUND per platformNotice tasks map directly back to the functional requirements in the spec — that traceability is the point of running these in order instead of jumping straight to implement. T006 is the easy one to forget by hand: the frontend can’t call an endpoint it doesn’t know exists yet, so the client has to be regenerated between writing the backend route and writing the page that calls it.
6. Build it
Section titled “6. Build it”$speckit-implementCodex works through tasks.md top to bottom — backend route, then the client regeneration, then the frontend page and sidebar entry — checking off each task ([ ] → [x]) as it completes it. Watch this one run rather than walking away, especially the first few times: it’s the fastest way to build a sense for what “normal” looks like, so you notice when a future run does something odd.
7. Try it
Section titled “7. Try it”Both dev servers from Getting Started should still be running. Refresh the app in your browser, log in if you’ve been logged out, and you should see a new OSINT Check entry in the sidebar. Click it, type in a username, and hit Check.
(octocat is GitHub’s own mascot/test account — a safe, real username to try first.) Expect something like:
GitHub: FOUNDReddit: NOT FOUNDHackerNews: NOT FOUND8. Read the diff
Section titled “8. Read the diff”Same “explain it back” rule as any other AI-generated code — a few concrete things worth checking on this one before you trust it:
- Hit the endpoint directly while logged out (or with an expired token) — does it actually reject you, or did the auth dependency get left off?
- Does the button disable (or show a spinner) while a check is running, or can you fire off five requests by clicking fast?
- Does a slow/unreachable platform hang the whole page, or does the 5-second timeout actually apply?
- Does submitting an empty username show a sensible message instead of a raw error?
9. Merge it
Section titled “9. Merge it”git add -Agit commit -m "Add OSINT username checker"git checkout maingit merge 004-osint-username-checkerIf you used a git worktree for this feature, remove it now too: git worktree remove ../osint-username-checker.
Why this was a good first feature
Section titled “Why this was a good first feature”One backend endpoint behind auth, one frontend page, no new database table — small enough to read start to finish in one sitting. But it’s still a real full-stack feature: browser → API → response → UI, the same shape as nearly everything else you’ll build here. That’s the rep worth doing before you point this loop at something bigger.