Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5896cd2e2a
commit
940b6a38dc
10 changed files with 54 additions and 24 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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 ---');
|
||||
|
|
|
|||
|
|
@ -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
10
tsconfig.extensions.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["src/resources/extensions/gsd"],
|
||||
"exclude": []
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue