Merge pull request #3681 from Tibsfox/fix/discuss-slice-structured-questions

fix(gsd): add structuredQuestionsAvailable conditional to slice discuss
This commit is contained in:
Jeremy McSpadden 2026-04-07 07:06:25 -05:00 committed by GitHub
commit 6308a9d6e9
3 changed files with 56 additions and 7 deletions

View file

@ -480,7 +480,7 @@ async function buildDiscussSlicePrompt(
sid: string,
sTitle: string,
base: string,
options?: { rediscuss?: boolean },
options?: { rediscuss?: boolean; structuredQuestionsAvailable?: string },
): Promise<string> {
const inlined: string[] = [];
@ -560,6 +560,7 @@ async function buildDiscussSlicePrompt(
contextPath: sliceContextPath,
projectRoot: base,
inlinedTemplates,
structuredQuestionsAvailable: options?.structuredQuestionsAvailable ?? "false",
commitInstruction: buildDocsCommitInstruction(`docs(${mid}/${sid}): slice context from discuss`),
});
}
@ -801,7 +802,8 @@ export async function showDiscuss(
if (confirm !== "rediscuss") continue;
}
const prompt = await buildDiscussSlicePrompt(mid, chosen.id, chosen.title, basePath, { rediscuss: isRediscuss });
const sqAvail = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
const prompt = await buildDiscussSlicePrompt(mid, chosen.id, chosen.title, basePath, { rediscuss: isRediscuss, structuredQuestionsAvailable: sqAvail });
await dispatchWorkflow(pi, prompt, "gsd-discuss", ctx, "discuss-slice");
// Wait for the discuss session to finish, then loop back to the picker
@ -1514,7 +1516,8 @@ export async function showSmartEntry(
}),
}), "gsd-run", ctx, "plan-slice");
} else if (choice === "discuss") {
await dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext }), "gsd-run", ctx, "discuss-slice");
const sqAvail = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
await dispatchWorkflow(pi, await buildDiscussSlicePrompt(milestoneId, sliceId, sliceTitle, basePath, { rediscuss: hasContext, structuredQuestionsAvailable: sqAvail }), "gsd-run", ctx, "discuss-slice");
} else if (choice === "research") {
const researchTemplates = inlineTemplate("research", "Research");
await dispatchWorkflow(pi, loadPrompt("guided-research-slice", {

View file

@ -22,7 +22,9 @@ Do **not** go deep — just enough that your questions reflect what's actually t
### Question rounds
Ask **13 questions per round** using `ask_user_questions`. **Call `ask_user_questions` exactly once per turn — never make multiple calls with the same or overlapping questions. Wait for the user's response before asking the next round.** Keep each question focused on one of:
**If `{{structuredQuestionsAvailable}}` is `true`:** Ask **13 questions per round** using `ask_user_questions`. **Call `ask_user_questions` exactly once per turn — never make multiple calls with the same or overlapping questions. Wait for the user's response before asking the next round.**
**If `{{structuredQuestionsAvailable}}` is `false`:** Ask **13 questions per round** in plain text. Number them and wait for the user's response before asking the next round.
Keep each question focused on one of:
- **UX and user-facing behaviour** — what does the user see, click, trigger, or experience?
- **Edge cases and failure states** — what happens when things go wrong or are in unusual states?
- **Scope boundaries** — what is explicitly in vs out for this slice? What deferred to later?
@ -37,9 +39,7 @@ After each round of answers, decide whether you already have enough signal to wr
- **Incremental persistence:** After every 2 question rounds, silently save a draft `{{sliceId}}-CONTEXT-DRAFT.md` in `{{sliceDirPath}}` using `gsd_summary_save` with `milestone_id: {{milestoneId}}`, `slice_id: {{sliceId}}`, `artifact_type: "CONTEXT-DRAFT"`. This protects against session crashes losing confirmed work. Do NOT mention this to the user. The final context file will replace it.
- If not, investigate any new unknowns and continue to the next round immediately. Do **not** ask a meta "ready to wrap up?" question after every round.
- Ask a single wrap-up question only when you genuinely believe the slice is well understood or the user signals they want to stop.
- When you do ask it, use `ask_user_questions` with:
- "Write the context file" *(recommended when the slice is well understood)*
- "One more pass"
- When you do ask it, offer two choices: "Write the context file" *(recommended when the slice is well understood)* or "One more pass". Use `ask_user_questions` if available, otherwise ask in plain text.
---

View file

@ -0,0 +1,46 @@
/**
* Regression test for discuss-slice structured questions availability
*
* The guided-discuss-slice.md template must use the structuredQuestionsAvailable
* template variable to conditionally switch between ask_user_questions tool
* calls and plain-text questions, so the prompt works correctly when the
* structured questions tool is not available.
*/
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
const template = readFileSync(
resolve(process.cwd(), 'src', 'resources', 'extensions', 'gsd', 'prompts', 'guided-discuss-slice.md'),
'utf-8',
)
describe('discuss-slice structuredQuestionsAvailable template variable', () => {
it('template references structuredQuestionsAvailable variable', () => {
assert.ok(
template.includes('{{structuredQuestionsAvailable}}'),
'guided-discuss-slice.md must use {{structuredQuestionsAvailable}} template variable',
)
})
it('template handles both true and false cases', () => {
const trueCase = template.includes('`{{structuredQuestionsAvailable}}` is `true`')
const falseCase = template.includes('`{{structuredQuestionsAvailable}}` is `false`')
assert.ok(trueCase, 'template must have a branch for structuredQuestionsAvailable=true')
assert.ok(falseCase, 'template must have a branch for structuredQuestionsAvailable=false')
})
it('false case instructs plain text questions', () => {
const falseIdx = template.indexOf('`{{structuredQuestionsAvailable}}` is `false`')
assert.ok(falseIdx !== -1)
const afterFalse = template.slice(falseIdx, falseIdx + 300)
assert.ok(
afterFalse.includes('plain text'),
'when structuredQuestionsAvailable is false, questions should be in plain text',
)
})
})