Merge pull request #2522 from gsd-build/copilot/fix-bypass-single-writer-api

fix(prompts): migrate remaining 4 prompts to DB-backed tool API
This commit is contained in:
TÂCHES 2026-03-25 12:17:19 -06:00 committed by GitHub
commit 97e27634d8
6 changed files with 71 additions and 6 deletions

View file

@ -32,6 +32,6 @@ Then:
11. Do not run git commands — the system commits your changes and handles any merge after this unit succeeds.
12. Update `.gsd/PROJECT.md` if it exists — refresh current state if needed.
**You MUST do ALL THREE before finishing: (1) write `{{sliceSummaryPath}}`, (2) write `{{sliceUatPath}}`, (3) call `gsd_complete_slice`. The unit will not be marked complete if any of these are missing.**
**You MUST call `gsd_complete_slice` with the slice summary and UAT content before finishing. The tool persists to both DB and disk and renders `{{sliceSummaryPath}}` and `{{sliceUatPath}}` automatically.**
When done, say: "Slice {{sliceId}} complete."

View file

@ -77,6 +77,6 @@ Then:
The slice directory and tasks/ subdirectory already exist. Do NOT mkdir. All work stays in your working directory: `{{workingDirectory}}`.
**You MUST write the file `{{outputPath}}` before finishing.**
**You MUST call `gsd_plan_slice` to persist the planning state before finishing.**
When done, say: "Slice {{sliceId}} planned."

View file

@ -28,7 +28,7 @@ Then research the codebase and relevant technologies. Narrate key findings and s
5. **Web search budget:** You have a limited budget of web searches (max ~15 per session). Use them strategically — prefer `resolve_library` / `get_library_docs` for library documentation. Do NOT repeat the same or similar queries. If a search didn't find what you need, rephrase once or move on. Target 3-5 total web searches for a typical research unit.
6. Use the **Research** output template from the inlined context above — include only sections that have real content
7. If `.gsd/REQUIREMENTS.md` exists, research against it. Identify which Active requirements are table stakes, likely omissions, overbuilt risks, or domain-standard behaviors the user may or may not want.
8. Write `{{outputPath}}`
8. Call `gsd_summary_save` with `milestone_id: {{milestoneId}}`, `artifact_type: "RESEARCH"`, and the full research markdown as `content` — the tool computes the file path and persists to both DB and disk.
## Strategic Questions to Answer
@ -42,6 +42,6 @@ Then research the codebase and relevant technologies. Narrate key findings and s
**Research is advisory, not auto-binding.** Surface candidate requirements clearly instead of silently expanding scope.
**You MUST write the file `{{outputPath}}` before finishing.**
**You MUST call `gsd_summary_save` with the research content before finishing.**
When done, say: "Milestone {{milestoneId}} researched."

View file

@ -55,7 +55,7 @@ After running all checks, compute the **overall verdict**:
- `FAIL` — one or more checks failed
- `PARTIAL` — some checks passed, but one or more checks were skipped, inconclusive, or still require human judgment
Write `{{uatResultPath}}` with:
Call `gsd_summary_save` with `milestone_id: {{milestoneId}}`, `slice_id: {{sliceId}}`, `artifact_type: "ASSESSMENT"`, and the full UAT result markdown as `content` — the tool computes the file path and persists to both DB and disk. The content should follow this format:
```markdown
---
@ -84,6 +84,6 @@ date: <ISO 8601 timestamp>
---
**You MUST write `{{uatResultPath}}` before finishing.**
**You MUST call `gsd_summary_save` with the UAT result content before finishing.**
When done, say: "UAT {{sliceId}} complete."

View file

@ -61,6 +61,18 @@ test("plan-slice prompt: DB-backed tool names survive template substitution", ()
assert.ok(result.includes("canonical write path"), "canonical write path language should survive substitution");
});
test("plan-slice prompt: footer references gsd_plan_slice tool, not direct write", () => {
const result = loadPrompt("plan-slice", { ...BASE_VARS, commitInstruction: "Do not commit." });
assert.ok(
result.includes("MUST call `gsd_plan_slice`"),
"footer should instruct calling gsd_plan_slice tool",
);
assert.ok(
!result.includes("MUST write the file"),
"footer should not instruct direct file write",
);
});
test("domain-work prompts use skillActivation placeholder", () => {
const prompts = [
"research-milestone",
@ -174,6 +186,34 @@ test("research-milestone prompt substitutes skillActivation", () => {
assert.ok(!result.includes("{{skillActivation}}"));
});
test("research-milestone prompt references gsd_summary_save, not direct write", () => {
const result = loadPrompt("research-milestone", {
workingDirectory: "/tmp/test-project",
milestoneId: "M001",
milestoneTitle: "Test Milestone",
milestonePath: ".gsd/milestones/M001",
contextPath: ".gsd/milestones/M001/M001-CONTEXT.md",
outputPath: "/tmp/test-project/.gsd/milestones/M001/M001-RESEARCH.md",
inlinedContext: "Context",
skillDiscoveryMode: "manual",
skillDiscoveryInstructions: " Discover skills manually.",
skillActivation: "Load research skills first.",
});
assert.ok(
result.includes("gsd_summary_save"),
"research-milestone should reference gsd_summary_save tool",
);
assert.ok(
result.includes('artifact_type: "RESEARCH"'),
"research-milestone should specify RESEARCH artifact type",
);
assert.ok(
!result.includes("MUST write the file"),
"research-milestone should not instruct direct file write",
);
});
test("research-slice prompt substitutes skillActivation", () => {
const result = loadPrompt("research-slice", {
workingDirectory: "/tmp/test-project",

View file

@ -228,6 +228,31 @@ test('(k) run-uat prompt template', () => {
);
});
test('(k2) run-uat prompt references gsd_summary_save, not direct write', () => {
const promptResult = loadPromptFromWorktree('run-uat', {
workingDirectory: '/tmp/test-project',
milestoneId: 'M001',
sliceId: 'S01',
uatPath: '.gsd/milestones/M001/slices/S01/S01-UAT.md',
uatResultPath: '.gsd/milestones/M001/slices/S01/S01-UAT-RESULT.md',
uatType: 'artifact-driven',
inlinedContext: '<!-- no context -->',
});
assert.ok(
promptResult.includes('gsd_summary_save'),
'run-uat prompt should reference gsd_summary_save tool',
);
assert.ok(
promptResult.includes('artifact_type: "ASSESSMENT"'),
'run-uat prompt should specify ASSESSMENT artifact type',
);
assert.ok(
!promptResult.includes('MUST write'),
'run-uat prompt should not instruct direct file write in footer',
);
});
test('(l) dispatch preconditions via resolveSliceFile', () => {
const base = createFixtureBase();
const uatContent = makeUatContent('artifact-driven');