fix(gsd): tolerate corrupt task arrays (#4056)
This commit is contained in:
parent
e6110976e7
commit
3c44e3d4e2
2 changed files with 78 additions and 3 deletions
|
|
@ -1564,6 +1564,23 @@ export interface TaskRow {
|
|||
sequence: number;
|
||||
}
|
||||
|
||||
function parseTaskArrayColumn(raw: unknown): string[] {
|
||||
if (typeof raw !== "string" || raw.trim() === "") return [];
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
if (Array.isArray(parsed)) return parsed.map((value) => String(value));
|
||||
if (parsed === null || parsed === undefined || parsed === "") return [];
|
||||
return [String(parsed)];
|
||||
} catch {
|
||||
// Older/corrupt rows may contain comma-separated strings instead of JSON.
|
||||
return raw
|
||||
.split(",")
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
}
|
||||
|
||||
function rowToTask(row: Record<string, unknown>): TaskRow {
|
||||
const parseTaskArray = (value: unknown): string[] => {
|
||||
if (Array.isArray(value)) {
|
||||
|
|
@ -1603,8 +1620,8 @@ function rowToTask(row: Record<string, unknown>): TaskRow {
|
|||
blocker_discovered: (row["blocker_discovered"] as number) === 1,
|
||||
deviations: row["deviations"] as string,
|
||||
known_issues: row["known_issues"] as string,
|
||||
key_files: JSON.parse((row["key_files"] as string) || "[]"),
|
||||
key_decisions: JSON.parse((row["key_decisions"] as string) || "[]"),
|
||||
key_files: parseTaskArrayColumn(row["key_files"]),
|
||||
key_decisions: parseTaskArrayColumn(row["key_decisions"]),
|
||||
full_summary_md: row["full_summary_md"] as string,
|
||||
description: (row["description"] as string) ?? "",
|
||||
estimate: (row["estimate"] as string) ?? "",
|
||||
|
|
|
|||
|
|
@ -15,10 +15,14 @@ import {
|
|||
getRequirementById,
|
||||
getActiveDecisions,
|
||||
getActiveRequirements,
|
||||
getTask,
|
||||
transaction,
|
||||
_getAdapter,
|
||||
_resetProvider,
|
||||
insertMilestone,
|
||||
insertSlice,
|
||||
insertTask,
|
||||
getTask,
|
||||
getSliceTasks,
|
||||
} from '../gsd-db.ts';
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
|
@ -460,6 +464,60 @@ describe('gsd-db', () => {
|
|||
assert.ok(!wasDbOpenAttempted(), 'wasDbOpenAttempted should reset after closeDatabase');
|
||||
});
|
||||
|
||||
test('gsd-db: rowToTask tolerates corrupt comma-separated task arrays', () => {
|
||||
openDatabase(':memory:');
|
||||
insertMilestone({ id: 'M001', status: 'active' });
|
||||
insertSlice({ milestoneId: 'M001', id: 'S01', status: 'active' });
|
||||
insertTask({
|
||||
milestoneId: 'M001',
|
||||
sliceId: 'S01',
|
||||
id: 'T01',
|
||||
title: 'Recover corrupt arrays',
|
||||
planning: {
|
||||
description: 'desc',
|
||||
estimate: 'small',
|
||||
files: ['src/original.ts'],
|
||||
verify: 'npm test',
|
||||
inputs: ['docs/original.md'],
|
||||
expectedOutput: ['dist/original.md'],
|
||||
observabilityImpact: '',
|
||||
},
|
||||
});
|
||||
|
||||
const adapter = _getAdapter()!;
|
||||
adapter.prepare(
|
||||
`UPDATE tasks
|
||||
SET files = ?, inputs = ?, expected_output = ?, key_files = ?, key_decisions = ?
|
||||
WHERE milestone_id = ? AND slice_id = ? AND id = ?`,
|
||||
).run(
|
||||
'src-erf/Models/foo.cs, src-erf/Models/bar.cs',
|
||||
'docs/input-a.md, docs/input-b.md',
|
||||
'dist/out-a.md, dist/out-b.md',
|
||||
'src/resources/extensions/gsd/gsd-db.ts, src/resources/extensions/gsd/state.ts',
|
||||
'"decision-1"',
|
||||
'M001',
|
||||
'S01',
|
||||
'T01',
|
||||
);
|
||||
|
||||
const task = getTask('M001', 'S01', 'T01');
|
||||
assert.ok(task, 'getTask should still return the corrupt row');
|
||||
assert.deepStrictEqual(task!.files, ['src-erf/Models/foo.cs', 'src-erf/Models/bar.cs']);
|
||||
assert.deepStrictEqual(task!.inputs, ['docs/input-a.md', 'docs/input-b.md']);
|
||||
assert.deepStrictEqual(task!.expected_output, ['dist/out-a.md', 'dist/out-b.md']);
|
||||
assert.deepStrictEqual(
|
||||
task!.key_files,
|
||||
['src/resources/extensions/gsd/gsd-db.ts', 'src/resources/extensions/gsd/state.ts'],
|
||||
);
|
||||
assert.deepStrictEqual(task!.key_decisions, ['decision-1']);
|
||||
|
||||
const sliceTasks = getSliceTasks('M001', 'S01');
|
||||
assert.equal(sliceTasks.length, 1, 'getSliceTasks should also survive corrupt rows');
|
||||
assert.deepStrictEqual(sliceTasks[0]!.files, task!.files);
|
||||
|
||||
closeDatabase();
|
||||
});
|
||||
|
||||
// ─── Final Report ──────────────────────────────────────────────────────────
|
||||
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue