fix(start-work): prefer nested session plan refs

This commit is contained in:
Häcker, Henning
2026-04-24 16:16:22 +02:00
parent d22bd71d0d
commit 771242ea3d
2 changed files with 70 additions and 5 deletions
+49
View File
@@ -281,6 +281,55 @@ You are starting a Sisyphus work session.
expect(state?.active_plan).toBe(newPlanPath)
})
test("should still find nested plan references when direct input fields contain a different plan path", async () => {
// given - direct path points to plan-a but nested serialized input also references newer plan-b
const plansDir = join(testDir, ".sisyphus", "plans")
mkdirSync(plansDir, { recursive: true })
const planAPath = join(plansDir, "plan-a.md")
const planBPath = join(plansDir, "plan-b.md")
writeFileSync(planAPath, "# Plan A\n- [ ] Task A")
writeFileSync(planBPath, "# Plan B\n- [ ] Task B")
const hook = createStartWorkHook({
directory: testDir,
client: {
session: {
messages: async () => ({
data: [
{
parts: [
{
input: {
path: `Legacy reference ${planAPath}`,
metadata: {
selectedPlan: `Current reference ${planBPath}`,
},
},
},
],
},
],
}),
},
},
} as Parameters<typeof createStartWorkHook>[0])
const output = {
parts: [{ type: "text", text: createStartWorkPrompt() }],
}
// when
await hook["chat.message"](
{ sessionID: "session-123" },
output,
)
// then - latest nested reference should still be discoverable and selected
const state = readBoulderState(testDir)
expect(state?.active_plan).toBe(planBPath)
expect(output.parts[0].text).toContain("plan-b")
})
test("should replace $SESSION_ID placeholder", async () => {
// given - hook and message with placeholder
const hook = createStartWorkHook(createMockPluginInput())
+21 -5
View File
@@ -29,20 +29,36 @@ function extractPlanPathsFromText(directory: string, text: string): string[] {
return matches.map((match) => normalizePlanPath(directory, match))
}
function extractPlanPathsFromValue(directory: string, value: unknown): string[] {
if (typeof value === "string") {
return extractPlanPathsFromText(directory, value)
}
if (Array.isArray(value)) {
return value.flatMap((item) => extractPlanPathsFromValue(directory, item))
}
if (value && typeof value === "object") {
return Object.values(value).flatMap((item) => extractPlanPathsFromValue(directory, item))
}
return []
}
function extractPlanPathsFromInput(directory: string, input: Record<string, unknown> | undefined): string[] {
if (!input) {
return []
}
const nestedCandidates = Object.entries(input)
.filter(([key]) => key !== "filePath" && key !== "path" && key !== "file")
.flatMap(([, value]) => extractPlanPathsFromValue(directory, value))
const directCandidates = [input.filePath, input.path, input.file]
.filter((value): value is string => typeof value === "string")
.flatMap((value) => extractPlanPathsFromText(directory, value))
if (directCandidates.length > 0) {
return directCandidates
}
return extractPlanPathsFromText(directory, JSON.stringify(input))
return [...new Set([...nestedCandidates, ...directCandidates])]
}
export async function findRecentSessionPlanPath(input: {