fix: continue question selection after graph updates

This commit is contained in:
2026-08-03 11:28:49 +01:00
parent b00928d6fb
commit 3e2edd2edc
7 changed files with 509 additions and 2 deletions
+111
View File
@@ -1,4 +1,5 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { applyValidatedProposal } from "@/lib/graph/apply-proposal.js";
import { validateGraphReferences } from "@/lib/graph/utils.js";
import { makeGraph, makeNode } from "@/lib/graph/schema.js";
@@ -91,6 +92,29 @@ function makeCommercialAnalysisResult(overrides = {}) {
});
}
function makeCommercialUpdateGraph() {
const parent = makeNode({
id: "n-commercial-parent",
label:
"Commercial justification for whether continuing development is commercially justified",
description:
"Need to know whether this solves a genuine problem, whether people would value it enough to pay for it, and whether it is commercially justified before continuing development.",
kind: "unknown",
status: "unknown",
confidence: "medium",
});
return makeGraph({
centralStatement:
"I have developed a new reasoning method that aims to help people determine whether they have enough justified confidence to make a decision. I believe it could become a commercial product, but I do not yet know whether it solves a genuine problem, whether people would value it enough to pay for it, or whether it is fundamentally different from existing AI tools. Before investing significant time and money into building it further, I want to determine whether continuing development is commercially justified.",
nodes: [parent],
edges: [],
activeUnknownNodeId: parent.id,
resolvedNodeIds: [],
currentSummary: "Commercial update scenario",
});
}
function makeUpdateGraph() {
const unknown = makeNode({
id: "n-unknown",
@@ -1168,6 +1192,93 @@ describe("lib/graph/orchestrator startCase", () => {
);
});
it("reselects the other-people sibling after the first commercial child is answered", async () => {
const { updateCase } = await import("@/lib/graph/orchestrator.js");
const seededGraph = applyValidatedProposal({
situationGraph: makeCommercialUpdateGraph(),
proposal: {
addedNodes: [
makeNode({
id: "n-anchor",
label: "Update anchor",
description:
"Anchor state introduced by the answer because the update must contain a meaningful change.",
kind: "state",
status: "known",
confidence: "low",
}),
],
updatedNodes: [],
addedEdges: [],
removedEdgeIds: [],
resolvedUnknownNodeIds: [],
affectedNodeIds: [],
selectedQuestion: null,
},
});
expect(seededGraph.success).toBe(true);
const resolvedFirstChildNodeId = seededGraph.selectedQuestion?.nodeId;
const resolvedFirstQuestion = seededGraph.selectedQuestion?.question;
const initial = await updateCase(
{
situationGraph: seededGraph.updatedSituationGraph,
previousQuestion: resolvedFirstQuestion,
answer:
"I experience it myself when I am trying to decide whether a project, idea or investment is justified, but I do not yet know how common that problem is for other people.",
promptVersion: "v0.4",
},
{
applyProposal: true,
config: MOCK_CONFIG,
provider: {
generateReconstruction: vi.fn().mockResolvedValue({
addedNodes: [],
updatedNodes: [
{
nodeId: resolvedFirstChildNodeId,
previousStatus: "unknown",
newStatus: "resolved",
previousValue: null,
newValue:
"I experience it myself when I am trying to decide whether a project, idea or investment is justified, but I do not yet know how common that problem is for other people.",
reason: "The answer confirms a self-observed instance.",
},
],
addedEdges: [],
removedEdgeIds: [],
resolvedUnknownNodeIds: [resolvedFirstChildNodeId],
affectedNodeIds: [],
selectedQuestion: null,
}),
},
},
);
expect(initial.success).toBe(true);
expect(initial.selectedQuestion?.nodeId).toBe(
initial.newActiveUnknownNodeId,
);
expect(initial.selectedQuestion?.question).toBe(
"What makes you think other people experience this problem too?",
);
expect(initial.selectedQuestion?.reasoningPattern).toBe("decision");
expect(initial.diagnostics.unresolvedCandidateCount).toBeGreaterThan(0);
expect(initial.diagnostics.eligibleCandidateCount).toBeGreaterThan(0);
expect(initial.diagnostics.candidateNodeIds).toContain(
initial.selectedQuestion?.nodeId,
);
expect(initial.diagnostics.resolvedCurrentTurnNodeIds).toContain(
resolvedFirstChildNodeId,
);
expect(initial.diagnostics.noQuestionReason).toBeNull();
expect(initial.selectedQuestion?.question.toLowerCase()).not.toMatch(
/price|budget|market size|pilot metrics|benchmark|technical differentiation/,
);
});
it("startCase no longer copies analysis nextQuestion directly when a graph-backed question exists", async () => {
mockAnalyseScenario.mockResolvedValue(makeAnalysisResult());
const { startCase } = await import("@/lib/graph/orchestrator.js");