feat(bmad): support alphanumeric epic and story IDs#19
Conversation
📝 WalkthroughSummary by CodeRabbitNotes de version
WalkthroughCette PR étend MyBMAD pour reconnaître les identifiants alphanumériques (ex. ChangesSupport complet des identifiants alphanumériques
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested labels
🚥 Pre-merge checks | ✅ 6 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (6 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (24)
README.mdsrc/components/dashboard/epics-list.tsxsrc/components/dashboard/repo-card.tsxsrc/components/dashboard/sprint-summary-card.tsxsrc/components/epics/epic-stories-sheet.tsxsrc/components/epics/epic-timeline-card.tsxsrc/components/epics/epics-browser.tsxsrc/components/epics/epics-kanban.tsxsrc/components/stories/kanban-board.tsxsrc/lib/bmad/__tests__/correlate.test.tssrc/lib/bmad/__tests__/parse-epic-file.test.tssrc/lib/bmad/__tests__/parse-epic-folder.test.tssrc/lib/bmad/__tests__/parse-epics.test.tssrc/lib/bmad/__tests__/parse-sprint-status.test.tssrc/lib/bmad/__tests__/parse-story.test.tssrc/lib/bmad/__tests__/parser.epic-folder.test.tssrc/lib/bmad/correlate.tssrc/lib/bmad/parse-epic-file.tssrc/lib/bmad/parse-epic-folder.tssrc/lib/bmad/parse-epics.tssrc/lib/bmad/parse-sprint-status.tssrc/lib/bmad/parse-story.tssrc/lib/bmad/parser.tssrc/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 inlineinsrc/app/globals.css(no tailwind.config file)
Files:
src/components/epics/epics-kanban.tsxsrc/components/epics/epic-timeline-card.tsxsrc/lib/bmad/__tests__/parse-epic-file.test.tssrc/lib/bmad/__tests__/parse-epic-folder.test.tssrc/components/stories/kanban-board.tsxsrc/lib/bmad/utils.tssrc/components/dashboard/repo-card.tsxsrc/components/dashboard/sprint-summary-card.tsxsrc/lib/bmad/__tests__/parse-sprint-status.test.tssrc/lib/bmad/__tests__/parse-epics.test.tssrc/components/epics/epic-stories-sheet.tsxsrc/lib/bmad/__tests__/correlate.test.tssrc/lib/bmad/__tests__/parse-story.test.tssrc/components/dashboard/epics-list.tsxsrc/lib/bmad/parse-epic-folder.tssrc/lib/bmad/correlate.tssrc/lib/bmad/parse-sprint-status.tssrc/components/epics/epics-browser.tsxsrc/lib/bmad/__tests__/parser.epic-folder.test.tssrc/lib/bmad/parse-story.tssrc/lib/bmad/parser.tssrc/lib/bmad/parse-epic-file.tssrc/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: usering-3notring-[3px], percentages: use fraction notation liketop-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.tsxsrc/components/epics/epic-timeline-card.tsxsrc/components/stories/kanban-board.tsxsrc/components/dashboard/repo-card.tsxsrc/components/dashboard/sprint-summary-card.tsxsrc/components/epics/epic-stories-sheet.tsxsrc/components/dashboard/epics-list.tsxsrc/components/epics/epics-browser.tsx
**/*.{css,tsx,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Repeated hex colors should be added as theme tokens in
globals.cssrather than as inline arbitrary Tailwind values
Files:
src/components/epics/epics-kanban.tsxsrc/components/epics/epic-timeline-card.tsxsrc/components/stories/kanban-board.tsxsrc/components/dashboard/repo-card.tsxsrc/components/dashboard/sprint-summary-card.tsxsrc/components/epics/epic-stories-sheet.tsxsrc/components/dashboard/epics-list.tsxsrc/components/epics/epics-browser.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CONTRIBUTING.md)
**/*.{ts,tsx}: Use TypeScript strictly — noanyunless 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.tsxsrc/components/epics/epic-timeline-card.tsxsrc/lib/bmad/__tests__/parse-epic-file.test.tssrc/lib/bmad/__tests__/parse-epic-folder.test.tssrc/components/stories/kanban-board.tsxsrc/lib/bmad/utils.tssrc/components/dashboard/repo-card.tsxsrc/components/dashboard/sprint-summary-card.tsxsrc/lib/bmad/__tests__/parse-sprint-status.test.tssrc/lib/bmad/__tests__/parse-epics.test.tssrc/components/epics/epic-stories-sheet.tsxsrc/lib/bmad/__tests__/correlate.test.tssrc/lib/bmad/__tests__/parse-story.test.tssrc/components/dashboard/epics-list.tsxsrc/lib/bmad/parse-epic-folder.tssrc/lib/bmad/correlate.tssrc/lib/bmad/parse-sprint-status.tssrc/components/epics/epics-browser.tsxsrc/lib/bmad/__tests__/parser.epic-folder.test.tssrc/lib/bmad/parse-story.tssrc/lib/bmad/parser.tssrc/lib/bmad/parse-epic-file.tssrc/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., usew-40instead ofw-[160px],mt-3.75instead ofmt-[15px],ring-3instead ofring-[3px],top-1/2instead oftop-[50%])
Arbitrary values in Tailwind CSS are acceptable for:calc()expressions, CSS variable references, and non-standard values
Files:
src/components/epics/epics-kanban.tsxsrc/components/epics/epic-timeline-card.tsxsrc/lib/bmad/__tests__/parse-epic-file.test.tssrc/lib/bmad/__tests__/parse-epic-folder.test.tssrc/components/stories/kanban-board.tsxsrc/lib/bmad/utils.tssrc/components/dashboard/repo-card.tsxsrc/components/dashboard/sprint-summary-card.tsxsrc/lib/bmad/__tests__/parse-sprint-status.test.tssrc/lib/bmad/__tests__/parse-epics.test.tssrc/components/epics/epic-stories-sheet.tsxsrc/lib/bmad/__tests__/correlate.test.tssrc/lib/bmad/__tests__/parse-story.test.tssrc/components/dashboard/epics-list.tsxsrc/lib/bmad/parse-epic-folder.tssrc/lib/bmad/correlate.tssrc/lib/bmad/parse-sprint-status.tssrc/components/epics/epics-browser.tsxsrc/lib/bmad/__tests__/parser.epic-folder.test.tssrc/lib/bmad/parse-story.tssrc/lib/bmad/parser.tssrc/lib/bmad/parse-epic-file.tssrc/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_SECRETas the key material
Files:
src/components/epics/epics-kanban.tsxsrc/components/epics/epic-timeline-card.tsxsrc/lib/bmad/__tests__/parse-epic-file.test.tssrc/lib/bmad/__tests__/parse-epic-folder.test.tssrc/components/stories/kanban-board.tsxsrc/lib/bmad/utils.tssrc/components/dashboard/repo-card.tsxsrc/components/dashboard/sprint-summary-card.tsxsrc/lib/bmad/__tests__/parse-sprint-status.test.tssrc/lib/bmad/__tests__/parse-epics.test.tssrc/components/epics/epic-stories-sheet.tsxsrc/lib/bmad/__tests__/correlate.test.tssrc/lib/bmad/__tests__/parse-story.test.tssrc/components/dashboard/epics-list.tsxsrc/lib/bmad/parse-epic-folder.tssrc/lib/bmad/correlate.tssrc/lib/bmad/parse-sprint-status.tssrc/components/epics/epics-browser.tsxsrc/lib/bmad/__tests__/parser.epic-folder.test.tssrc/lib/bmad/parse-story.tssrc/lib/bmad/parser.tssrc/lib/bmad/parse-epic-file.tssrc/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.tsxsrc/components/epics/epic-timeline-card.tsxsrc/lib/bmad/__tests__/parse-epic-file.test.tssrc/lib/bmad/__tests__/parse-epic-folder.test.tssrc/components/stories/kanban-board.tsxsrc/lib/bmad/utils.tssrc/components/dashboard/repo-card.tsxsrc/components/dashboard/sprint-summary-card.tsxsrc/lib/bmad/__tests__/parse-sprint-status.test.tssrc/lib/bmad/__tests__/parse-epics.test.tssrc/components/epics/epic-stories-sheet.tsxsrc/lib/bmad/__tests__/correlate.test.tssrc/lib/bmad/__tests__/parse-story.test.tssrc/components/dashboard/epics-list.tsxsrc/lib/bmad/parse-epic-folder.tssrc/lib/bmad/correlate.tssrc/lib/bmad/parse-sprint-status.tssrc/components/epics/epics-browser.tsxsrc/lib/bmad/__tests__/parser.epic-folder.test.tssrc/lib/bmad/parse-story.tssrc/lib/bmad/parser.tssrc/lib/bmad/parse-epic-file.tssrc/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.tsxsrc/components/epics/epic-timeline-card.tsxsrc/components/stories/kanban-board.tsxsrc/components/dashboard/repo-card.tsxsrc/components/dashboard/sprint-summary-card.tsxsrc/components/epics/epic-stories-sheet.tsxsrc/components/dashboard/epics-list.tsxsrc/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.tssrc/lib/bmad/__tests__/parse-epic-folder.test.tssrc/lib/bmad/__tests__/parse-sprint-status.test.tssrc/lib/bmad/__tests__/parse-epics.test.tssrc/lib/bmad/__tests__/correlate.test.tssrc/lib/bmad/__tests__/parse-story.test.tssrc/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.tssrc/lib/bmad/__tests__/parse-epic-folder.test.tssrc/lib/bmad/utils.tssrc/lib/bmad/__tests__/parse-sprint-status.test.tssrc/lib/bmad/__tests__/parse-epics.test.tssrc/lib/bmad/__tests__/correlate.test.tssrc/lib/bmad/__tests__/parse-story.test.tssrc/lib/bmad/parse-epic-folder.tssrc/lib/bmad/correlate.tssrc/lib/bmad/parse-sprint-status.tssrc/lib/bmad/__tests__/parser.epic-folder.test.tssrc/lib/bmad/parse-story.tssrc/lib/bmad/parser.tssrc/lib/bmad/parse-epic-file.tssrc/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.tssrc/lib/bmad/__tests__/parse-epic-folder.test.tssrc/lib/bmad/__tests__/parse-sprint-status.test.tssrc/lib/bmad/__tests__/parse-epics.test.tssrc/lib/bmad/__tests__/correlate.test.tssrc/lib/bmad/__tests__/parse-story.test.tssrc/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.tssrc/lib/bmad/__tests__/parse-epic-folder.test.tssrc/lib/bmad/__tests__/parse-sprint-status.test.tssrc/lib/bmad/__tests__/parse-epics.test.tssrc/lib/bmad/__tests__/correlate.test.tssrc/lib/bmad/__tests__/parse-story.test.tssrc/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 winLa normalisation de casse entre
epic.storiesetstory.idest 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.tsnormalise les références de stories en minuscules quand elles contiennent des lettres, etparse-story.tsnormalise aussistory.iden minuscules (vianormalizeStoryIdentifier/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
| const sortedEpics = [...byEpic.entries()].sort((a, b) => | ||
| compareIds(a[0], b[0]), | ||
| ); |
There was a problem hiding this comment.
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.
| <span className="text-xs font-mono text-muted-foreground shrink-0" title={story.id}> | ||
| S{getStoryShortId(story.id)} |
There was a problem hiding this comment.
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}.
| 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"); | ||
| }); |
There was a problem hiding this comment.
🛠️ 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");📝 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.
| 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.
| 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); | ||
| } | ||
| } |
There was a problem hiding this comment.
🧹 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.
| import { normalizeAlphanumericId, normalizeStoryStatus } from "./utils"; | ||
|
|
||
| function normalizeStoryIdentifier(raw: string): string { | ||
| return /[A-Za-z]/.test(raw) ? raw.toLowerCase() : raw; | ||
| } |
There was a problem hiding this comment.
🧹 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.
| 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; |
There was a problem hiding this comment.
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.
| 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.
Summary
GitNexus / risk
Validation