Skip to content

SRS Walkthrough ​

End-to-end tutorial : enable SRS on a project, draft an Epic from scratch, spawn the Stories. Takes about 15 minutes if you already have a Notion integration set up.

New to the model? Start with One source of truth for product requirements to understand what remains in Notion, what the agent may propose, and where explicit approval is required.

What you'll need ​

  • A SaaSFoundryAI project (generated by sf new) or an existing project where .saasfoundry.json lives
  • A Notion workspace you can create pages in
  • A Notion integration token (see sf-tool-notion for the one-time setup)
  • A parent page in Notion that you've explicitly shared with the integration — this is where your SRS root will live

Why a dedicated parent page?

The Notion integration can only see pages it's been shared with. Creating a dedicated "SRS root" page (shared once) is cleaner than sharing the whole workspace — it scopes the integration's reach to exactly the area you want under version.

1. Enable SRS on the project ​

If you're at sf new time, pass the flags :

bash
sf new --non-interactive \
  --project-name tutorial-saas \
  --structure monorepo \
  --setup-repo local \
  --db-setup docker \
  --db-type postgresql \
  --email-service none \
  --no-analytics \
  --advanced-skills notion \
  --srs-enable \
  --srs-backend notion \
  --srs-parent-page-input "https://www.notion.so/your-workspace/SRS-root-abc123"

On an existing project :

bash
sf update --add-modules srs \
  --srs-backend notion \
  --srs-parent-page-input "https://www.notion.so/your-workspace/SRS-root-abc123" \
  --notion-api-token "secret_..."

Either path writes tools.srs into .saasfoundry.json and installs the sf-srs + sf-tool-notion skills under .claude/skills/.

Verify :

bash
jq '.tools.srs' .saasfoundry.json

Expected :

jsonc
{
  "enabled": true,
  "backend": "notion",
  "rootPage": {
    "id": "abc123-...",
    "url": "https://www.notion.so/your-workspace/SRS-root-abc123",
    "name": "SRS root"
  }
}

Smoke-test the adapter :

bash
.claude/skills/sf-srs/scripts/srs-cli.sh validate
# → exit 0 : adapter.init() succeeded

2. Create the drafting ticket ​

SRS work is always driven by a GitHub ticket — that's how the lifecycle transitions get audited. Create one with the srs:new label :

bash
gh issue create \
  --title "Epic — Authentication & session management" \
  --body "First cut at the auth SRS. Will drive the signup / login / SSO feature tree." \
  --label "srs:new,complexity: medium"

Note the ticket number that comes back — this example assumes #42.

Drive it through the standard early phases :

bash
.claude/skills/sf-workflow/workflow-cli.sh update-status 42 "Ready"
.claude/skills/sf-workflow/workflow-cli.sh update-status 42 "In progress"

3. Brainstorm in conversation ​

In your Claude Code session, open the ticket and brainstorm with Claude :

Let's draft an Epic for Authentication. I want three FRs : password login, SSO (Google + GitHub), and session refresh. Each needs URs describing what the user sees, DS for the implementation, and TCs for acceptance. No existing notes — draft from scratch.

Claude reads the brainstorm, composes a DraftCandidate[] list (one Epic + N FR specs), and shows it back for approval before writing anything to Notion.

4. Run the drafter ​

Once the spec reads right :

bash
.claude/skills/sf-workflow/workflow-cli.sh transition-drafting 42 ai-draft

Claude then saves the DraftCandidate[] to a temp JSON file and runs :

bash
.claude/skills/sf-srs/scripts/srs-cli.sh write --spec /tmp/draft-42.json

On success :

  • An Epic page Authentication & session management is created under the SRS root
  • Three FR child pages land under the Epic (FR-001 Password login, FR-002 SSO, FR-003 Session refresh)
  • Each FR page ships with its canonical sections : User Requirements · Functional Requirements · Design · Test Cases
  • The traceability table on the Epic page lists all FRs
  • Exit 0 — tools.srs.pendingIngestion is cleared if it was set

Open the Epic page in Notion — this is your single source of truth from here on.

5. Human review ​

bash
.claude/skills/sf-workflow/workflow-cli.sh transition-drafting 42 human-review

Go through each FR page :

  • Does the User Requirement match what a product reviewer would write ?
  • Do the Design blocks reflect your actual implementation plan (libraries, algorithms, boundaries) ?
  • Are the Test Cases specific enough to be runnable tests ?

Edit directly in Notion. The drafter is not re-run for small tightenings — you own the page now.

Tip : comment on the ticket as you review so the audit trail captures what changed between the ai-draft output and the approved page.

6. Spawn the Stories ​

Once the page tree is approved :

bash
.claude/skills/sf-workflow/workflow-cli.sh transition-drafting 42 spawning

Claude runs :

bash
.claude/skills/sf-srs/scripts/srs-cli.sh spawn --ticket 42 --epic "https://www.notion.so/your-workspace/Authentication-..."

This enumerates the three FR pages and creates three GitHub sub-issues under ticket #42 :

  • #43 [Parent #42] FR-001: Password login · label srs:new · status Backlog
  • #44 [Parent #42] FR-002: SSO (Google + GitHub) · label srs:new · status Backlog
  • #45 [Parent #42] FR-003: Session refresh · label srs:new · status Backlog

Use --dry-run first if you want to preview without writing :

bash
.claude/skills/sf-srs/scripts/srs-cli.sh spawn --ticket 42 --epic "..." --dry-run

Each spawned ticket's body is rendered from renderStoryTicketBody : a link back to the FR page, the acceptance criteria pulled from the TC blocks, and the traceability chain UR → FR → DS → TC.

7. Close the drafter ​

bash
.claude/skills/sf-workflow/workflow-cli.sh transition-drafting 42 done

Ticket #42 closes on GitHub. The three spawned Stories stay open — they'll flow through the normal code-path workflow from here, each independently picked up, implemented, tested, reviewed, merged.

8. Evolve the spec as you code ​

Later, during implementation of (say) #43 Password login, you realise a password policy detail wasn't in the SRS. You mention it in conversation :

Passwords should accept any unicode character — we saw a bug with emoji passwords last week.

Claude recognises this as an acceptance condition (TC) on FR-001 and proposes a diff :

💡 Ce que tu viens de décrire ressemble à TC. Proposition :
  • FR-001 Password login — add TC-007 : "passwords containing unicode/emoji characters are accepted without error"

J'applique via `srs-cli.sh apply-update` ? [accept / edit / reject]

On accept, Claude calls :

bash
echo '{
  "kind": "add-tc",
  "pageId": "<FR-001 page id>",
  "item": {
    "id": "TC-007",
    "title": "passwords containing unicode/emoji characters are accepted without error"
  }
}' | .claude/skills/sf-srs/scripts/srs-cli.sh apply-update

The FR-001 page now has a new "Added Test Case — TC-007" block appended at the end. Reviewers fold it back into the canonical "Test Cases" section during the next SRS review.

Drafting from an existing codebase ​

If the project already has code (controllers, Prisma models, React pages, specs, docs) and Notion is empty or stale, skip the brainstorm in section 3 and let the scanners propose the first draft from the source tree.

bash
.claude/skills/sf-srs/scripts/srs-cli.sh draft --from codebase
# or point at a specific repo :
.claude/skills/sf-srs/scripts/srs-cli.sh draft --from codebase --path /path/to/repo

The CLI walks the tree (respecting .gitignore) and emits a JSON envelope :

jsonc
{
  "source": "codebase",
  "findings": [
    { "kind": "endpoint", "area": "auth", "method": "POST", "path": "/auth/signin", "hasTests": true, "file": "api/src/modules/auth/auth.controller.ts" },
    {
      "kind": "entity",
      "area": "auth",
      "model": "Session",
      "fields": [
        /* … */
      ],
      "relations": [{ "field": "user", "target": "User" }],
      "file": "api/prisma/schema/auth.prisma"
    },
    { "kind": "ui-flow", "area": "auth", "title": "SignInPage (public/SignInPage)", "route": "/signin", "linkedEndpointGuess": "signin", "file": "web/src/pages/public/SignInPage.tsx" },
    {
      "kind": "test",
      "area": "auth",
      "describe": "AuthService",
      "cases": ["hashes passwords with bcrypt", "rejects expired refresh tokens"],
      "file": "api/src/modules/auth/tests/unit/auth.service.spec.ts"
    },
    { "kind": "doc-context", "area": "project", "heading": "Authentication", "excerpt": "Email + password sign-in with refresh tokens.", "file": "docs/auth.md" }
    /* … */
  ]
}

Five scanner kinds fire today — see Scanner findings reference for the full JSON shape of each.

Claude then drives a review loop one domain at a time. The scanner output groups findings by area, so the conversation reads like :

🔍 Findings cluster for `auth` — 6 endpoints, 2 entities, 2 specs, 1 page, 1 doc block.

📘 Epic proposal : Authentication & session management
   ├─ FR-001 Password sign-in   (POST /auth/signin + SignInPage + auth.service.spec.ts)
   ├─ FR-002 Sign-up            (POST /auth/signup + SignUpPage)
   └─ FR-003 Session refresh    (GET /auth/me + Session model)

Accept this Epic structure, or tighten the FR split first? [accept / edit / reject / skip-area]

Before asking for accept/edit, Claude emits a five-category coverage table so the reviewer sees what got seeded per section and where it came from — UR/FR come from endpoints and docs, DS comes from entities and API contracts, TC comes from test.cases[] (plus TODO items for endpoints without tests), NFR is proposed from stack signals and always marked proposed — needs human validation. See Scanner findings reference → Five-category section seeding for the full mapping.

On accept, Claude serialises the cluster as DraftCandidate[] and calls srs-cli.sh write --spec <tmp.json> exactly like the green-field flow in section 4. You can drive multiple Epics in a row, one prompt per cluster — the reviewer always gets a chance to course-correct before any Notion write happens.

Use this flow when :

  • You want to bootstrap SRS on a mature project and the codebase is the truth of record
  • Your Notion SRS has drifted and you want a fresh baseline from the code
  • You want to audit coverage gaps (endpoints without tests, pages without linked endpoints — the scanner marks both)

Troubleshooting ​

srs-cli.sh validate exits with code 5 ​

Network error during adapter.init(). Check :

  • NOTION_API_TOKEN is set and not a placeholder
  • The parent page in tools.srs.rootPage.url is explicitly shared with the integration (Notion's permission model is opt-in)
  • Outbound HTTPS to api.notion.com is allowed

Spawning fails with "Rule 8" error on create-subtask ​

This is the guard — you're trying to create a Story that doesn't come from the spawner. Either :

  • Run srs-cli.sh spawn --ticket <parent> --epic <url> (the spawner passes --bypass-srs spawned-from-srs automatically)
  • Or, for genuinely meta tickets (SRS tooling, drafter refactors), pass --bypass-srs <your-reason> to create-subtask yourself

The Traceability table on the Epic page is out of date ​

Known limitation in v1 — adapter.updatePage is append-only on Notion, so the conversational eval hook can't refresh the table in place. When you add an FR through apply-update, the new FR page is created but the Epic's Traceability table isn't edited. Refresh it manually during the next human review, or wait for the replace / delete adapter extension on the roadmap.

What's next ​

Released under the MIT License.