FN-8539: make Planning Mode selection-driven
Make Planning Mode refine repository-grounded plans through successive operator choices. - Offer concrete directions and Other for vague planning openers - Rebuild running plans around selected options, multi-selections, and Other responses - Add selection-loop coverage and operator documentation Files changed: .changeset/fn-8539-option-driven-planning.md | 7 ++ docs/dashboard-guide.md | 2 +- .../__tests__/planning-infinite-interview.test.ts | 131 ++++++++++++++++++++- .../planning-interview-formatters.test.ts | 37 ++++-- packages/dashboard/src/planning.ts | 34 +++--- 5 files changed, 185 insertions(+), 26 deletions(-) Fusion-Task-Id: FN-8539 Fusion-Task-Lineage: 585bae1e-6d9e-4733-b787-77b70a7d1259 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8539-option-driven-planning.md
Normal file
7
.changeset/fn-8539-option-driven-planning.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Make Planning Mode refine plans through codebase-grounded direction choices.
|
||||
category: feature
|
||||
dev: Selected directions and Other responses now rebuild the running-plan backbone before the next narrowing question.
|
||||
@@ -2223,7 +2223,7 @@ If the endpoint is unavailable on the running dashboard build, the response will
|
||||
|
||||
<!-- FNXC:PlanningMode 2026-07-20-01:00: Planning interviews are always infinite and user-validated. The former follow-up toggle cannot suppress questions or produce a final summary; the dashboard starts each interview in the full questioning mode. -->
|
||||
|
||||
Planning Mode asks another focused question after every answer until you select **Validate plan**. Each `awaiting_input` question can send the configured `planning-awaiting-input` ntfy event and delivers a dashboard mailbox message that links the operator back to the Planning view. Mailbox delivery does not depend on ntfy configuration and is deduplicated by session/question across restarts.
|
||||
Planning Mode asks another focused question after every answer until you select **Validate plan**. For a vague, subjective, preference-style, or symptom-only opener, it first inspects the relevant repository surface and offers materially distinct, concrete directions plus **Other** instead of an abstract clarification question. Each selected direction, multi-selection, or verbatim Other response rebuilds the evolving plan around that accumulated decision; the next question then narrows the selected direction one consequential level further with concrete options. The provisional plan does not falsely commit to an unselected alternative, and only the operator can validate the finished plan. Each `awaiting_input` question can send the configured `planning-awaiting-input` ntfy event.
|
||||
|
||||
### Mobile footer quick actions
|
||||
|
||||
|
||||
@@ -102,6 +102,34 @@ const SECOND_QUESTION = {
|
||||
],
|
||||
};
|
||||
|
||||
const BACKGROUND_DIRECTIONS = {
|
||||
id: "background-direction", type: "single_select", question: "Which background direction should the dashboard take?",
|
||||
description: "Repository inspection found the dashboard's shared background tokens and visual-effects surface.",
|
||||
options: [
|
||||
{ id: "change-color", label: "Change the background color", pros: ["Keeps rendering simple"], cons: ["Adds little depth"] },
|
||||
{ id: "add-effects", label: "Add effects to the background", pros: ["Creates a distinctive atmosphere"], cons: ["Needs performance guardrails"] },
|
||||
{ id: "other", label: "Other (write your own)", isOther: true },
|
||||
],
|
||||
};
|
||||
|
||||
const EFFECT_TYPES = {
|
||||
id: "effect-type", type: "single_select", question: "What type of background effects should we add?",
|
||||
options: [
|
||||
{ id: "3d", label: "3D effects", pros: ["Adds spatial depth"], cons: ["Can increase GPU work"] },
|
||||
{ id: "light", label: "Light effects", pros: ["Keeps the interface subtle"], cons: ["May be less dramatic"] },
|
||||
{ id: "other", label: "Other (write your own)", isOther: true },
|
||||
],
|
||||
};
|
||||
|
||||
const EFFECT_INTENSITY = {
|
||||
id: "effect-intensity", type: "single_select", question: "How prominent should the selected light effects be?",
|
||||
options: [
|
||||
{ id: "subtle", label: "Subtle ambient light", pros: ["Protects readability"], cons: ["Has a quieter visual impact"] },
|
||||
{ id: "expressive", label: "Expressive animated light", pros: ["Makes the background more visible"], cons: ["Needs motion safeguards"] },
|
||||
{ id: "other", label: "Other (write your own)", isOther: true },
|
||||
],
|
||||
};
|
||||
|
||||
describe("reactive Planning Mode question contract", () => {
|
||||
beforeEach(() => {
|
||||
__resetPlanningState();
|
||||
@@ -172,7 +200,7 @@ describe("reactive Planning Mode question contract", () => {
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/Proceed with plan serializes the plan as plan\.md/i);
|
||||
});
|
||||
|
||||
it("defines a collaborative planning contract rather than an execution-task specification", () => {
|
||||
it("defines a collaborative selection-led narrowing contract rather than an execution-task specification", () => {
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/investigate relevant repository and active-board context/i);
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/assumptions, unknowns, constraints/i);
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/Compare viable approaches and their trade-offs/i);
|
||||
@@ -181,6 +209,11 @@ describe("reactive Planning Mode question contract", () => {
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/not an executor-ready task specification/i);
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/do not automatically split work/i);
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/Do not produce task-specification bookkeeping.*commit guidance.*no-code-change caveats.*task-creation directives/i);
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/vague, subjective, preference-based, or symptom-only/i);
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/materially distinct actionable directions/i);
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/durable decision/i);
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/selected direction—not the original vague complaint or an unselected alternative—the central intended outcome/i);
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/narrows it one level further/i);
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/respond only with JSON/i);
|
||||
});
|
||||
|
||||
@@ -487,6 +520,102 @@ describe("reactive Planning Mode question contract", () => {
|
||||
expect(await getSession(created.sessionId)).toMatchObject({ validated: true, currentQuestion: undefined });
|
||||
});
|
||||
|
||||
it("rebuilds a non-streaming vague-background plan around successive selected directions", async () => {
|
||||
installScriptedAgent([
|
||||
payload({
|
||||
...BACKGROUND_DIRECTIONS,
|
||||
runningPlan: {
|
||||
title: "Improve dashboard background direction",
|
||||
description: "Evaluate the inspected shared background surfaces before choosing a color change or visual effects.",
|
||||
proposedChanges: ["Inspect shared background tokens and effects surfaces"],
|
||||
acceptanceCriteria: ["The selected direction is reflected in the plan"],
|
||||
keyDeliverables: ["Choose a background direction"],
|
||||
},
|
||||
}),
|
||||
payload({
|
||||
...EFFECT_TYPES,
|
||||
runningPlan: {
|
||||
title: "Add effects to the dashboard background",
|
||||
description: "Add visual effects to the dashboard background instead of changing its color.",
|
||||
proposedChanges: ["Add performant background effects to the shared dashboard surface"],
|
||||
acceptanceCriteria: ["The dashboard background renders the chosen effects without replacing its color strategy"],
|
||||
keyDeliverables: ["Implement background effects", "Verify background-effect performance"],
|
||||
},
|
||||
}),
|
||||
payload({
|
||||
...EFFECT_INTENSITY,
|
||||
runningPlan: {
|
||||
title: "Add light effects to the dashboard background",
|
||||
description: "Add light effects to the dashboard background with the selected effects direction retained.",
|
||||
proposedChanges: ["Add light-based background effects to the shared dashboard surface"],
|
||||
acceptanceCriteria: ["Light effects render without a color-change implementation"],
|
||||
keyDeliverables: ["Implement light background effects", "Verify light-effect performance"],
|
||||
},
|
||||
}),
|
||||
]);
|
||||
|
||||
const created = await createSession("127.0.0.31", "I don't like the black background", MOCK_TASK_STORE, "/tmp/project");
|
||||
expect(created.firstQuestion).toMatchObject({ id: "background-direction" });
|
||||
expect(created.firstQuestion.options).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ label: "Change the background color" }),
|
||||
expect.objectContaining({ label: "Add effects to the background" }),
|
||||
]));
|
||||
expect(created.summary.description).toContain("before choosing a color change or visual effects");
|
||||
|
||||
await submitResponse(created.sessionId, { "background-direction": "add-effects" }, "/tmp/project", undefined, MOCK_TASK_STORE);
|
||||
let session = await getSession(created.sessionId);
|
||||
expect(session?.summary).toMatchObject({
|
||||
title: "Add effects to the dashboard background",
|
||||
description: expect.stringContaining("instead of changing its color"),
|
||||
proposedChanges: ["Add performant background effects to the shared dashboard surface"],
|
||||
keyDeliverables: ["Implement background effects", "Verify background-effect performance"],
|
||||
});
|
||||
expect(session?.currentQuestion).toMatchObject({ id: "effect-type", question: expect.stringMatching(/type of background effects/i) });
|
||||
expect(session?.currentQuestion?.options).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ label: "3D effects" }),
|
||||
expect.objectContaining({ label: "Light effects" }),
|
||||
]));
|
||||
|
||||
await submitResponse(created.sessionId, { "effect-type": "light" }, "/tmp/project", undefined, MOCK_TASK_STORE);
|
||||
session = await getSession(created.sessionId);
|
||||
expect(session?.summary).toMatchObject({
|
||||
title: "Add light effects to the dashboard background",
|
||||
description: expect.stringContaining("selected effects direction retained"),
|
||||
proposedChanges: ["Add light-based background effects to the shared dashboard surface"],
|
||||
});
|
||||
expect(session?.summary?.description).not.toContain("Change the background color");
|
||||
expect(session?.history).toHaveLength(2);
|
||||
expect(session?.currentQuestion).toMatchObject({ id: "effect-intensity" });
|
||||
expect(session?.validated).toBe(false);
|
||||
});
|
||||
|
||||
it("persists the same selected-direction plan through streaming session recreation", async () => {
|
||||
installScriptedAgent([
|
||||
payload({ ...BACKGROUND_DIRECTIONS, runningPlan: { title: "Improve dashboard background direction", description: "Choose a repository-grounded dashboard background direction.", keyDeliverables: ["Choose a background direction"] } }),
|
||||
payload({ ...EFFECT_TYPES, runningPlan: { title: "Add effects to the dashboard background", description: "Add effects to the background after the operator selected that direction.", proposedChanges: ["Add background effects"], keyDeliverables: ["Implement background effects"] } }),
|
||||
]);
|
||||
const sessionId = await createSessionWithAgent("127.0.0.32", "I don't like the black background", "/tmp/project", MOCK_TASK_STORE);
|
||||
const initialQuestionReady = new Promise<void>((resolveQuestion) => {
|
||||
planningStreamManager.subscribe(sessionId, (event) => {
|
||||
if (event.type === "question") resolveQuestion();
|
||||
});
|
||||
});
|
||||
planningStreamManager.consumeInitialTurn(sessionId)?.();
|
||||
await initialQuestionReady;
|
||||
await new Promise<void>((resolveTurn) => setImmediate(resolveTurn));
|
||||
await submitResponse(sessionId, { "background-direction": "add-effects" }, "/tmp/project", undefined, MOCK_TASK_STORE);
|
||||
|
||||
const session = await getSession(sessionId);
|
||||
expect(session?.summary).toMatchObject({
|
||||
title: "Add effects to the dashboard background",
|
||||
description: expect.stringContaining("operator selected that direction"),
|
||||
proposedChanges: ["Add background effects"],
|
||||
});
|
||||
expect(session?.history).toEqual([expect.objectContaining({ response: { "background-direction": "add-effects" } })]);
|
||||
expect(session?.currentQuestion).toMatchObject({ id: "effect-type" });
|
||||
expect(agentSystemPrompts).toEqual([PLANNING_SYSTEM_PROMPT]);
|
||||
});
|
||||
|
||||
it("uses a model-authored initial plan on the non-streaming first turn", async () => {
|
||||
const prompts = installScriptedAgent([payload({
|
||||
...FIRST_QUESTION,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { PlanningQuestion } from "@fusion/core";
|
||||
import {
|
||||
formatInitialPlanRequestForAgent,
|
||||
formatInitialRunningPlanRequestForAgent,
|
||||
formatInterviewQA,
|
||||
formatResponseForAgent,
|
||||
normalizePlanningSummaryPayload,
|
||||
@@ -56,6 +58,17 @@ describe("normalizePlanningSummaryPayload", () => {
|
||||
});
|
||||
|
||||
describe("planning interview formatter Other answers", () => {
|
||||
it("makes vague openers request inspected, unselected first-level directions", () => {
|
||||
for (const prompt of [
|
||||
formatInitialPlanRequestForAgent("I don't like the black background"),
|
||||
formatInitialRunningPlanRequestForAgent("I don't like the black background"),
|
||||
]) {
|
||||
expect(prompt).toMatch(/vague, subjective, preference-based, or symptom-only/i);
|
||||
expect(prompt).toMatch(/inspect the relevant implementation surface|Inspect the relevant codebase/i);
|
||||
expect(prompt).toMatch(/materially distinct first-level directions|materially distinct direction options/i);
|
||||
expect(prompt).toMatch(/unselected direction/i);
|
||||
}
|
||||
});
|
||||
it("formats Other-only single-select answers for the planning agent and Q&A history", () => {
|
||||
const response = { scope: "other", _other: "Run discovery first" };
|
||||
const agent = formatResponseForAgent(singleSelectQuestion, response);
|
||||
@@ -88,16 +101,24 @@ describe("planning interview formatter Other answers", () => {
|
||||
expect(qa).toContain("Need more context");
|
||||
});
|
||||
|
||||
it("reasserts the infinite, high-impact next-question contract with every answer", () => {
|
||||
/*
|
||||
FNXC:DashboardTests 2026-07-22-03:20:
|
||||
Response key must match the question id (`scope`). Product copy asks for exactly one
|
||||
next question and user-only proceed; keep the infinite-loop contract without pinning
|
||||
retired "new/high-impact/does not repeat" phrasing.
|
||||
*/
|
||||
it("makes a standard selected option the durable plan backbone before one deeper question", () => {
|
||||
const prompt = formatResponseForAgent(singleSelectQuestion, { scope: "mvp" });
|
||||
|
||||
expect(prompt).toMatch(/exactly one (?:new, high-impact |high-impact )?next question|exactly one next question/i);
|
||||
expect(prompt).toContain("Selected: MVP");
|
||||
expect(prompt).toMatch(/durable planning decision/i);
|
||||
expect(prompt).toMatch(/selected direction is the central intended outcome/i);
|
||||
expect(prompt).toMatch(/exactly one next question: a deeper concrete option-driven question/i);
|
||||
expect(prompt).toMatch(/only the user can (?:validate|proceed)/i);
|
||||
});
|
||||
|
||||
it("carries every multi-select label and verbatim Other steering into the rebuilt plan contract", () => {
|
||||
const prompt = formatResponseForAgent(multiSelectQuestion, {
|
||||
priorities: ["speed", "quality"],
|
||||
_other: "Keep the review checkpoint",
|
||||
});
|
||||
|
||||
expect(prompt).toContain("Selected: Speed, Quality, Keep the review checkpoint (user's own answer)");
|
||||
expect(prompt).toMatch(/Preserve free-text Other verbatim as steering/i);
|
||||
expect(prompt).toMatch(/every accumulated decision/i);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -227,10 +227,10 @@ async function ensureNtfyHelpersReady(): Promise<void> {
|
||||
// ── Constants ───────────────────────────────────────────────────────────────
|
||||
|
||||
/*
|
||||
FNXC:PlanningMode 2026-07-23-11:30:
|
||||
FNXC:PlanningMode 2026-07-23-14:00:
|
||||
Planning Mode is a collaborative, user-terminated discovery session, not task triage. Its dedicated prompt must not inherit workflow or assigned-triage execution instructions, because those instructions can turn exploratory responses into executor specifications and child-task directives.
|
||||
|
||||
The model may update the running plan but must never infer completion; only the visible Proceed with plan action can make a session terminal.
|
||||
A selected direction is a durable plan-backbone decision: every affected running-plan field must be rebuilt around accumulated selections, then exactly one repository-grounded question must narrow that direction another consequential level. The model may update the running plan but must never infer completion; only the visible Proceed with plan action can make a session terminal.
|
||||
*/
|
||||
/** Self-contained system prompt for the separate collaborative Planning Mode. */
|
||||
export const PLANNING_SYSTEM_PROMPT = `## Collaborative Planning Mode
|
||||
@@ -239,7 +239,9 @@ Help the operator iteratively turn an idea into a clear, useful plan. First inve
|
||||
|
||||
Build an evolving operator-facing plan, not an executor-ready task specification. Focus on intended outcomes, concrete deliverables, alternatives considered, and observable acceptance criteria. Do not produce task-specification bookkeeping or execution-process instructions such as task-size policy, commit guidance, no-code-change caveats, or task-creation directives. Author the operator-facing plan in Markdown: write the description as concise GitHub-flavored Markdown, while the structured change, acceptance, dependency, and deliverable fields become its Markdown sections and lists.
|
||||
|
||||
Start by producing a concrete initial plan and exactly one high-impact question. After every answer, regenerate the plan and ask exactly one consequential next question. A refine turn uses the selected or free-text focus to choose that next question. The model never validates or terminates the session. Only the user can validate it through the visible Proceed with plan action.
|
||||
Use a deliberate iterative narrowing loop: analyze → concrete options → operator selection → plan rebuild → one deeper question. When the opener is vague, subjective, preference-based, or symptom-only, inspect the relevant implementation surface before proposing at least two materially distinct actionable directions grounded in those findings, plus exactly one Other option. Do not ask a generic clarification question or silently select a direction. Keep the provisional plan honest about unselected alternatives.
|
||||
|
||||
After every selected option, multi-selection, or free-text Other answer, treat the choice as a durable decision and rebuild every affected running-plan field around all accumulated decisions. The title, description, proposedChanges, acceptanceCriteria, keyDeliverables, and suggestedRefinements must make the selected direction—not the original vague complaint or an unselected alternative—the central intended outcome. Preserve Other text verbatim as steering. Then inspect the selected direction and relevant repository context and ask exactly one consequential next question that narrows it one level further with concrete, materially distinct options. A refine turn uses the selected or free-text focus to choose that next question. Continue this loop until the operator chooses Proceed with plan. The model never validates or terminates the session. Only the user can validate it through the visible Proceed with plan action.
|
||||
|
||||
For every initial, answer, or refine turn respond only with JSON: {"type":"question","data":{"id":"unique-id","type":"single_select|multi_select","question":"...","description":"...","options":[{"id":"option-a","label":"...","description":"...","pros":["..."],"cons":["..."]},{"id":"option-b","label":"...","description":"...","pros":["..."],"cons":["..."]},{"id":"other","label":"...","isOther":true}],"runningPlan":{"title":"...","description":"...","proposedChanges":["specific change"],"acceptanceCriteria":["observable outcome"],"suggestedSize":"S|M|L","priority":"normal","suggestedDependencies":[],"keyDeliverables":["concrete work item"],"suggestedRefinements":["next focus 1","next focus 2"]}}}.
|
||||
|
||||
@@ -2067,7 +2069,7 @@ function buildHistoryReplayPrompt(
|
||||
return [
|
||||
"Previous conversation summary:",
|
||||
interviewSummary,
|
||||
"Use this as context for the next response. Do not repeat prior questions unless necessary.",
|
||||
"Treat every recorded selection and Other answer as an accumulated durable decision. Rebuild affected plan fields around those decisions; do not preserve superseded or unselected alternatives as the plan backbone. Use this as context for the next response. Do not repeat prior questions unless necessary.",
|
||||
].join("\n\n");
|
||||
}
|
||||
|
||||
@@ -2358,8 +2360,9 @@ for both agent entry points because system instructions alone can be displaced b
|
||||
export function formatInitialPlanRequestForAgent(initialPlan: string): string {
|
||||
return [
|
||||
"Create the initial running plan from this operator idea before asking the first interview question.",
|
||||
"If the idea is vague, subjective, preference-based, or symptom-only, first inspect the relevant implementation surface and turn those findings into at least two concrete, materially distinct first-level directions plus exactly one Other option. Do not ask a generic question, invent repository findings, or commit the provisional plan to an unselected direction.",
|
||||
"Return only type:\"question\" JSON with a full runningPlan: a work-product title, a concise implementation description, and concrete work-item keyDeliverables derived from the idea.",
|
||||
"Then ask exactly one high-impact clarifying question with alternatives and pros/cons. Never use that question text as a deliverable. Do not complete or validate the plan; only the user can validate it.",
|
||||
"Then ask exactly one high-impact, option-driven question with alternatives and pros/cons. Never use that question text as a deliverable. Do not complete or validate the plan; only the user can validate it.",
|
||||
"Operator idea:",
|
||||
initialPlan,
|
||||
].join("\n\n");
|
||||
@@ -2370,9 +2373,9 @@ export function formatInitialRunningPlanRequestForAgent(initialPlan: string): st
|
||||
return [
|
||||
"Create a concrete initial implementation plan from this operator idea.",
|
||||
"Author the operator-facing plan in Markdown. Write the description as concise GitHub-flavored Markdown; the structured proposed changes, acceptance criteria, dependencies, and deliverables will render as Markdown sections and lists.",
|
||||
"Inspect the relevant codebase and active-board context before drafting it. Make the description specific about the affected behavior and intended outcome. Provide concrete proposedChanges that name what behavior, component, interface, data, or configuration should change, and acceptanceCriteria stated as observable pass/fail outcomes. Make every key deliverable an actionable work item rather than generic planning advice.",
|
||||
"Inspect the relevant codebase and active-board context before drafting it. For a vague, subjective, preference-based, or symptom-only idea, turn that inspection into at least two concrete, materially distinct direction options plus exactly one Other option; do not invent findings or preselect a direction. Make the provisional description specific about the affected behavior and intended outcome without falsely committing to an unselected direction. Provide concrete proposedChanges that name what behavior, component, interface, data, or configuration should change, and acceptanceCriteria stated as observable pass/fail outcomes. Make every key deliverable an actionable work item rather than generic planning advice.",
|
||||
"Also propose concise suggestedRefinements covering every distinct, high-value unresolved area the operator could explore next; do not cap the list at three.",
|
||||
"Return only type:\"question\" JSON with the complete plan in runningPlan and exactly one high-impact next question. Give that question at least two useful alternatives with pros and cons plus one write-your-own option. Do not validate the plan; only the operator can proceed with it.",
|
||||
"Return only type:\"question\" JSON with the complete plan in runningPlan and exactly one high-impact, option-driven next question. Give that question at least two useful alternatives with pros and cons plus one write-your-own option. Do not validate the plan; only the operator can proceed with it.",
|
||||
"Operator idea:",
|
||||
initialPlan,
|
||||
].join("\n\n");
|
||||
@@ -2995,8 +2998,8 @@ function getContextualComments(responses: Record<string, unknown>): ContextualCo
|
||||
export function formatContextualCommentsForAgent(summary: PlanningSummary, comments: ContextualComment[]): string {
|
||||
return [
|
||||
"The operator reviewed the running plan and submitted contextual comments.",
|
||||
"Revise the running plan using every comment below, preserve unaffected content, and continue the established Planning Mode response contract.",
|
||||
"Return the revised plan in Markdown and ask exactly one next question when more input is needed.",
|
||||
"Revise the running plan using every comment below, preserve unaffected content, and continue the established Planning Mode response contract. Treat each accepted comment as a durable decision: rebuild every affected plan field around it rather than appending contradictory notes.",
|
||||
"Return the revised plan in Markdown and ask exactly one concrete option-driven next question that narrows the updated direction when more input is needed.",
|
||||
"Current summary:",
|
||||
JSON.stringify(summary),
|
||||
"Contextual comments, in submitted order:",
|
||||
@@ -3007,8 +3010,8 @@ export function formatContextualCommentsForAgent(summary: PlanningSummary, comme
|
||||
function formatRefineRequestForAgent(summary: PlanningSummary, focus?: string): string {
|
||||
return [
|
||||
"The user clicked Refine Further on the planning summary.",
|
||||
"Continue the planning interview from the existing context.",
|
||||
"Ask exactly one focused, high-impact follow-up question with alternatives and pros/cons.",
|
||||
"Continue the planning interview from the existing context. Rebuild affected plan fields around every accumulated selection, Other answer, and requested focus; do not retain an unselected alternative as the plan backbone.",
|
||||
"Inspect the selected direction and relevant repository context, then ask exactly one focused, high-impact option-driven follow-up question that narrows it one consequential level further with alternatives and pros/cons.",
|
||||
"Do not return a completion response: only the user can validate a plan.",
|
||||
...(focus ? ["The operator wants this next question to focus on:", focus] : []),
|
||||
"Current summary:",
|
||||
@@ -3163,7 +3166,7 @@ export async function submitResponse(
|
||||
);
|
||||
}
|
||||
const message = isEditingPriorAnswer
|
||||
? "An earlier answer was edited. Use the complete preserved interview context above, regenerate the running plan, and ask exactly one next question."
|
||||
? "An earlier answer was edited. Use the complete preserved interview context above, discard downstream assumptions contradicted by the edit, rebuild every affected running-plan field around the accumulated decisions, and ask exactly one concrete option-driven next question that narrows the selected direction."
|
||||
: formatResponseForAgent(currentQuestion, responses);
|
||||
await continueAgentConversation(session, message);
|
||||
}
|
||||
@@ -3511,11 +3514,10 @@ export function formatResponseForAgent(
|
||||
|
||||
const answerContext = comment.length > 0 ? `${formatted}\n\nAdditional context: ${comment}` : formatted;
|
||||
/*
|
||||
FNXC:PlanningMode 2026-07-20-00:55:
|
||||
System prompts can be displaced by long tool/context turns. Repeat the per-answer contract at the invocation boundary
|
||||
so every submitted answer steers the following high-impact question instead of inviting a model-generated completion.
|
||||
FNXC:PlanningMode 2026-07-23-14:10:
|
||||
System prompts can be displaced by long tool/context turns. Repeat the selection-as-plan-backbone contract at the invocation boundary so every submitted option, multi-selection, or Other answer rebuilds the work product before the one deeper question, instead of becoming an append-only note or inviting model-authored completion.
|
||||
*/
|
||||
return `${answerContext}\n\nRegenerate the runningPlan fields (title, description, concrete proposedChanges, observable acceptanceCriteria, suggestedSize, optional priority, suggestedDependencies, concrete keyDeliverables, and all distinct high-value suggestedRefinements) informed by this answer; do not cap suggestedRefinements at three. Author the operator-facing plan in Markdown: use concise GitHub-flavored Markdown in the description, with the structured fields supplying its Markdown sections and lists. Never list interview questions as deliverables or PROMPT.md sections such as Mission, Steps, File Scope, Review Level, Completion Criteria, or Do NOT. Return type:"question" with that complete runningPlan and ask exactly one next question. Do not validate the plan; only the user can proceed with it.`;
|
||||
return `${answerContext}\n\nThis answer is a durable planning decision, not an append-only note. Regenerate the runningPlan fields (title, description, concrete proposedChanges, observable acceptanceCriteria, suggestedSize, optional priority, suggestedDependencies, concrete keyDeliverables, and all distinct high-value suggestedRefinements) around this selected label/description and every accumulated decision; do not cap suggestedRefinements at three. Rewrite every affected field so the selected direction is the central intended outcome, not the original vague complaint or an unselected alternative. Preserve free-text Other verbatim as steering. Author the operator-facing plan in Markdown: use concise GitHub-flavored Markdown in the description, with the structured fields supplying its Markdown sections and lists. Never list interview questions as deliverables or PROMPT.md sections such as Mission, Steps, File Scope, Review Level, Completion Criteria, or Do NOT. Inspect the selected direction and relevant repository context, return type:"question" with that complete runningPlan, and ask exactly one next question: a deeper concrete option-driven question with materially distinct alternatives and pros/cons. Do not validate the plan; only the user can proceed with it.`;
|
||||
}
|
||||
|
||||
function coerceResponseRecord(question: PlanningQuestion, response: unknown): Record<string, unknown> {
|
||||
|
||||
Reference in New Issue
Block a user