Skip to content

feat(bmad): support alphanumeric epic and story IDs#19

Merged
DevHDI merged 1 commit into
mainfrom
codex/alphanumeric-bmad-ids
May 30, 2026
Merged

feat(bmad): support alphanumeric epic and story IDs#19
DevHDI merged 1 commit into
mainfrom
codex/alphanumeric-bmad-ids

Conversation

@DevHDI

@DevHDI DevHDI commented May 30, 2026

Copy link
Copy Markdown
Owner

Summary

  • replaces closed PR feat(bmad): support alphanumeric IDs and introduce agile timeline dashboard #6 with a smaller current-main implementation
  • supports explicit alphanumeric epic/story IDs across epics.md, epic files/folders, story filenames, and sprint-status keys
  • keeps current correlation behavior: no orphan-story dropping, no _sourcePath exposure, no LocalProvider Guard 7 changes
  • adds compact UI labels and natural sorting for alphanumeric IDs

GitNexus / risk

  • GitNexus index refreshed with npx gitnexus analyze
  • impact/detect-changes report critical risk because getBmadProject feeds Dashboard, Overview, Epics, and Stories flows
  • mitigation: focused parser/correlation changes plus integration coverage for custom output folders, epic-folder layout, flat epic files, and backfill linking

Validation

  • pnpm test: 166 passed
  • pnpm lint: 0 errors, 2 existing React Compiler/TanStack warnings
  • pnpm exec tsc --noEmit: passed
  • pnpm build: blocked by next/font failing to fetch Google Inter in this environment

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

Notes de version

  • Nouvelles fonctionnalités

    • Support des identifiants alphanumériques pour les épics et stories (ex. devops-infra) en complément des IDs numériques.
    • Affichage simplifié des identifiants dans l'interface avec conservation de l'ID complet en info-bulle.
  • Documentation

    • Ajout d'une section détaillant les conventions de nommage des épics et stories.
  • Tests

    • Couverture étendue pour valider le parsing et l'affichage des identifiants alphanumériques.

Walkthrough

Cette PR étend MyBMAD pour reconnaître les identifiants alphanumériques (ex. devops-infra, di.1) en plus des identifiants numériques existants dans les épics et les histoires. Les modifications incluent l'ajout de fonctions de normalisation et de comparaison d'identifiants, l'extension des parsers pour reconnaître les nouveaux formats, la mise à jour de l'interface utilisateur pour afficher des IDs raccourcis, et une documentation complète avec tests de couverture.

Changes

Support complet des identifiants alphanumériques

Layer / File(s) Summary
Utilitaires de normalisation et comparaison
src/lib/bmad/utils.ts
Ajout de quatre fonctions exportées : normalizeAlphanumericId (normalise en minuscules, remplace / par -), compareIds (tri numérique avec sensibilité locale), et les raccourcis getEpicShortId/getStoryShortId pour dériver les IDs affichés.
Parsers épics avec support alphanumérique
src/lib/bmad/parse-epic-file.ts, parse-epic-folder.ts, parse-epics.ts
Modification de parseEpicFile, parseEpicFolderName et parseEpics pour reconnaître les identifiants alphanumériques dans les noms de fichiers (ex. epic-devops-infra.md), les noms de dossiers et les headings d'épics (ex. ## Epic DevOps/Infra: ...), avec extraction robuste des références d'histoires en variantes alphanumériques.
Parser stories avec support alphanumérique
src/lib/bmad/parse-story.ts
Extension pour détecter les préfixes alphanumériques dans les noms de fichiers (ex. di-1-title.md), traiter les frontmatters avec id alphanumérique, et améliorer l'extraction des titres pour accepter des variantes comme DI.*.
Parser sprint et corrélation
src/lib/bmad/parse-sprint-status.ts, correlate.ts
Mise à jour de parseSprintStatus pour gérer les clés épics/stories alphanumériques dans development_status, avec normalisation et filtrage des entrées rétrospectives. Modification de correlate pour enrichir les stories via un fallback basé sur l'appartenance aux histoires d'une épic.
Parser global et tri cohérent
src/lib/bmad/parser.ts
Réorganisation des imports, extension pour reconnaître les fichiers épics alphanumériques, amélioration de la sélection des fichiers stories avec filtres d'exclusion (epic-, bmad-), et remplacement du tri numérique par compareIds pour gérer les identifiants mixtes.
Affichage des IDs raccourcis
src/components/dashboard/*, src/components/epics/*, src/components/stories/*
Mise à jour de neuf composants (EpicsList, RepoCard, SprintSummaryCard, EpicStoriesSheet, EpicTimelineCard, EpicsBrowser, EpicsKanban, KanbanBoard) pour afficher les IDs raccourcis via getEpicShortId/getStoryShortId avec conservation des IDs complets en attributs title, et remplacement du tri numérique par compareIds.
Documentation et tests
README.md, src/lib/bmad/__tests__/*
Ajout de la section "Epic and Story Naming" documentant l'inférence des identifiants alphanumériques et numériques. Ajout de 37 tests couvrant l'extraction, la normalisation et l'association des identifiants alphanumériques dans les fichiers épics, les dossiers, les en-têtes, les frontmatters, le statut de sprint et les parsers globaux.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

frontend, parser, docs

🚥 Pre-merge checks | ✅ 6 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Tailwind V4 Conventions ⚠️ Warning Modified UI code in src/components/epics/epic-stories-sheet.tsx violates Tailwind v4 conventions by using arbitrary bracket value text-[10px] instead of canonical class text-xs. Replace text-[10px] with text-xs in epic-stories-sheet.tsx to follow Tailwind v4 canonical utility class convention.
✅ Passed checks (6 passed)
Check name Status Explanation
Title check ✅ Passed Le titre est directement lié au changement principal : l'ajout du support des identifiants alphanumériques pour les épics et les stories dans le parseur BMAD.
Description check ✅ Passed La description est étroitement liée au changeset : elle détaille l'implémentation des identifiants alphanumériques, les fichiers affectés, et les mesures de validation effectuées.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Server Action Contract ✅ Passed No server actions were modified in this PR. All 24 changed files are UI components, BMAD parsers, tests, or documentation. Existing server actions remain unchanged.
Auth And Data Isolation ✅ Passed PR modifie uniquement la logique de parsing des IDs alphanumériques et l'affichage UI. Aucune modification d'auth, tokens, fichiers sensibles, ou isolement utilisateur. LocalProvider Guard préservé.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/alphanumeric-bmad-ids

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/dashboard/sprint-summary-card.tsx`:
- Around line 57-59: La carte trie déjà les epics via sortedEpics (à partir de
byEpic.entries() et compareIds) mais affiche encore l'ID brut dans le rendu
("Epic {epicId}"); changez l'affichage pour utiliser l'ID court utilisé ailleurs
(appelez ici l'utilitaire de formatage des IDs court — p.ex.
toShortId/formatShortId or the project's shortId helper) et remplacez les
occurrences de "Epic {epicId}" par "Epic {shortId(epicId)}" dans le composant
qui itère sortedEpics afin d'aligner cette vue avec le reste de la PR.

In `@src/components/stories/kanban-board.tsx`:
- Around line 59-60: Supprimez le préfixe hardcodé "S" dans le rendu du label
afin d'afficher uniquement la valeur retournée par getStoryShortId(story.id);
localisez l'élément <span> où on affiche S{getStoryShortId(story.id)} dans le
composant Kanban (référence getStoryShortId and story.id) et remplacez
l'affichage par simplement {getStoryShortId(story.id)} en conservant
title={story.id}.

In `@src/lib/bmad/__tests__/parse-story.test.ts`:
- Around line 50-59: The test in parse-story.test.ts currently asserts that
parseStory(normalizes frontmatter id) sets result!.id but misses verifying epic
relationship; update the "uses frontmatter id when present" test to also assert
result!.epicId === "di" after calling parseStory(content, "1-1-test.md") so that
when frontmatter id (DI.2) overrides the filename-derived id the epicId is
normalized and updated accordingly; locate the assertion block around parseStory
and add the epicId expectation alongside the existing id expectation.

In `@src/lib/bmad/parse-epics.ts`:
- Around line 32-41: The story reference extraction logic (regex, normalization
to lowercase, and deduplication) used in parse-epics.ts (the
storyRef/raw/id/storyIds block) is duplicated in parse-epic-file.ts; extract
that logic into a shared utility function named
extractStoryReferencesFromText(body: string): string[] in utils.ts and replace
the inline code in both parse-epics.ts and parse-epic-file.ts with calls to that
function; ensure the utility returns unique, normalized IDs (letters ->
lowercased, numeric preserved) and use it where storyIds were previously
populated.

In `@src/lib/bmad/parse-story.ts`:
- Around line 3-7: The local function normalizeStoryIdentifier duplicates logic
used in parse-epic-file.ts and parse-epics.ts; move its implementation into
utils.ts (export it, e.g. export function normalizeStoryIdentifier(raw: string):
string { ... }), remove the local definition in src/lib/bmad/parse-story.ts and
replace with an import from utils, then update parse-epic-file.ts and
parse-epics.ts to import and use the shared normalizeStoryIdentifier to ensure
consistent behavior across parseStoryIdentifier, parseEpicFile and parseEpics
usages.

In `@src/lib/bmad/parser.ts`:
- Around line 123-127: The filename prefilter in src/lib/bmad/parser.ts (the
checks referencing filename, before calling parseStory) wrongly excludes
explicit alphanumeric IDs like "DI.1" because it only accepts patterns with
hyphen-separated numeric IDs; update the regexes so existing exclusions
(/^epic[-_]/i and /^bmad[-_]/i) remain but add/adjust acceptance patterns to
include dot-separated alphanumeric IDs — e.g. add or change to a pattern that
returns true for filenames matching /^\w+\.\d+(-.+)?\.md$/i (covers DI.1,
DI.1-setup-ci, devops-infra.2.md) and broaden the hyphenated pattern to
/^[a-z][a-z0-9._-]*-\d+-.+\.md$/i so parseStory is reached for these files; then
add a Vitest regression using files like implementation-artifacts/DI.1.md and
DI.1-setup-ci.md to lock the behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: fb78da90-32b4-41c0-9d27-04d76ce11f20

📥 Commits

Reviewing files that changed from the base of the PR and between 3bb4a50 and c5eabe3.

📒 Files selected for processing (24)
  • README.md
  • src/components/dashboard/epics-list.tsx
  • src/components/dashboard/repo-card.tsx
  • src/components/dashboard/sprint-summary-card.tsx
  • src/components/epics/epic-stories-sheet.tsx
  • src/components/epics/epic-timeline-card.tsx
  • src/components/epics/epics-browser.tsx
  • src/components/epics/epics-kanban.tsx
  • src/components/stories/kanban-board.tsx
  • src/lib/bmad/__tests__/correlate.test.ts
  • src/lib/bmad/__tests__/parse-epic-file.test.ts
  • src/lib/bmad/__tests__/parse-epic-folder.test.ts
  • src/lib/bmad/__tests__/parse-epics.test.ts
  • src/lib/bmad/__tests__/parse-sprint-status.test.ts
  • src/lib/bmad/__tests__/parse-story.test.ts
  • src/lib/bmad/__tests__/parser.epic-folder.test.ts
  • src/lib/bmad/correlate.ts
  • src/lib/bmad/parse-epic-file.ts
  • src/lib/bmad/parse-epic-folder.ts
  • src/lib/bmad/parse-epics.ts
  • src/lib/bmad/parse-sprint-status.ts
  • src/lib/bmad/parse-story.ts
  • src/lib/bmad/parser.ts
  • src/lib/bmad/utils.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🧰 Additional context used
📓 Path-based instructions (13)
**/*.{css,tsx,ts,jsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Tailwind CSS v4 with @theme inline in src/app/globals.css (no tailwind.config file)

Files:

  • src/components/epics/epics-kanban.tsx
  • src/components/epics/epic-timeline-card.tsx
  • src/lib/bmad/__tests__/parse-epic-file.test.ts
  • src/lib/bmad/__tests__/parse-epic-folder.test.ts
  • src/components/stories/kanban-board.tsx
  • src/lib/bmad/utils.ts
  • src/components/dashboard/repo-card.tsx
  • src/components/dashboard/sprint-summary-card.tsx
  • src/lib/bmad/__tests__/parse-sprint-status.test.ts
  • src/lib/bmad/__tests__/parse-epics.test.ts
  • src/components/epics/epic-stories-sheet.tsx
  • src/lib/bmad/__tests__/correlate.test.ts
  • src/lib/bmad/__tests__/parse-story.test.ts
  • src/components/dashboard/epics-list.tsx
  • src/lib/bmad/parse-epic-folder.ts
  • src/lib/bmad/correlate.ts
  • src/lib/bmad/parse-sprint-status.ts
  • src/components/epics/epics-browser.tsx
  • src/lib/bmad/__tests__/parser.epic-folder.test.ts
  • src/lib/bmad/parse-story.ts
  • src/lib/bmad/parser.ts
  • src/lib/bmad/parse-epic-file.ts
  • src/lib/bmad/parse-epics.ts
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{tsx,jsx}: Never use arbitrary bracket values in Tailwind when a canonical class exists (spacing: divide px by 4, ring width: use ring-3 not ring-[3px], percentages: use fraction notation like top-1/2, rem values: convert to spacing scale)
Arbitrary Tailwind values are only OK for: calc expressions, non-standard values, and CSS variable references

Files:

  • src/components/epics/epics-kanban.tsx
  • src/components/epics/epic-timeline-card.tsx
  • src/components/stories/kanban-board.tsx
  • src/components/dashboard/repo-card.tsx
  • src/components/dashboard/sprint-summary-card.tsx
  • src/components/epics/epic-stories-sheet.tsx
  • src/components/dashboard/epics-list.tsx
  • src/components/epics/epics-browser.tsx
**/*.{css,tsx,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Repeated hex colors should be added as theme tokens in globals.css rather than as inline arbitrary Tailwind values

Files:

  • src/components/epics/epics-kanban.tsx
  • src/components/epics/epic-timeline-card.tsx
  • src/components/stories/kanban-board.tsx
  • src/components/dashboard/repo-card.tsx
  • src/components/dashboard/sprint-summary-card.tsx
  • src/components/epics/epic-stories-sheet.tsx
  • src/components/dashboard/epics-list.tsx
  • src/components/epics/epics-browser.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.{ts,tsx}: Use TypeScript strictly — no any unless absolutely necessary
Prefer explicit types over inference for function signatures in TypeScript
Use Server Components by default — add "use client" directive only when needed

Files:

  • src/components/epics/epics-kanban.tsx
  • src/components/epics/epic-timeline-card.tsx
  • src/lib/bmad/__tests__/parse-epic-file.test.ts
  • src/lib/bmad/__tests__/parse-epic-folder.test.ts
  • src/components/stories/kanban-board.tsx
  • src/lib/bmad/utils.ts
  • src/components/dashboard/repo-card.tsx
  • src/components/dashboard/sprint-summary-card.tsx
  • src/lib/bmad/__tests__/parse-sprint-status.test.ts
  • src/lib/bmad/__tests__/parse-epics.test.ts
  • src/components/epics/epic-stories-sheet.tsx
  • src/lib/bmad/__tests__/correlate.test.ts
  • src/lib/bmad/__tests__/parse-story.test.ts
  • src/components/dashboard/epics-list.tsx
  • src/lib/bmad/parse-epic-folder.ts
  • src/lib/bmad/correlate.ts
  • src/lib/bmad/parse-sprint-status.ts
  • src/components/epics/epics-browser.tsx
  • src/lib/bmad/__tests__/parser.epic-folder.test.ts
  • src/lib/bmad/parse-story.ts
  • src/lib/bmad/parser.ts
  • src/lib/bmad/parse-epic-file.ts
  • src/lib/bmad/parse-epics.ts
**/*.{css,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.{css,ts,tsx,js,jsx}: Never use arbitrary bracket values in Tailwind CSS when a canonical class exists (e.g., use w-40 instead of w-[160px], mt-3.75 instead of mt-[15px], ring-3 instead of ring-[3px], top-1/2 instead of top-[50%])
Arbitrary values in Tailwind CSS are acceptable for: calc() expressions, CSS variable references, and non-standard values

Files:

  • src/components/epics/epics-kanban.tsx
  • src/components/epics/epic-timeline-card.tsx
  • src/lib/bmad/__tests__/parse-epic-file.test.ts
  • src/lib/bmad/__tests__/parse-epic-folder.test.ts
  • src/components/stories/kanban-board.tsx
  • src/lib/bmad/utils.ts
  • src/components/dashboard/repo-card.tsx
  • src/components/dashboard/sprint-summary-card.tsx
  • src/lib/bmad/__tests__/parse-sprint-status.test.ts
  • src/lib/bmad/__tests__/parse-epics.test.ts
  • src/components/epics/epic-stories-sheet.tsx
  • src/lib/bmad/__tests__/correlate.test.ts
  • src/lib/bmad/__tests__/parse-story.test.ts
  • src/components/dashboard/epics-list.tsx
  • src/lib/bmad/parse-epic-folder.ts
  • src/lib/bmad/correlate.ts
  • src/lib/bmad/parse-sprint-status.ts
  • src/components/epics/epics-browser.tsx
  • src/lib/bmad/__tests__/parser.epic-folder.test.ts
  • src/lib/bmad/parse-story.ts
  • src/lib/bmad/parser.ts
  • src/lib/bmad/parse-epic-file.ts
  • src/lib/bmad/parse-epics.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (SECURITY.md)

Implement AES-256-GCM encryption for OAuth tokens stored in the database using BETTER_AUTH_SECRET as the key material

Files:

  • src/components/epics/epics-kanban.tsx
  • src/components/epics/epic-timeline-card.tsx
  • src/lib/bmad/__tests__/parse-epic-file.test.ts
  • src/lib/bmad/__tests__/parse-epic-folder.test.ts
  • src/components/stories/kanban-board.tsx
  • src/lib/bmad/utils.ts
  • src/components/dashboard/repo-card.tsx
  • src/components/dashboard/sprint-summary-card.tsx
  • src/lib/bmad/__tests__/parse-sprint-status.test.ts
  • src/lib/bmad/__tests__/parse-epics.test.ts
  • src/components/epics/epic-stories-sheet.tsx
  • src/lib/bmad/__tests__/correlate.test.ts
  • src/lib/bmad/__tests__/parse-story.test.ts
  • src/components/dashboard/epics-list.tsx
  • src/lib/bmad/parse-epic-folder.ts
  • src/lib/bmad/correlate.ts
  • src/lib/bmad/parse-sprint-status.ts
  • src/components/epics/epics-browser.tsx
  • src/lib/bmad/__tests__/parser.epic-folder.test.ts
  • src/lib/bmad/parse-story.ts
  • src/lib/bmad/parser.ts
  • src/lib/bmad/parse-epic-file.ts
  • src/lib/bmad/parse-epics.ts
**/*.{tsx,ts,css}

📄 CodeRabbit inference engine (Custom checks)

Tailwind v4 conventions: Use canonical utility classes over arbitrary bracket values, represent repeated colors as globals.css theme tokens, and ensure accessible responsive controls

Files:

  • src/components/epics/epics-kanban.tsx
  • src/components/epics/epic-timeline-card.tsx
  • src/lib/bmad/__tests__/parse-epic-file.test.ts
  • src/lib/bmad/__tests__/parse-epic-folder.test.ts
  • src/components/stories/kanban-board.tsx
  • src/lib/bmad/utils.ts
  • src/components/dashboard/repo-card.tsx
  • src/components/dashboard/sprint-summary-card.tsx
  • src/lib/bmad/__tests__/parse-sprint-status.test.ts
  • src/lib/bmad/__tests__/parse-epics.test.ts
  • src/components/epics/epic-stories-sheet.tsx
  • src/lib/bmad/__tests__/correlate.test.ts
  • src/lib/bmad/__tests__/parse-story.test.ts
  • src/components/dashboard/epics-list.tsx
  • src/lib/bmad/parse-epic-folder.ts
  • src/lib/bmad/correlate.ts
  • src/lib/bmad/parse-sprint-status.ts
  • src/components/epics/epics-browser.tsx
  • src/lib/bmad/__tests__/parser.epic-folder.test.ts
  • src/lib/bmad/parse-story.ts
  • src/lib/bmad/parser.ts
  • src/lib/bmad/parse-epic-file.ts
  • src/lib/bmad/parse-epics.ts
src/components/**

⚙️ CodeRabbit configuration file

src/components/**: Review React UI code for accessibility, server/client component boundaries, hydration risks, keyboard navigation, loading/error/empty states, and responsive behavior.
Tailwind CSS v4 conventions for this repo:

  • Do not use arbitrary bracket values when a canonical Tailwind class exists.
  • Repeated colors should become theme tokens in src/app/globals.css.
  • shadcn components in src/components/ui are project-owned and should follow the same rules.
    Ignore style concerns for src/components/animate-ui and src/components/reui because they are excluded vendored components.

Files:

  • src/components/epics/epics-kanban.tsx
  • src/components/epics/epic-timeline-card.tsx
  • src/components/stories/kanban-board.tsx
  • src/components/dashboard/repo-card.tsx
  • src/components/dashboard/sprint-summary-card.tsx
  • src/components/epics/epic-stories-sheet.tsx
  • src/components/dashboard/epics-list.tsx
  • src/components/epics/epics-browser.tsx
src/lib/bmad/__tests__/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

BMAD parser tests must be located in src/lib/bmad/__tests__/

Files:

  • src/lib/bmad/__tests__/parse-epic-file.test.ts
  • src/lib/bmad/__tests__/parse-epic-folder.test.ts
  • src/lib/bmad/__tests__/parse-sprint-status.test.ts
  • src/lib/bmad/__tests__/parse-epics.test.ts
  • src/lib/bmad/__tests__/correlate.test.ts
  • src/lib/bmad/__tests__/parse-story.test.ts
  • src/lib/bmad/__tests__/parser.epic-folder.test.ts
src/lib/bmad/**

⚙️ CodeRabbit configuration file

src/lib/bmad/**: Review BMAD parsers as untrusted-input parsers:

  • Handle malformed Markdown, YAML frontmatter, JSON, sprint status, chunked epics, and missing files gracefully.
  • Preserve existing parser contracts and typed return shapes.
  • Prefer schema validation and sanitizer utilities over ad hoc string assumptions.
  • Ask for Vitest coverage when parser behavior, edge cases, or correlations change.

Files:

  • src/lib/bmad/__tests__/parse-epic-file.test.ts
  • src/lib/bmad/__tests__/parse-epic-folder.test.ts
  • src/lib/bmad/utils.ts
  • src/lib/bmad/__tests__/parse-sprint-status.test.ts
  • src/lib/bmad/__tests__/parse-epics.test.ts
  • src/lib/bmad/__tests__/correlate.test.ts
  • src/lib/bmad/__tests__/parse-story.test.ts
  • src/lib/bmad/parse-epic-folder.ts
  • src/lib/bmad/correlate.ts
  • src/lib/bmad/parse-sprint-status.ts
  • src/lib/bmad/__tests__/parser.epic-folder.test.ts
  • src/lib/bmad/parse-story.ts
  • src/lib/bmad/parser.ts
  • src/lib/bmad/parse-epic-file.ts
  • src/lib/bmad/parse-epics.ts
src/**/*.test.ts

⚙️ CodeRabbit configuration file

src/**/*.test.ts: Tests use Vitest. Check that test names describe behavior, edge cases are covered, mocks do not hide integration bugs, and parser/security regressions have direct assertions.

Files:

  • src/lib/bmad/__tests__/parse-epic-file.test.ts
  • src/lib/bmad/__tests__/parse-epic-folder.test.ts
  • src/lib/bmad/__tests__/parse-sprint-status.test.ts
  • src/lib/bmad/__tests__/parse-epics.test.ts
  • src/lib/bmad/__tests__/correlate.test.ts
  • src/lib/bmad/__tests__/parse-story.test.ts
  • src/lib/bmad/__tests__/parser.epic-folder.test.ts
src/**/__tests__/**

⚙️ CodeRabbit configuration file

src/**/__tests__/**: Tests use Vitest. Check malformed input, auth/security boundaries, parser fallbacks, and multi-user isolation where relevant.

Files:

  • src/lib/bmad/__tests__/parse-epic-file.test.ts
  • src/lib/bmad/__tests__/parse-epic-folder.test.ts
  • src/lib/bmad/__tests__/parse-sprint-status.test.ts
  • src/lib/bmad/__tests__/parse-epics.test.ts
  • src/lib/bmad/__tests__/correlate.test.ts
  • src/lib/bmad/__tests__/parse-story.test.ts
  • src/lib/bmad/__tests__/parser.epic-folder.test.ts
README.md

⚙️ CodeRabbit configuration file

README.md: Review README changes for accurate quick-start commands, ports, feature claims, screenshots, links, and consistency with docs/GETTING_STARTED.md and docker/DEPLOY.md.

Files:

  • README.md
🔇 Additional comments (27)
src/lib/bmad/utils.ts (1)

75-102: LGTM!

src/lib/bmad/parse-epic-file.ts (3)

1-49: LGTM!


51-77: LGTM!


79-139: LGTM!

src/lib/bmad/parse-epic-folder.ts (1)

1-44: LGTM!

src/lib/bmad/parse-epics.ts (1)

14-29: LGTM!

Also applies to: 49-58

src/lib/bmad/parse-story.ts (2)

16-42: LGTM!

Also applies to: 51-65


67-118: LGTM!

README.md (1)

17-17: LGTM!

Also applies to: 172-191

src/lib/bmad/__tests__/correlate.test.ts (1)

166-199: LGTM!

src/lib/bmad/__tests__/parse-epic-file.test.ts (1)

143-167: LGTM!

src/lib/bmad/__tests__/parse-epic-folder.test.ts (1)

59-64: LGTM!

src/lib/bmad/__tests__/parse-epics.test.ts (1)

77-112: LGTM!

src/lib/bmad/__tests__/parse-sprint-status.test.ts (1)

48-88: LGTM!

src/lib/bmad/__tests__/parse-story.test.ts (1)

26-32: LGTM!

Also applies to: 61-69

src/lib/bmad/__tests__/parser.epic-folder.test.ts (1)

73-119: LGTM!

Also applies to: 175-209

src/lib/bmad/parse-sprint-status.ts (2)

40-52: LGTM!


54-74: LGTM!

src/lib/bmad/correlate.ts (3)

8-9: LGTM!


106-120: LGTM!


111-113: ⚡ Quick win

La normalisation de casse entre epic.stories et story.id est cohérente.

Le fallback e.stories.includes(story.id) ne souffre pas d’un décalage de casse sur des IDs du type "DI.1" vs "di.1" : parse-epic-file.ts normalise les références de stories en minuscules quand elles contiennent des lettres, et parse-story.ts normalise aussi story.id en minuscules (via normalizeStoryIdentifier/normalizeAlphanumericId).

src/components/dashboard/epics-list.tsx (1)

8-8: LGTM!

Also applies to: 38-38, 57-58

src/components/dashboard/repo-card.tsx (1)

9-9: LGTM!

Also applies to: 23-23, 32-32

src/components/epics/epic-stories-sheet.tsx (1)

17-17: LGTM!

Also applies to: 78-79, 140-141

src/components/epics/epic-timeline-card.tsx (1)

5-5: LGTM!

Also applies to: 36-37

src/components/epics/epics-browser.tsx (1)

16-16: LGTM!

Also applies to: 111-121, 166-166, 251-252, 313-314

src/components/epics/epics-kanban.tsx (1)

9-9: LGTM!

Also applies to: 67-68

Comment on lines +57 to +59
const sortedEpics = [...byEpic.entries()].sort((a, b) =>
compareIds(a[0], b[0]),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Appliquez aussi les IDs courts dans ce rendu.

Le tri alphanumérique est bien pris en charge ici, mais la section “Progress by epic” affiche encore l’ID brut plus bas (Epic {epicId}). La carte Sprint reste donc incohérente avec les autres vues mises à jour dans cette PR.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/dashboard/sprint-summary-card.tsx` around lines 57 - 59, La
carte trie déjà les epics via sortedEpics (à partir de byEpic.entries() et
compareIds) mais affiche encore l'ID brut dans le rendu ("Epic {epicId}");
changez l'affichage pour utiliser l'ID court utilisé ailleurs (appelez ici
l'utilitaire de formatage des IDs court — p.ex. toShortId/formatShortId or the
project's shortId helper) et remplacez les occurrences de "Epic {epicId}" par
"Epic {shortId(epicId)}" dans le composant qui itère sortedEpics afin d'aligner
cette vue avec le reste de la PR.

Comment on lines +59 to +60
<span className="text-xs font-mono text-muted-foreground shrink-0" title={story.id}>
S{getStoryShortId(story.id)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Supprimez le préfixe S ajouté en dur.

getStoryShortId() est déjà utilisé ailleurs comme libellé d’affichage final. En le préfixant ici, cette colonne risque d’afficher un format différent du reste de l’UI.

Correctif proposé
-                      <span className="text-xs font-mono text-muted-foreground shrink-0" title={story.id}>
-                        S{getStoryShortId(story.id)}
+                      <span className="text-xs font-mono text-muted-foreground shrink-0" title={story.id}>
+                        {getStoryShortId(story.id)}
                       </span>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/stories/kanban-board.tsx` around lines 59 - 60, Supprimez le
préfixe hardcodé "S" dans le rendu du label afin d'afficher uniquement la valeur
retournée par getStoryShortId(story.id); localisez l'élément <span> où on
affiche S{getStoryShortId(story.id)} dans le composant Kanban (référence
getStoryShortId and story.id) et remplacez l'affichage par simplement
{getStoryShortId(story.id)} en conservant title={story.id}.

Comment on lines 50 to +59
it("uses frontmatter id when present", () => {
const content = `---
id: 5.1
id: DI.2
status: in-progress
---
# Story`;
const result = parseStory(content, "1-1-test.md");
expect(result).not.toBeNull();
expect(result!.id).toBe("5.1");
expect(result!.id).toBe("di.2");
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Ajoutez l’assertion sur epicId quand le frontmatter remplace l’ID.

Ici, le test verrouille id, mais pas la relation d’épic associée. Si parseStory normalise id: DI.2 tout en laissant epicId hérité du nom de fichier (1), cette régression passerait quand même. Ajoutez une assertion explicite sur epicId === "di".

✅ Assertion proposée
       const result = parseStory(content, "1-1-test.md");
       expect(result).not.toBeNull();
       expect(result!.id).toBe("di.2");
+      expect(result!.epicId).toBe("di");
As per coding guidelines, `src/**/*.test.ts`: Tests use Vitest. Check that test names describe behavior, edge cases are covered, mocks do not hide integration bugs, and parser/security regressions have direct assertions.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("uses frontmatter id when present", () => {
const content = `---
id: 5.1
id: DI.2
status: in-progress
---
# Story`;
const result = parseStory(content, "1-1-test.md");
expect(result).not.toBeNull();
expect(result!.id).toBe("5.1");
expect(result!.id).toBe("di.2");
});
it("uses frontmatter id when present", () => {
const content = `---
id: DI.2
status: in-progress
---
# Story`;
const result = parseStory(content, "1-1-test.md");
expect(result).not.toBeNull();
expect(result!.id).toBe("di.2");
expect(result!.epicId).toBe("di");
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/bmad/__tests__/parse-story.test.ts` around lines 50 - 59, The test in
parse-story.test.ts currently asserts that parseStory(normalizes frontmatter id)
sets result!.id but misses verifying epic relationship; update the "uses
frontmatter id when present" test to also assert result!.epicId === "di" after
calling parseStory(content, "1-1-test.md") so that when frontmatter id (DI.2)
overrides the filename-derived id the epicId is normalized and updated
accordingly; locate the assertion block around parseStory and add the epicId
expectation alongside the existing id expectation.

Comment on lines +32 to 41
const storyRef = line.matchAll(
/(?:story|S)[\s-]*((?:\d+(?:\.\d+)?)|(?:[A-Za-z][A-Za-z0-9_-]*\.\d+(?:\.\d+)?))/gi
);
for (const match of storyRef) {
const raw = match[1];
const id = /[A-Za-z]/.test(raw) ? raw.toLowerCase() : raw;
if (id && !storyIds.includes(id)) {
storyIds.push(id);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Logique d'extraction des références de stories dupliquée.

Cette logique (regex + normalisation + déduplication) est identique à celle de parse-epic-file.ts lignes 112-121. Envisagez d'extraire une fonction utilitaire partagée comme extractStoryReferencesFromText(body: string): string[] dans utils.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/bmad/parse-epics.ts` around lines 32 - 41, The story reference
extraction logic (regex, normalization to lowercase, and deduplication) used in
parse-epics.ts (the storyRef/raw/id/storyIds block) is duplicated in
parse-epic-file.ts; extract that logic into a shared utility function named
extractStoryReferencesFromText(body: string): string[] in utils.ts and replace
the inline code in both parse-epics.ts and parse-epic-file.ts with calls to that
function; ensure the utility returns unique, normalized IDs (letters ->
lowercased, numeric preserved) and use it where storyIds were previously
populated.

Comment on lines +3 to +7
import { normalizeAlphanumericId, normalizeStoryStatus } from "./utils";

function normalizeStoryIdentifier(raw: string): string {
return /[A-Za-z]/.test(raw) ? raw.toLowerCase() : raw;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Fonction normalizeStoryIdentifier potentiellement consolidable.

Cette fonction est similaire à la logique inline utilisée dans parse-epic-file.ts et parse-epics.ts (lowercase si contient des lettres). Vous pourriez l'exporter depuis utils.ts pour assurer la cohérence, mais ce n'est pas bloquant.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/bmad/parse-story.ts` around lines 3 - 7, The local function
normalizeStoryIdentifier duplicates logic used in parse-epic-file.ts and
parse-epics.ts; move its implementation into utils.ts (export it, e.g. export
function normalizeStoryIdentifier(raw: string): string { ... }), remove the
local definition in src/lib/bmad/parse-story.ts and replace with an import from
utils, then update parse-epic-file.ts and parse-epics.ts to import and use the
shared normalizeStoryIdentifier to ensure consistent behavior across
parseStoryIdentifier, parseEpicFile and parseEpics usages.

Comment thread src/lib/bmad/parser.ts
Comment on lines +123 to 127
if (/^epic[-_]/i.test(filename)) return false;
if (/^bmad[-_]/i.test(filename)) return false;
if (/^\d+-\d+-.+\.md$/.test(filename)) return true;
if (/^[a-z][a-z0-9_-]*-\d+-.+\.md$/i.test(filename)) return true;
if (/^story[_-]?\d/i.test(filename)) return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Le préfiltre des stories exclut encore les IDs explicites au format DI.1.

Ici, seuls les noms 1-2-*, <epic>-<story>-* et story* passent. Un fichier implementation-artifacts/DI.1.md, DI.1-setup-ci.md ou devops-infra.2.md ne sera donc jamais envoyé à parseStory, alors que la PR ajoute justement le support des IDs alphanumériques explicites depuis les noms de fichiers. Ça casse silencieusement le nouveau flux sur ces fichiers.

💡 Correctif minimal
     if (/^\d+-\d+-.+\.md$/.test(filename)) return true;
     if (/^[a-z][a-z0-9_-]*-\d+-.+\.md$/i.test(filename)) return true;
+    if (/^(?:\d+|[a-z][a-z0-9_-]*)\.\d+(?:-.+)?\.md$/i.test(filename)) return true;
     if (/^story[_-]?\d/i.test(filename)) return true;

Je recommanderais aussi une régression Vitest dédiée pour un fichier sous implementation-artifacts nommé DI.1.md ou DI.1-setup-ci.md, afin d’éviter que ce filtre re-casse le support ajouté. As per coding guidelines, src/lib/bmad/**: “Review BMAD parsers as untrusted-input parsers: ... Handle malformed Markdown ... gracefully ... Ask for Vitest coverage when parser behavior, edge cases, or correlations change.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (/^epic[-_]/i.test(filename)) return false;
if (/^bmad[-_]/i.test(filename)) return false;
if (/^\d+-\d+-.+\.md$/.test(filename)) return true;
if (/^[a-z][a-z0-9_-]*-\d+-.+\.md$/i.test(filename)) return true;
if (/^story[_-]?\d/i.test(filename)) return true;
if (/^epic[-_]/i.test(filename)) return false;
if (/^bmad[-_]/i.test(filename)) return false;
if (/^\d+-\d+-.+\.md$/.test(filename)) return true;
if (/^[a-z][a-z0-9_-]*-\d+-.+\.md$/i.test(filename)) return true;
if (/^(?:\d+|[a-z][a-z0-9_-]*)\.\d+(?:-.+)?\.md$/i.test(filename)) return true;
if (/^story[_-]?\d/i.test(filename)) return true;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/bmad/parser.ts` around lines 123 - 127, The filename prefilter in
src/lib/bmad/parser.ts (the checks referencing filename, before calling
parseStory) wrongly excludes explicit alphanumeric IDs like "DI.1" because it
only accepts patterns with hyphen-separated numeric IDs; update the regexes so
existing exclusions (/^epic[-_]/i and /^bmad[-_]/i) remain but add/adjust
acceptance patterns to include dot-separated alphanumeric IDs — e.g. add or
change to a pattern that returns true for filenames matching
/^\w+\.\d+(-.+)?\.md$/i (covers DI.1, DI.1-setup-ci, devops-infra.2.md) and
broaden the hyphenated pattern to /^[a-z][a-z0-9._-]*-\d+-.+\.md$/i so
parseStory is reached for these files; then add a Vitest regression using files
like implementation-artifacts/DI.1.md and DI.1-setup-ci.md to lock the behavior.

@DevHDI DevHDI merged commit ab2ed8e into main May 30, 2026
3 checks passed
@DevHDI DevHDI deleted the codex/alphanumeric-bmad-ids branch May 30, 2026 11:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant