When the LLM writes freeform prose roadmaps with `## Slice S01: Title` headers instead of the machine-readable `## Slices` checklist, parseRoadmapSlices() returned zero slices, causing deriveState() to permanently block with 'No slice eligible'. Add a fallback parser that detects prose-style `## Slice SXX:` headers (and variants like `## S01:`, `## S01 —`) and extracts slice IDs, titles, and dependencies from the prose. Also parses `Depends on:` text patterns. All fallback slices default to risk:medium and done:false.
This commit is contained in:
parent
2f459b5d03
commit
a7453719f5
1 changed files with 45 additions and 1 deletions
|
|
@ -53,7 +53,12 @@ function extractSlicesSection(content: string): string {
|
|||
export function parseRoadmapSlices(content: string): RoadmapSliceEntry[] {
|
||||
const slicesSection = extractSlicesSection(content);
|
||||
const slices: RoadmapSliceEntry[] = [];
|
||||
if (!slicesSection) return slices;
|
||||
if (!slicesSection) {
|
||||
// Fallback: detect prose-style slice headers (## Slice S01: Title)
|
||||
// when the LLM writes freeform prose instead of the ## Slices checklist.
|
||||
// This prevents a permanent "No slice eligible" block (#807).
|
||||
return parseProseSliceHeaders(content);
|
||||
}
|
||||
|
||||
const checkboxItems = slicesSection.split("\n");
|
||||
let currentSlice: RoadmapSliceEntry | null = null;
|
||||
|
|
@ -88,3 +93,42 @@ export function parseRoadmapSlices(content: string): RoadmapSliceEntry[] {
|
|||
if (currentSlice) slices.push(currentSlice);
|
||||
return slices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback parser for prose-style roadmaps where the LLM wrote
|
||||
* `## Slice S01: Title` headers instead of the machine-readable
|
||||
* `## Slices` checklist. Extracts slice IDs and titles so auto-mode
|
||||
* can at least identify slices and plan them.
|
||||
*
|
||||
* Also handles `## S01: Title` and `## S01 — Title` variants.
|
||||
*/
|
||||
function parseProseSliceHeaders(content: string): RoadmapSliceEntry[] {
|
||||
const slices: RoadmapSliceEntry[] = [];
|
||||
const headerPattern = /^##\s+(?:Slice\s+)?(S\d+)[:\s—–-]+\s*(.+)/gm;
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
while ((match = headerPattern.exec(content)) !== null) {
|
||||
const id = match[1]!;
|
||||
const title = match[2]!.trim();
|
||||
|
||||
// Try to extract depends from prose: "Depends on: S01" or "**Depends on:** S01, S02"
|
||||
const afterHeader = content.slice(match.index + match[0].length);
|
||||
const nextHeader = afterHeader.search(/^##\s/m);
|
||||
const section = nextHeader !== -1 ? afterHeader.slice(0, nextHeader) : afterHeader.slice(0, 500);
|
||||
|
||||
const depsMatch = section.match(/\*{0,2}Depends\s+on:?\*{0,2}\s*(.+)/i);
|
||||
let depends: string[] = [];
|
||||
if (depsMatch) {
|
||||
const rawDeps = depsMatch[1]!.replace(/none/i, "").trim();
|
||||
if (rawDeps) {
|
||||
depends = expandDependencies(
|
||||
rawDeps.split(/[,;]/).map(s => s.trim().replace(/[^A-Za-z0-9]/g, "")).filter(Boolean)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
slices.push({ id, title, risk: "medium" as RiskLevel, depends, done: false, demo: "" });
|
||||
}
|
||||
|
||||
return slices;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue