fix: add gsd_generate_milestone_id tool for multi-milestone unique ID generation (#818)
When unique_milestone_ids is enabled, the LLM cannot generate random suffixes itself. Previously only the first milestone got a correct ID (pre-generated in TS), while subsequent milestones in multi-milestone projects got bare M002/M003 without suffixes. Added a gsd_generate_milestone_id tool that the LLM calls to get each milestone ID. The tool scans disk for existing milestones and respects the unique_milestone_ids preference, making it impossible to produce wrong-format IDs. Updated discuss, discuss-headless, and queue prompts to instruct the LLM to use the tool instead of inventing milestone IDs.
This commit is contained in:
parent
7c449b8b73
commit
68edb39f9e
5 changed files with 49 additions and 9 deletions
|
|
@ -636,9 +636,11 @@ async function showQueueAdd(
|
|||
const existingContext = await buildExistingMilestonesContext(basePath, milestoneIds, state);
|
||||
|
||||
// ── Determine next milestone ID ─────────────────────────────────────
|
||||
// Note: the LLM will use the gsd_generate_milestone_id tool to get IDs
|
||||
// at creation time, but we still mention the next ID in the preamble
|
||||
// for context about where the sequence is.
|
||||
const uniqueEnabled = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
||||
const nextId = nextMilestoneId(milestoneIds, uniqueEnabled);
|
||||
const nextIdPlus1 = nextMilestoneId([...milestoneIds, nextId], uniqueEnabled);
|
||||
|
||||
// ── Build preamble ──────────────────────────────────────────────────
|
||||
const activePart = state.activeMilestone
|
||||
|
|
@ -659,8 +661,6 @@ async function showQueueAdd(
|
|||
const queueInlinedTemplates = inlineTemplate("context", "Context");
|
||||
const prompt = loadPrompt("queue", {
|
||||
preamble,
|
||||
nextId,
|
||||
nextIdPlus1,
|
||||
existingMilestonesContext: existingContext,
|
||||
inlinedTemplates: queueInlinedTemplates,
|
||||
commitInstruction: buildDocsCommitInstruction("docs: queue <milestone list>"),
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import { loadPrompt } from "./prompt-loader.js";
|
|||
import { deriveState } from "./state.js";
|
||||
import { isAutoActive, isAutoPaused, handleAgentEnd, pauseAuto, getAutoDashboardData, markToolStart, markToolEnd } from "./auto.js";
|
||||
import { saveActivityLog } from "./activity-log.js";
|
||||
import { checkAutoStartAfterDiscuss, getDiscussionMilestoneId } from "./guided-flow.js";
|
||||
import { checkAutoStartAfterDiscuss, getDiscussionMilestoneId, findMilestoneIds, nextMilestoneId } from "./guided-flow.js";
|
||||
import { GSDDashboardOverlay } from "./dashboard-overlay.js";
|
||||
import {
|
||||
loadEffectiveGSDPreferences,
|
||||
|
|
@ -467,6 +467,46 @@ export default function (pi: ExtensionAPI) {
|
|||
},
|
||||
});
|
||||
|
||||
// ── gsd_generate_milestone_id — canonical milestone ID generation ──────
|
||||
// The LLM cannot generate random suffixes for unique_milestone_ids on its
|
||||
// own. This tool calls back into the TS code that owns ID generation,
|
||||
// ensuring the preference is always respected and IDs are always valid.
|
||||
pi.registerTool({
|
||||
name: "gsd_generate_milestone_id",
|
||||
label: "Generate Milestone ID",
|
||||
description:
|
||||
"Generate the next milestone ID for a new GSD milestone. " +
|
||||
"Scans existing milestones on disk and respects the unique_milestone_ids preference. " +
|
||||
"Always use this tool when creating a new milestone — never invent milestone IDs manually.",
|
||||
promptSnippet: "Generate a valid milestone ID (respects unique_milestone_ids preference)",
|
||||
promptGuidelines: [
|
||||
"ALWAYS call gsd_generate_milestone_id before creating a new milestone directory or writing milestone files.",
|
||||
"Never invent or hardcode milestone IDs like M001, M002 — always use this tool.",
|
||||
"Call it once per milestone you need to create. For multi-milestone projects, call it once for each milestone in sequence.",
|
||||
"The tool returns the correct format based on project preferences (e.g. M001 or M001-r5jzab).",
|
||||
],
|
||||
parameters: Type.Object({}),
|
||||
async execute(_toolCallId, _params, _signal, _onUpdate, _ctx) {
|
||||
try {
|
||||
const basePath = process.cwd();
|
||||
const existingIds = findMilestoneIds(basePath);
|
||||
const uniqueEnabled = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
||||
const newId = nextMilestoneId(existingIds, uniqueEnabled);
|
||||
return {
|
||||
content: [{ type: "text" as const, text: newId }],
|
||||
details: { operation: "generate_milestone_id", id: newId, existingCount: existingIds.length, uniqueEnabled },
|
||||
};
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
return {
|
||||
content: [{ type: "text" as const, text: `Error generating milestone ID: ${msg}` }],
|
||||
isError: true,
|
||||
details: { operation: "generate_milestone_id", error: msg },
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// ── session_start: render branded GSD header + load tool keys + remote status ──
|
||||
pi.on("session_start", async (_event, ctx) => {
|
||||
// Theme access throws in RPC mode (no TUI) — header is decorative, skip it
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ Use these templates exactly:
|
|||
9. Say exactly: "Milestone {{milestoneId}} ready."
|
||||
|
||||
**For multi-milestone**, write in this order:
|
||||
1. Create all milestone directories: `mkdir -p .gsd/milestones/{M###}/slices` for each
|
||||
1. For each milestone, call `gsd_generate_milestone_id` to get its ID — never invent milestone IDs manually. Then `mkdir -p .gsd/milestones/<ID>/slices` for each.
|
||||
2. Write `.gsd/PROJECT.md` — full vision across ALL milestones (using Project template)
|
||||
3. Write `.gsd/REQUIREMENTS.md` — full capability contract (using Requirements template)
|
||||
4. Seed `.gsd/DECISIONS.md` (using Decisions template)
|
||||
|
|
@ -82,5 +82,5 @@ Use these templates exactly:
|
|||
- **Investigate before writing** — always scout the codebase first
|
||||
- **Use depends_on frontmatter** for multi-milestone sequences (the state machine reads this field to determine execution order)
|
||||
- **Anti-reduction rule** — if the spec describes a big vision, plan the big vision. Do not ask "what's the minimum viable version?" or reduce scope. Phase complex/risky work into later milestones — do not cut it.
|
||||
- **Naming convention** — directories use bare IDs (`M001/`, `S01/`), files use ID-SUFFIX format (`M001-CONTEXT.md`, `M001-ROADMAP.md`)
|
||||
- **Naming convention** — always use `gsd_generate_milestone_id` to get milestone IDs. Directories use bare IDs (e.g. `M001/` or `M001-r5jzab/`), files use ID-SUFFIX format (e.g. `M001-CONTEXT.md` or `M001-r5jzab-CONTEXT.md`). Never invent milestone IDs manually.
|
||||
- **End with "Milestone {{milestoneId}} ready."** — this triggers auto-start detection
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ Once the user confirms the milestone split:
|
|||
|
||||
#### Phase 1: Shared artifacts
|
||||
|
||||
1. `mkdir -p .gsd/milestones/{{milestoneId}}/slices` for each milestone
|
||||
1. For each milestone, call `gsd_generate_milestone_id` to get its ID — never invent milestone IDs manually. Then `mkdir -p .gsd/milestones/<ID>/slices`.
|
||||
2. Write `.gsd/PROJECT.md` — use the **Project** output template below.
|
||||
3. Write `.gsd/REQUIREMENTS.md` — use the **Requirements** output template below. Capture Active, Deferred, Out of Scope, and any already Validated requirements. Later milestones may have provisional ownership where slice plans do not exist yet.
|
||||
4. Seed `.gsd/DECISIONS.md` — use the **Decisions** output template below.
|
||||
|
|
|
|||
|
|
@ -79,9 +79,9 @@ Determine where the new milestones should go in the overall sequence. Consider d
|
|||
|
||||
## Output Phase
|
||||
|
||||
Once the user is satisfied, in a single pass for **each** new milestone (starting from {{nextId}}):
|
||||
Once the user is satisfied, in a single pass for **each** new milestone:
|
||||
|
||||
1. `mkdir -p .gsd/milestones/<ID>/slices`
|
||||
1. Call `gsd_generate_milestone_id` to get the milestone ID — never invent milestone IDs manually. Then `mkdir -p .gsd/milestones/<ID>/slices`.
|
||||
2. Write `.gsd/milestones/<ID>/<ID>-CONTEXT.md` — use the **Context** output template below. Capture intent, scope, risks, constraints, integration points, and relevant requirements. Mark the status as "Queued — pending auto-mode execution." **If this milestone depends on other milestones, add YAML frontmatter with `depends_on`:**
|
||||
```yaml
|
||||
---
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue