fix: resolve TypeScript type errors in gsd extension files (#204) (#211)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
TÂCHES 2026-03-13 11:34:34 -06:00 committed by GitHub
parent 5896cd2e2a
commit 940b6a38dc
10 changed files with 54 additions and 24 deletions

View file

@ -1034,6 +1034,14 @@ async function dispatchNextUnit(
return;
}
// Guard: mid/midTitle must be defined strings from this point onward.
// The !mid check above returns early if mid is falsy; midTitle comes from
// the same object so it should always be present when mid is.
if (!midTitle) {
await stopAuto(ctx, pi);
return;
}
// ── General merge guard: merge completed slice branches before advancing ──
// If we're on a gsd/MID/SID branch and that slice is done (roadmap [x]),
// merge to main before dispatching the next unit. This handles:
@ -1106,6 +1114,17 @@ async function dispatchNextUnit(
}
}
// After merge, mid/midTitle may have been re-derived and could be undefined
if (!mid || !midTitle) {
if (currentUnit) {
const modelId = ctx.model?.id ?? "unknown";
snapshotUnitMetrics(ctx, currentUnit.type, currentUnit.id, currentUnit.startedAt, modelId);
saveActivityLog(ctx, basePath, currentUnit.type, currentUnit.id);
}
await stopAuto(ctx, pi);
return;
}
// Determine next unit
let unitType: string;
let unitId: string;
@ -1540,9 +1559,9 @@ async function dispatchNextUnit(
// soft timeout; only idle/stalled tasks pause early.
clearUnitTimeout();
const supervisor = resolveAutoSupervisorConfig();
const softTimeoutMs = supervisor.soft_timeout_minutes * 60 * 1000;
const idleTimeoutMs = supervisor.idle_timeout_minutes * 60 * 1000;
const hardTimeoutMs = supervisor.hard_timeout_minutes * 60 * 1000;
const softTimeoutMs = (supervisor.soft_timeout_minutes ?? 0) * 60 * 1000;
const idleTimeoutMs = (supervisor.idle_timeout_minutes ?? 0) * 60 * 1000;
const hardTimeoutMs = (supervisor.hard_timeout_minutes ?? 0) * 60 * 1000;
wrapupWarningHandle = setTimeout(() => {
wrapupWarningHandle = null;

View file

@ -415,7 +415,7 @@ async function handlePrefsWizard(
await saveFile(path, content);
await ctx.waitForIdle();
await ctx.reload();
ctx.ui.notify(`Saved ${scope} preferences to ${path}`, "success");
ctx.ui.notify(`Saved ${scope} preferences to ${path}`, "info");
}
/** Wrap a YAML value in double quotes if it contains special characters. */

View file

@ -79,7 +79,7 @@ function validatePreferenceShape(preferences: GSDPreferences): string[] {
issues.push(`skill_rules[${index}].when must be a string`);
}
for (const key of ["use", "prefer", "avoid"] as const) {
const value = (rule as Record<string, unknown>)[key];
const value = (rule as unknown as Record<string, unknown>)[key];
if (value !== undefined && !Array.isArray(value)) {
issues.push(`skill_rules[${index}].${key} must be a list`);
}

View file

@ -127,7 +127,7 @@ export default function (pi: ExtensionAPI) {
...params,
timeout: params.timeout ?? DEFAULT_BASH_TIMEOUT_SECS,
};
return baseBash.execute(toolCallId, paramsWithTimeout, signal, onUpdate, ctx);
return (baseBash as any).execute(toolCallId, paramsWithTimeout, signal, onUpdate, ctx);
},
};
pi.registerTool(dynamicBash as any);
@ -148,7 +148,7 @@ export default function (pi: ExtensionAPI) {
ctx?: any,
) => {
const fresh = createWriteTool(process.cwd());
return fresh.execute(toolCallId, params, signal, onUpdate, ctx);
return (fresh as any).execute(toolCallId, params, signal, onUpdate, ctx);
},
};
pi.registerTool(dynamicWrite as any);
@ -164,7 +164,7 @@ export default function (pi: ExtensionAPI) {
ctx?: any,
) => {
const fresh = createReadTool(process.cwd());
return fresh.execute(toolCallId, params, signal, onUpdate, ctx);
return (fresh as any).execute(toolCallId, params, signal, onUpdate, ctx);
},
};
pi.registerTool(dynamicRead as any);
@ -180,7 +180,7 @@ export default function (pi: ExtensionAPI) {
ctx?: any,
) => {
const fresh = createEditTool(process.cwd());
return fresh.execute(toolCallId, params, signal, onUpdate, ctx);
return (fresh as any).execute(toolCallId, params, signal, onUpdate, ctx);
},
};
pi.registerTool(dynamicEdit as any);
@ -339,7 +339,7 @@ export default function (pi: ExtensionAPI) {
"errorMessage" in lastMsg && lastMsg.errorMessage
? `: ${lastMsg.errorMessage}`
: "";
ctx.log(`Auto-mode paused due to provider error${errorDetail}`);
(ctx as any).log(`Auto-mode paused due to provider error${errorDetail}`);
await pauseAuto(ctx, pi);
return;
}

View file

@ -625,7 +625,7 @@ function validatePreferences(preferences: GSDPreferences): {
}
const validatedRule: GSDSkillRule = { when };
for (const action of SKILL_ACTIONS) {
const values = normalizeStringList((rule as Record<string, unknown>)[action]);
const values = normalizeStringList((rule as unknown as Record<string, unknown>)[action]);
if (values.length > 0) {
validatedRule[action as keyof GSDSkillRule] = values as never;
}

View file

@ -354,8 +354,8 @@ async function main(): Promise<void> {
assert(state.phase !== undefined, 'pipeline: deriveState returns phase');
assert(state.activeMilestone !== null, 'pipeline: deriveState has activeMilestone');
assertEq(state.activeMilestone!.id, 'M001', 'pipeline: deriveState activeMilestone is M001');
assert(state.progress.slices !== undefined, 'pipeline: deriveState has slices progress');
assert(state.progress.tasks !== undefined, 'pipeline: deriveState has tasks progress');
assert(state.progress!.slices !== undefined, 'pipeline: deriveState has slices progress');
assert(state.progress!.tasks !== undefined, 'pipeline: deriveState has tasks progress');
} finally {
rmSync(base, { recursive: true, force: true });

View file

@ -317,7 +317,7 @@ function makeResearch(fileName: string, content: string): PlanningResearch {
assertEq(doneSlice?.tasks[0]?.summary?.duration, '2h', 'completion: summary duration from frontmatter');
assertEq(doneSlice?.tasks[0]?.summary?.provides, ['feature-01'], 'completion: summary provides from frontmatter');
assertEq(doneSlice?.tasks[0]?.summary?.keyFiles, ['file-01.ts'], 'completion: summary keyFiles from frontmatter');
assert(doneSlice?.tasks[0]?.summary?.whatHappened?.includes('Summary body'), 'completion: summary whatHappened from body');
assert(doneSlice?.tasks[0]?.summary?.whatHappened?.includes('Summary body') ?? false, 'completion: summary whatHappened from body');
assert(doneSlice?.summary !== null, 'completion: done slice has slice summary');
assert(activeSlice?.summary === null, 'completion: active slice has null summary');
assertEq(doneSlice?.tasks[0]?.estimate, '2h', 'completion: task estimate from summary duration');

View file

@ -234,18 +234,18 @@ async function main(): Promise<void> {
assertEq(state.activeSlice!.id, 'S02', 'incomplete: deriveState activeSlice is S02');
assert(state.activeTask !== null, 'incomplete: deriveState has activeTask');
assertEq(state.activeTask!.id, 'T03', 'incomplete: deriveState activeTask is T03');
assert(state.progress.slices !== undefined, 'incomplete: deriveState has slices progress');
assertEq(state.progress.slices!.done, 1, 'incomplete: deriveState slices done count');
assertEq(state.progress.slices!.total, 2, 'incomplete: deriveState slices total count');
assert(state.progress.tasks !== undefined, 'incomplete: deriveState has tasks progress');
assert(state.progress!.slices !== undefined, 'incomplete: deriveState has slices progress');
assertEq(state.progress!.slices!.done, 1, 'incomplete: deriveState slices done count');
assertEq(state.progress!.slices!.total, 2, 'incomplete: deriveState slices total count');
assert(state.progress!.tasks !== undefined, 'incomplete: deriveState has tasks progress');
// S02 has 1 task, 0 done (only active slice tasks counted)
assertEq(state.progress.tasks!.done, 0, 'incomplete: deriveState tasks done (in active slice)');
assertEq(state.progress.tasks!.total, 1, 'incomplete: deriveState tasks total (in active slice)');
assertEq(state.progress!.tasks!.done, 0, 'incomplete: deriveState tasks done (in active slice)');
assertEq(state.progress!.tasks!.total, 1, 'incomplete: deriveState tasks total (in active slice)');
// Requirements
assertEq(state.requirements.active, 1, 'incomplete: deriveState requirements active');
assertEq(state.requirements.validated, 1, 'incomplete: deriveState requirements validated');
assertEq(state.requirements.deferred, 1, 'incomplete: deriveState requirements deferred');
assertEq(state.requirements.outOfScope, 1, 'incomplete: deriveState requirements outOfScope');
assertEq(state.requirements!.active, 1, 'incomplete: deriveState requirements active');
assertEq(state.requirements!.validated, 1, 'incomplete: deriveState requirements validated');
assertEq(state.requirements!.deferred, 1, 'incomplete: deriveState requirements deferred');
assertEq(state.requirements!.outOfScope, 1, 'incomplete: deriveState requirements outOfScope');
// (f) generatePreview
console.log(' --- generatePreview ---');

View file

@ -176,6 +176,7 @@ export interface GSDState {
blockers: string[];
nextAction: string;
activeBranch?: string;
activeWorkspace?: string;
registry: MilestoneRegistryEntry[];
requirements?: RequirementCounts;
progress?: {

10
tsconfig.extensions.json Normal file
View file

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"allowImportingTsExtensions": true,
"rootDir": "."
},
"include": ["src/resources/extensions/gsd"],
"exclude": []
}