prd-to-backlog v0.1.0 · TypeScript CLI and HTTP API

Turn a markdown PRD into a backlog you can trace back to every line.

Epics, user stories, Given/When/Then acceptance criteria, estimates, dependencies and risks, each citing the PRD lines it came from. A linter scores the result, and exporters write GitHub Issues, Jira CSV and Linear CSV. The default generator is deterministic and runs offline; Anthropic and OpenAI adapters are optional.

  • 100%requirements traced to a story
  • 33/33labelled requirements extracted
  • 94-97lint score out of 100

Measured with npm run eval on the 3 bundled synthetic PRDs, heuristic generator, no API keys.

See a PRD next to its backlog View on GitHub

Heuristic generator on the bundled synthetic PRDs npm run eval, no API keys. Percentages, lint score out of 100. 0 25 50 75 100 Team Invites Team Invites: Extraction recall 100 100 Team Invites: Capability coverage 100 100 Team Invites: Traceability 100 100 Team Invites: Lint score 97 97 Offline Mode for the Field Inspections App Offline Mode for the Field Inspections App: Extraction recall 100 100 Offline Mode for the Field Inspections App: Capability coverage 92 92 Offline Mode for the Field Inspections App: Traceability 100 100 Offline Mode for the Field Inspections App: Lint score 94 94 Usage-Based Billing Usage-Based Billing: Extraction recall 100 100 Usage-Based Billing: Capability coverage 100 100 Usage-Based Billing: Traceability 100 100 Usage-Based Billing: Lint score 96 96 Extraction recall Capability coverage Traceability Lint score

A PRD next to its generated backlog

Hover or focus a story to highlight the PRD lines it came from; click a highlighted PRD line to jump to its story. Criteria in italics were inferred because the PRD gave no testable detail, and the linter flags them for review. Everything below is generated from the PRD on the left by npm run site.

4 epics · 12 stories · 23 pts · 8 dependencies · 3 risks · lint 97/100 (A) · traceability 100% · 7 warnings

PRD examples/team-invites.md (synthetic)

1# Team Invites2 3> SYNTHETIC sample PRD written for prd-to-backlog demos and evals. It does not describe a real4> product or company.5 6## Problem7 8New workspaces stall after sign-up because the creator cannot easily bring teammates in.9Today invites are sent manually by support, which takes up to two business days.10 11## Goals12 13- Let workspace owners add their team without contacting support.14- Reduce time from workspace creation to first collaborator joining to under 1 day.15- Keep workspace membership auditable for security reviews.16 17## Users18 19- **Workspace admin**: owns the workspace, manages members and billing seats.20- **Invitee**: a teammate who receives an invite and may not have an account yet.21- **Security reviewer**: audits who was granted access and when.22 23## Requirements24 25### Sending invites26 27- Workspace admins can invite teammates by entering one or more email addresses.28  - When an admin submits up to 20 valid addresses, then each address receives an invite email within 2 minutes.29  - If an address is malformed, then the form shows an inline error and no invite is sent for it.30  - If the address already belongs to a member, then the admin sees "already a member".31- Workspace admins can set the role (Admin or Member) for each invite.32  - Given a role is selected, when the invite is accepted, then the new member has that role.33- Workspace admins can resend a pending invite.34  - Resending generates a new link and invalidates the previous one.35 36### Accepting invites37 38- Invitees can accept an invite from the email link and join the workspace.39  - When an invitee with an existing account opens a valid link, then they join the workspace after one confirmation click.40  - When an invitee without an account opens a valid link, then they are taken to sign-up with the email prefilled.41- Invite links must expire after 7 days.42  - If an invitee opens an expired link, then they see an expiry message and a button to request a new invite.43- The invite flow should be fast and easy for invitees.44 45### Managing invites46 47- Workspace admins can view all pending invites with sender, role and sent date.48- Workspace admins can revoke a pending invite.49  - When an admin revokes an invite, then its link stops working immediately.50- The system must record an audit log entry for every invite sent, accepted, resent or revoked.51  - Each entry includes actor, target email, action and timestamp in UTC.52 53## Non-functional requirements54 55- Invite emails must be sent through the existing transactional email provider.56- The system must rate-limit invites to 200 per workspace per day.57- Invite tokens must be single-use and at least 128 bits of entropy.58 59## Out of scope60 61- Inviting users via SCIM or directory sync.62- Bulk CSV upload of invitees.63- Custom invite email branding.64 65## Risks66 67- Invite emails may land in spam folders, lowering acceptance rate.68- Rate limits that are too strict could block legitimate onboarding for large teams.69 70## Open questions71 72- Should Members (not only Admins) be allowed to send invites?

Generated backlog heuristic generator

EP-2f525c Sending invites

ST-456a6dshould5 pts

Invite teammates by entering one or more email addresses

As a workspace admin, I want to invite teammates by entering one or more email addresses, so that it supports the goal "Let workspace owners add their team without contacting support".

  • Given a workspace admin, when an admin submits up to 20 valid addresses, then each address receives an invite email within 2 minutes.
  • Given a workspace admin, when an address is malformed, then the form shows an inline error and no invite is sent for it.
  • Given a workspace admin, when the address already belongs to a member, then the admin sees "already a member".

Traces to examples/team-invites.md:27-30

ST-b5ddb7should1 pt

Set the role (Admin or Member) for each invite

As a workspace admin, I want to set the role (Admin or Member) for each invite, so that it supports the goal "Keep workspace membership auditable for security reviews".

  • Given a role is selected, when the invite is accepted, then the new member has that role.

Traces to examples/team-invites.md:31-32

ST-cb792fshould1 pt

Resend a pending invite

As a workspace admin, I want to resend a pending invite, so that it supports the goal "Keep workspace membership auditable for security reviews".

  • Given a workspace admin, when the workspace admin resends a pending invite, then resending generates a new link and invalidates the previous one.

Traces to examples/team-invites.md:33-34 · blocked by ST-456a6d (inferred)

EP-8bb632 Accepting invites

ST-c1b247should2 pts

Accept an invite from the email link and join the workspace

As an invitee, I want to accept an invite from the email link and join the workspace, so that it supports the goal "Let workspace owners add their team without contacting support".

  • Given an invitee, when an invitee with an existing account opens a valid link, then they join the workspace after one confirmation click.
  • Given an invitee, when an invitee without an account opens a valid link, then they are taken to sign-up with the email prefilled.

Traces to examples/team-invites.md:38-40 · blocked by ST-456a6d (inferred)

ST-2ab69fmust1 pt

Invite links must expire after 7 days

As a workspace admin, I want invite links to expire after 7 days, so that it supports the goal "Let workspace owners add their team without contacting support".

  • Given a workspace admin, when an invitee opens an expired link, then they see an expiry message and a button to request a new invite.

Traces to examples/team-invites.md:41-42 · blocked by ST-456a6d (inferred)

ST-b7f7fashould1 pt

The invite flow should be fast and easy for invitees

As an invitee, I want the invite flow to be fast and easy for invitees, so that it supports the goal "Let workspace owners add their team without contacting support".

  • Given an invitee, when the behaviour is exercised, then the invite flow should be fast and easy for invitees.

Traces to examples/team-invites.md:43 · blocked by ST-456a6d (inferred)

untestable-criterion: Then-clause uses subjective terms (fast, easy): "the invite flow should be fast and easy for invitees". State a measurable or visible result.

inferred-criterion: All criteria were inferred; the PRD gives no testable detail for this story.

vague-language: Vague wording: "fast", "easy". Replace with a measurable target.

EP-2f8b9d Managing invites

ST-51ea02should1 pt

View all pending invites with sender, role and sent date

As a workspace admin, I want to view all pending invites with sender, role and sent date, so that it supports the goal "Keep workspace membership auditable for security reviews".

  • Given a workspace admin, when the workspace admin views all pending invites with sender, role and sent date, then workspace admins can view all pending invites with sender, role and sent date.

Traces to examples/team-invites.md:47 · blocked by ST-456a6d (inferred)

inferred-criterion: All criteria were inferred; the PRD gives no testable detail for this story.

ST-b39715should1 pt

Revoke a pending invite

As a workspace admin, I want to revoke a pending invite, so that it supports the goal "Keep workspace membership auditable for security reviews".

  • Given a workspace admin, when an admin revokes an invite, then its link stops working immediately.

Traces to examples/team-invites.md:48-49 · blocked by ST-456a6d (inferred)

ST-55acadmust2 pts

Record an audit log entry for every invite sent, accepted, resent or revoked

As a workspace admin, I want the system to record an audit log entry for every invite sent, accepted, resent or revoked, so that it supports the goal "Let workspace owners add their team without contacting support".

  • Given a workspace admin, when the system records an audit log entry for every invite sent, accepted, resent or revoked, then each entry includes actor, target email, action and timestamp in UTC.

Traces to examples/team-invites.md:50-51

EP-ea441d Team Invites: non-functional requirements

ST-9b69c7must3 pts

Invite emails must be sent through the existing transactional email provider

As a workspace admin, I want invite emails to be sent through the existing transactional email provider, so that it supports the goal "Let workspace owners add their team without contacting support".

  • Given a workspace admin, when the behaviour is exercised, then invite emails must be sent through the existing transactional email provider.

Traces to examples/team-invites.md:55 · blocked by ST-456a6d (inferred)

inferred-criterion: All criteria were inferred; the PRD gives no testable detail for this story.

ST-03f995must3 pts

Rate-limit invites to 200 per workspace per day

As a workspace admin, I want the system to rate-limit invites to 200 per workspace per day, so that it supports the goal "Reduce time from workspace creation to first collaborator joining to under 1 day".

  • Given a workspace admin, when the system rate-limits invites to 200 per workspace per day, then the system must rate-limit invites to 200 per workspace per day.

Traces to examples/team-invites.md:56

inferred-criterion: All criteria were inferred; the PRD gives no testable detail for this story.

ST-c74591must2 pts

Invite tokens must be single-use and at least 128 bits of entropy

As a workspace admin, I want invite tokens to be single-use and at least 128 bits of entropy, so that it supports the goal "Let workspace owners add their team without contacting support".

  • Given a workspace admin, when the behaviour is exercised, then invite tokens must be single-use and at least 128 bits of entropy.

Traces to examples/team-invites.md:57 · blocked by ST-456a6d (inferred)

inferred-criterion: All criteria were inferred; the PRD gives no testable detail for this story.

Dependency graph

ST-456a6d (5) Invite teammates by entering one or more email addresses ST-456a6d (5) Invite teammates by entering one or more email addresses ST-b5ddb7 (1) Set the role (Admin or Member) for each invite ST-b5ddb7 (1) Set the role (Admin or Member) for each invite ST-cb792f (1) Resend a pending invite ST-cb792f (1) Resend a pending invite ST-c1b247 (2) Accept an invite from the email link and join the workspace ST-c1b247 (2) Accept an invite from the email link and join the workspace ST-2ab69f (1) Invite links must expire after 7 days ST-2ab69f (1) Invite links must expire after 7 days ST-b7f7fa (1) The invite flow should be fast and easy for invitees ST-b7f7fa (1) The invite flow should be fast and easy for invitees ST-51ea02 (1) View all pending invites with sender, role and sent date ST-51ea02 (1) View all pending invites with sender, role and sent date ST-b39715 (1) Revoke a pending invite ST-b39715 (1) Revoke a pending invite ST-55acad (2) Record an audit log entry for every invite sent, accepted, resent or revoked ST-55acad (2) Record an audit log entry for every invite sent, accepted, resent or revoked ST-9b69c7 (3) Invite emails must be sent through the existing transactional email provider ST-9b69c7 (3) Invite emails must be sent through the existing transactional email provider ST-03f995 (3) Rate-limit invites to 200 per workspace per day ST-03f995 (3) Rate-limit invites to 200 per workspace per day ST-c74591 (2) Invite tokens must be single-use and at least 128 bits of entropy ST-c74591 (2) Invite tokens must be single-use and at least 128 bits of entropy

Sending invitesAccepting invitesManaging invitesTeam Invites: non-functional requirementssolid: stated in the PRD · dashed: inferred · red outline: in a cycle

Risks

IDRiskLikelihoodImpactMitigation
RISK-38ad73Invite emails may land in spam folders, lowering acceptance rate.mediummediumNot stated in PRD
RISK-e2b1e7Rate limits that are too strict could block legitimate onboarding for large teams.mediummediumNot stated in PRD
RISK-ef2de2Unresolved question: Should Members (not only Admins) be allowed to send invites?mediummediumResolve with the PRD owner before the related stories enter a sprint.

Download the committed outputs: backlog.json · review markdown · GitHub Issues · Jira CSV · Linear CSV · mermaid graph · lint report

Results

Heuristic generator on the 3 bundled synthetic PRDs (33 requirements, 33 stories, 83 points). LLM providers are not run here because the site build uses no API keys.

PRDExtraction recallCapability coverageTraceabilityLint scoreSchema validStories
Team Invites100%100%100%97yes12
Offline Mode for the Field Inspections App100%92%100%94yes10
Usage-Based Billing100%100%100%96yes11

How evaluation works

What the linter says about the PRDs

20 of 33 (61%) requirements had no testable detail in the PRD, so 20 of 36 (56%) acceptance criteria had to be inferred. For 31 of 33 (94%) stories the "so that" clause was linked to a PRD goal by the generator because the requirement did not state one. These are the PRD gaps the tool is built to surface, and where an LLM generator should beat the heuristic.

Quickstart

Requires Node 20 or later. No API keys.

git clone https://github.com/seanmcrae/prd-to-backlog.git && cd prd-to-backlog
npm ci && npm run build
node dist/bin.js generate examples/team-invites.md --out out/
node dist/bin.js lint out/backlog.json --min-score 90
node dist/bin.js export out/backlog.json --format jira --out out/jira.csv
CommandWhat it does
prd2backlog generate <prd.md> [--out dir] [--provider p]Writes backlog.json and a reviewable backlog.md
prd2backlog lint <backlog.json> [--format json] [--min-score n]Scored report; --min-score exits 1 below the threshold
prd2backlog export <backlog.json> --format <f>github, jira, linear, markdown, mermaid
prd2backlog serve [--port 8787]HTTP API: POST /generate, /lint, /export

Architecture

errors fed back PRD markdown PRD markdown markdown reader sections, lists, tables, line spans markdown reader sections, lists, tables, line spans requirement extraction stable IDs, priority, personas, goals, scope cuts requirement extraction stable IDs, priority, personas, goals, scope cuts BacklogGenerator BacklogGenerator heuristic offline, deterministic heuristic offline, deterministic LLM generator Anthropic / OpenAI LLM generator Anthropic / OpenAI zod validation + referential checks zod validation + referential checks backlog.json BacklogSchema backlog.json BacklogSchema linter INVEST, vague terms, coverage, cycles linter INVEST, vague terms, coverage, cycles exporters GitHub, Jira, Linear, markdown, mermaid exporters GitHub, Jira, Linear, markdown, mermaid score report score report
Drawn at build time from the Mermaid diagram in the README.

Design decisions

  • Requirements are parser-owned. Models generate epics, stories, dependencies and risks only. The requirement list, its IDs and its line numbers always come from the parser, so a model cannot invent or reword scope, and every story citation can be checked.
  • Content-addressed IDs. A requirement without an explicit PRD ID (FR-3) gets REQ-<sha1 of normalised text>. Inserting, deleting or reordering other requirements does not renumber it, so references in trackers stay valid across PRD revisions. Editing the requirement's own wording changes its ID, and that is intended: it is a different requirement.
  • A heuristic default that does real work. It reads the PRD structure, not keywords. Detail bullets become Given/When/Then criteria, headings become epics, "Must have" buckets set priority, and "Depends on FR-2" becomes an explicit edge. Create/consume verb pairs ("invite" then "revoke an invite") become inferred edges. Same input, byte-identical output, which is what makes snapshot tests and the committed samples possible.
  • Inference is labelled, never hidden. Criteria carry origin: prd | inferred. Benefits linked to a PRD goal carry a benefit-inferred label. Inferred dependencies are dotted in the graph. Estimates list every signal that moved the number. The linter turns these labels into review prompts instead of treating generated text as fact.
  • Repair loop over trust. LLM output goes through JSON extraction, zod validation and referential checks. Unknown requirement IDs, dangling dependencies and uncited stories are sent back to the model verbatim, for up to three attempts, after which it fails loudly.
  • A score you can argue with. Each story starts at 100 and loses 25 per error and 10 per warning. The backlog score is 60% mean story quality plus 40% traceability coverage, minus 10 per dependency cycle. Info findings cost nothing; they mark things a person should look at.
  • Own markdown reader instead of remark. PRDs use a small, predictable subset of markdown. A reader of under 300 lines with exact line spans, setext headings, nested lists, tables and front matter was simpler to make line-accurate than mapping mdast positions back through list nesting. It is covered by its own tests.

Product brief

Rendered from docs/PRODUCT.md.

Problem

Between "PRD approved" and "sprint planned" a PM or eng lead spends hours, sometimes days, re-typing the PRD into a tracker. The output is worse than the input, in four predictable ways:

  1. Dropped scope. A requirement in a sub-bullet or a prose paragraph never becomes a ticket. Nobody notices until QA or a customer does.
  2. Invented scope. Tickets show up that no requirement asked for. Sometimes they are good ideas, but they skip review because they look like they came from the PRD.
  3. Untestable acceptance criteria. "Should be fast and easy" goes straight from the PRD into the ticket. The disagreement about what "done" means is pushed into the sprint.
  4. Lost traceability. Once tickets exist, nobody can answer "which ticket covers PRD line 41?" or "what did the PRD say about this ticket?". When the PRD changes, nobody knows which tickets are now wrong.

The same translation also hides PRD quality problems. A requirement with no testable detail reads fine in a doc and only becomes visible when someone tries to write its acceptance criteria.

Users and jobs to be done

User Job What they need from the tool
Product manager (PRD author) "Turn my approved PRD into a first-draft backlog I can review in an hour, not build in a day." Complete coverage, stories in the team's format, the gaps in the PRD itself called out
Engineering lead "Know the backlog is buildable before planning: sized, sequenced, no circular dependencies." Estimates with visible reasoning, a dependency graph, cycle detection
Reviewer / QA "Check that every requirement has testable criteria and nothing extra slipped in." Requirement-to-story trace, inferred-content labels, an untraced-story check
Team running PRD reviews "Gate low-quality PRDs before they reach planning." A score with a CI exit code (lint --min-score)

Scope

In scope (v0.1, this repo)

  • Markdown PRDs: headings, nested bullets, tables, prose with modal verbs, front matter, "Acceptance notes" sections keyed by requirement ID.
  • Requirement extraction with stable IDs and source line spans. Personas, goals, out-of-scope items, risks, open questions and external dependencies are captured as context.
  • Backlog generation: deterministic heuristic (default, offline), plus Anthropic and OpenAI behind one interface, with schema validation and a repair loop.
  • Quality linting with a 0-100 score, and exports to GitHub Issues payloads, Jira CSV, Linear CSV, markdown and mermaid.
  • CLI, a small stateless HTTP API, and an offline eval harness.

Out of scope (deliberately)

  • Writing to trackers through their APIs. Exports are files a person imports after review. Pushing straight to Jira would skip the review step that the whole design depends on.
  • Google Docs, Confluence or Notion ingestion. Markdown export from those tools is good enough for v0.1, and connectors are an integration project, not a product question.
  • Sprint planning, capacity or velocity forecasting. Estimates are relative sizing inputs.
  • Multi-document PRDs and PRD diffing (see Roadmap).

Requirements

ID Requirement How it is met
R1 Every extracted requirement is traceable to PRD lines and keeps its ID when other requirements change Line spans on every block; content-addressed IDs or explicit PRD IDs (REQ-FR-3)
R2 Every requirement is covered by at least one story, or the gap is reported orphan-requirement rule; traceability coverage % in the score
R3 No story enters the backlog without citing a requirement untraced-story rule; LLM output with uncited stories is rejected and repaired
R4 Generated content is distinguishable from PRD content origin on criteria, benefit-inferred label, explicit vs inferred dependency kind
R5 Output is machine-validated before anyone sees it zod BacklogSchema plus referential checks in validateBacklog
R6 Works with no network access and no API keys Heuristic generator is the default; all tests run offline with faked HTTP for LLM adapters
R7 Same input, same output for the default path No timestamps or randomness; committed sample outputs are checked by a test
R8 Import into the trackers teams already use GitHub, Jira CSV (epic links and blockers by Issue Id), Linear CSV

Success metrics and evals

The tool measures itself on four axes. The first two are automated today; the last two need a pilot with real users and are listed so the instrumentation gets built with that in mind.

Metric Definition Current reading (bundled synthetic corpus, heuristic generator)
Extraction recall Share of hand-labelled requirement phrases found among extracted requirements 100% on all three PRDs (33 of 33)
Capability coverage Share of hand-written capabilities (keyword sets) present in at least one story 100%, 100%, 92%; the miss is an open question the generator turns into a risk
Traceability coverage Requirements with at least one story / all requirements 100% on all three
Lint score 0.6 x mean story score + 0.4 x traceability %, minus 10 per dependency cycle 97 (team invites), 96 (billing), 94 (offline mode)
Schema validity Output passes validateBacklog 3 of 3
PM edit distance / acceptance Share of generated stories accepted unchanged, edited, or deleted during review; token edit distance on edited ones Not measured yet; needs a review UI or a diff of backlog.json before and after review
Time saved Minutes from approved PRD to an import-ready backlog, against the team's manual baseline Not measured yet; pilot metric

All current readings come from running npm run eval and prd2backlog lint on the three synthetic PRDs in examples/. They show the pipeline is wired correctly and the heuristic handles well-structured PRDs. They do not show performance on messy real-world PRDs, and the lint score partly grades the heuristic's own phrasing.

The more useful reading is what the linter says about the inputs. Across the corpus, 20 of 36 acceptance criteria (56%) had to be inferred, because 20 of 33 requirements had no testable detail in the PRD. For 31 of 33 stories the benefit clause was linked to a PRD goal by the generator, because the requirement did not state its own "so that". Those are the PRD-quality gaps this tool exists to surface, and they are also where an LLM generator should beat the heuristic. The eval harness is built to measure exactly that once API keys are available: same expectations, one row per generator.

Minimum viable quality

Release thresholds for a change to the parser, a generator or the linter. The "ship" column for the first five rows is what test/eval.test.ts already enforces on the bundled synthetic PRDs; the "delight" column needs the messy-PRD set and a verbatim-copy baseline that do not exist yet.

Metric Do not ship Ship Delight Measured today
Schema validity Any backlog that fails validateBacklog 3 of 3 Every PRD in a larger, messier set Yes
Extraction recall Below 100% on any bundled PRD 100% (33 of 33 today) 95% or higher on messy PRDs Yes, bundled only
Traceability coverage Below 100% 100% 100% after a PRD revision, with changed requirements reported by ID Yes
Capability coverage, per PRD Below 90% 90% or higher (92-100% today) 100%, and clearly above a verbatim-copy baseline Yes, but with no baseline (issue #6)
Lint score, per PRD Below 90 90 or higher (94-97 today) 95 or higher with no untestable-criterion warning surviving review Yes
Invented scope from an LLM generator Any untraced story reaching the output 0 untraced stories (the repair loop rejects them) 0 invented criteria on a precision check as well Untraced stories yes; criteria precision no (issue #7)
PM acceptance rate Not set Not set Not set No; the threshold should come from the first pilot, not be guessed here

Cost at 1x and 10x usage

Estimates only, from what the repo itself defines. It defines no traffic level, so 1x is set at 100 PRDs a month for illustration.

Usage Heuristic generator (default) Anthropic generator: output-token ceiling OpenAI generator
1x: 100 PRDs a month No model calls; local compute only 4.8M output tokens a month No ceiling
10x: 1,000 PRDs a month Same 48M output tokens a month No ceiling

The Anthropic ceiling is the adapter's max_tokens of 16,000 per call times the default three validation-and-repair attempts, so 48,000 output tokens per PRD at worst. Each repair attempt also resends the PRD and schema as input, which the repo does not measure. The OpenAI adapter sets no output cap, so it has no ceiling to estimate. The repo holds no vendor prices and no measured token counts, so none of this is converted to dollars; issue #7 tracks recording tokens, latency and repair attempts per case. The heuristic path costs the same at 1x and 10x because it never calls a model, and the HTTP API is stateless, so it scales with plain compute.

Trade-offs and alternatives considered

Heuristic vs LLM generation. The heuristic is free, instant, private and deterministic, and it never invents scope. But it writes one story per requirement, cannot split or merge, and leans on the PRD's own wording. An LLM writes better benefits and criteria and can slice stories, but it costs money per run, varies between runs and can hallucinate. The decision was to keep both behind one interface, make the heuristic the default, and hold the LLM to the same contract through validation and repair. Teams can start without sending a PRD to a vendor and switch on a model when the eval shows it earns its cost.

Human review as a step, not an option. The obvious feature is "push to Jira". It was cut on purpose. The output is a reviewable backlog.md plus import files, and inferred content is labelled so the reviewer knows where to look. The linter's info findings exist to direct review time, not to block anything.

Own markdown reader vs remark/unified. Remark is more complete, but the product needs exact line spans for list items nested in list items and for table rows, plus a few PRD-specific conventions. A small reader with focused tests was cheaper than mapping mdast positions, and it has no runtime dependencies.

Content-hash IDs vs sequential IDs. Sequential IDs (REQ-1, REQ-2) are friendlier but renumber on every insertion, which breaks every downstream reference. Hash IDs are ugly but stable. Explicit IDs written in the PRD always win, so teams that number requirements keep their numbering.

One composite score vs a checklist. A single number is easy to game and hides detail, but it is what makes the --min-score CI gate and generator comparison possible. The report always shows the components (story quality, coverage, cycles) and every finding, so the number is never the only output.

Risks

Risk Likelihood Impact Mitigation
Hallucinated scope from LLM generators: stories or criteria the PRD never asked for High High Requirements are parser-owned; stories must cite known IDs (enforced in the repair loop); untraced-story and out-of-scope-overlap rules
Reviewers rubber-stamp generated stories because they look polished Medium High Inferred content is labelled throughout; stories with only inferred criteria are warnings, not info; review is a required step (no direct push)
Heuristic phrasing degrades on PRDs that do not follow common structure High Medium Extraction warnings when nothing looks like a requirement; awkward phrasings fall back to literal text the linter flags; eval set to grow with messy PRDs
Score inflation: teams tune PRDs to the linter rather than to clarity Medium Medium Score components shown separately; vague-term list is conservative; score positioned as a review aid and CI floor, not a target
Tracker import formats drift Medium Low Exporters are small and snapshot-tested; README tells users to confirm column mapping on first import
Sending confidential PRDs to model vendors Medium High Offline default; LLM providers only run when explicitly selected and keyed

Roadmap

Now (v0.1, shipped in this repo)

  • Markdown ingestion with traceable, stable requirement IDs.
  • Heuristic, Anthropic and OpenAI generators behind one validated contract.
  • Linter with score and CI gate; GitHub, Jira, Linear, markdown and mermaid exports.
  • Offline eval on three synthetic PRDs.

Next

  • Run the eval against both LLM providers and publish the comparison, including cost and latency per PRD.
  • Grow the eval set with deliberately messy PRDs (prose-only, mixed numbering, requirements hidden in tables of contents) and add a precision check for invented stories.
  • Story splitting suggestions for invest-small findings, using the criteria as split points.
  • PRD diff mode: re-run on a revised PRD and report added, removed and changed requirements by ID, with the stories each change affects.

Later

  • Two-way sync with trackers. Push reviewed backlogs through the Jira, Linear and GitHub APIs, keep the requirement-to-issue mapping, and pull tracker edits back so the PRD trace stays true after sprint changes.
  • Measure PM edit distance and acceptance rate from that sync loop. This is the metric that tells us whether the drafts actually save time.
  • Google Docs and Confluence ingestion, with the same line-level traceability.

Limitations

  • The heuristic generator writes one story per requirement. It does not split a large requirement or merge small ones. That is a refinement call for a person or an LLM.
  • Story phrasing is pattern-based ("X can Y", "The system must Y", "As a X, I want Y"). Unusual sentence shapes fall back to a literal "I want ..." clause, and the linter usually flags them.
  • Inferred dependencies come from verb/noun overlap. They catch "create before act on" chains and miss semantic ones (for example, offline storage before background sync).
  • Estimates are relative signals, not velocity-calibrated forecasts.
  • The lint score grades structure and wording, not whether the product decisions are right. On the bundled corpus it is also grading the heuristic's own output. It is most useful for comparing generators, or a backlog before and after review.
  • The LLM adapters are covered by tests against faked HTTP responses. They have not been benchmarked against live models in this repo, so the eval table above has heuristic rows only.
  • Jira and Linear CSV column names follow their importers' documented fields. Confirm the column mapping in the importer UI on first use.