Merge pull request #3692 from Tibsfox/fix/complete-task-normalize-list-inputs
fix(gsd): normalize list inputs in complete-task + fix roadmap dep parsing
This commit is contained in:
commit
1146352202
3 changed files with 89 additions and 7 deletions
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* Regression test for #3692 — normalizeListParam in complete-task
|
||||
*
|
||||
* Agents sometimes pass keyFiles/keyDecisions as comma-separated strings
|
||||
* instead of arrays. normalizeListParam coerces both forms to string[].
|
||||
*
|
||||
* Also verifies roadmap-slices.ts detects dependency column from header.
|
||||
*/
|
||||
|
||||
import { describe, test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const completeTaskSrc = readFileSync(
|
||||
join(__dirname, '..', 'tools', 'complete-task.ts'),
|
||||
'utf-8',
|
||||
);
|
||||
const roadmapSlicesSrc = readFileSync(
|
||||
join(__dirname, '..', 'roadmap-slices.ts'),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
describe('complete-task normalizeListParam (#3692)', () => {
|
||||
test('normalizeListParam function is defined', () => {
|
||||
assert.match(completeTaskSrc, /function normalizeListParam\(/,
|
||||
'normalizeListParam function should be defined in complete-task.ts');
|
||||
});
|
||||
|
||||
test('normalizeListParam is applied to keyFiles', () => {
|
||||
assert.match(completeTaskSrc, /normalizeListParam\(params\.keyFiles\)/,
|
||||
'normalizeListParam should be applied to keyFiles');
|
||||
});
|
||||
|
||||
test('normalizeListParam is applied to keyDecisions', () => {
|
||||
assert.match(completeTaskSrc, /normalizeListParam\(params\.keyDecisions\)/,
|
||||
'normalizeListParam should be applied to keyDecisions');
|
||||
});
|
||||
});
|
||||
|
||||
describe('roadmap-slices depColumnIndex detection (#3692)', () => {
|
||||
test('depColumnIndex is detected from header row', () => {
|
||||
assert.match(roadmapSlicesSrc, /depColumnIndex/,
|
||||
'depColumnIndex variable should exist in roadmap-slices.ts');
|
||||
assert.match(roadmapSlicesSrc, /headerCells/,
|
||||
'headerCells should be parsed from the header row');
|
||||
assert.match(roadmapSlicesSrc, /depends|deps|depend/i,
|
||||
'header detection should match depends/deps/depend');
|
||||
});
|
||||
});
|
||||
|
|
@ -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: "",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue