diff --git a/src/resources/extensions/gsd/roadmap-slices.ts b/src/resources/extensions/gsd/roadmap-slices.ts index 781105ff8..33ec34b83 100644 --- a/src/resources/extensions/gsd/roadmap-slices.ts +++ b/src/resources/extensions/gsd/roadmap-slices.ts @@ -66,6 +66,17 @@ function parseTableSlices(section: string): RoadmapSliceEntry[] { const lines = section.split("\n"); const slices: RoadmapSliceEntry[] = []; + // Detect dependency column index from the header row (#3383, #3336). + // Only parse deps from this column (or cells with explicit "depends"/"deps" keywords). + let depColumnIndex = -1; + for (const line of lines) { + if (!line.includes("|")) continue; + if (/S\d+/.test(line)) break; // reached data rows + const headerCells = line.split("|").map(c => c.trim()).filter(Boolean); + depColumnIndex = headerCells.findIndex(c => /^(depends|deps|depend)/i.test(c)); + if (depColumnIndex >= 0) break; + } + for (const line of lines) { // Skip non-table lines, separator lines (|---|---|), and header rows if (!line.includes("|")) continue; @@ -95,12 +106,17 @@ function parseTableSlices(section: string): RoadmapSliceEntry[] { if (/\bmedium\b/.test(cellLower) || /\bmed\b/.test(cellLower)) { risk = "medium"; break; } } - // Extract dependencies from cells containing S-prefixed IDs (excluding the slice's own ID) + // Extract dependencies only from the dependency column or cells with + // explicit "depends"/"deps" keywords — never from title cells (#3383). let depends: string[] = []; - for (const cell of cells) { - if (/depends|deps/i.test(cell) || (cell.match(/S\d+/g)?.length ?? 0) > 0) { - const depIds = (cell.match(/S\d+/g) ?? []).filter(d => d !== id); - if (depIds.length > 0 || /none|—|-/i.test(cell)) { + if (depColumnIndex >= 0 && cells[depColumnIndex]) { + const depCell = cells[depColumnIndex]!; + const depIds = (depCell.match(/S\d+/g) ?? []).filter(d => d !== id); + depends = expandDependencies(depIds); + } else { + for (const cell of cells) { + if (/depends|deps/i.test(cell)) { + const depIds = (cell.match(/S\d+/g) ?? []).filter(d => d !== id); depends = expandDependencies(depIds); break; } diff --git a/src/resources/extensions/gsd/tools/complete-task.ts b/src/resources/extensions/gsd/tools/complete-task.ts index a862ae92b..e3c422aa9 100644 --- a/src/resources/extensions/gsd/tools/complete-task.ts +++ b/src/resources/extensions/gsd/tools/complete-task.ts @@ -44,6 +44,18 @@ export interface CompleteTaskResult { import type { TaskRow } from "../gsd-db.js"; +/** + * Normalize a list parameter that may arrive as a string (newline-delimited + * bullet list from the LLM) into a string array (#3361). + */ +function normalizeListParam(value: unknown): string[] { + if (Array.isArray(value)) return value.map(String); + if (typeof value === "string" && value.trim()) { + return value.split(/\n/).map(s => s.replace(/^[\s\-*•]+/, "").trim()).filter(Boolean); + } + return []; +} + /** * Build a TaskRow-shaped object from CompleteTaskParams so the unified * renderSummaryContent() can be used at completion time (#2720). @@ -63,8 +75,8 @@ function paramsToTaskRow(params: CompleteTaskParams, completedAt: string): TaskR blocker_discovered: params.blockerDiscovered ?? false, deviations: params.deviations ?? "", known_issues: params.knownIssues ?? "", - key_files: params.keyFiles ?? [], - key_decisions: params.keyDecisions ?? [], + key_files: normalizeListParam(params.keyFiles), + key_decisions: normalizeListParam(params.keyDecisions), full_summary_md: "", description: "", estimate: "",