gsd-pi 2.48.0-dev.2e7390c → 2.48.0-dev.ced2eca
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resources/extensions/gsd/auto-dispatch.js +17 -2
- package/dist/resources/extensions/gsd/auto-post-unit.js +17 -3
- package/dist/resources/extensions/gsd/auto-worktree.js +5 -2
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +43 -3
- package/dist/resources/extensions/gsd/git-service.js +11 -10
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +223 -56
- package/dist/resources/extensions/gsd/prompts/run-uat.md +4 -4
- package/dist/resources/extensions/gsd/worktree-command.js +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/worktree-cli.js +1 -1
- package/package.json +1 -1
- package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +8 -4
- package/src/resources/extensions/gsd/auto-dispatch.ts +18 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +18 -3
- package/src/resources/extensions/gsd/auto-worktree.ts +4 -2
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +46 -3
- package/src/resources/extensions/gsd/git-service.ts +12 -11
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +223 -56
- package/src/resources/extensions/gsd/prompts/run-uat.md +4 -4
- package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-stash-merge.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +14 -12
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/git-service.test.ts +19 -9
- package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/parallel-merge.test.ts +6 -6
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +68 -0
- package/src/resources/extensions/gsd/worktree-command.ts +1 -1
- /package/dist/web/standalone/.next/static/{uTZ196cPUij3KcIDCweR6 → PTL5V00OW8q4-092tUQKx}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{uTZ196cPUij3KcIDCweR6 → PTL5V00OW8q4-092tUQKx}/_ssgManifest.js +0 -0
|
@@ -1,86 +1,253 @@
|
|
|
1
1
|
# Headless Milestone Creation
|
|
2
2
|
|
|
3
|
-
You are creating a GSD milestone from a provided specification document. This is a **headless** (non-interactive) flow — do NOT ask the user any questions.
|
|
3
|
+
You are creating a GSD milestone from a provided specification document. This is a **headless** (non-interactive) flow — do NOT ask the user any questions. Wherever the interactive flow would ask the user, make your best-judgment call and document it as an assumption.
|
|
4
4
|
|
|
5
5
|
## Provided Specification
|
|
6
6
|
|
|
7
7
|
{{seedContext}}
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Reflection Step
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Summarize your understanding of the specification concretely — not abstractly:
|
|
12
12
|
|
|
13
|
-
Summarize
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- Any ambiguities or gaps you notice
|
|
13
|
+
1. Summarize what is being built in your own words.
|
|
14
|
+
2. Give an honest size read: roughly how many milestones, roughly how many slices in the first one. Base this on the actual work involved, not a classification label.
|
|
15
|
+
3. Include scope honesty — a bullet list of the major capabilities: "Here's what I'm reading from the spec: [bullet list of major capabilities]."
|
|
16
|
+
4. Note any ambiguities, gaps, or areas where the spec is vague.
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
Print this reflection in chat. Do not skip this step.
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
- `ls` the project root and key directories
|
|
23
|
-
- Search for relevant existing code, patterns, dependencies
|
|
24
|
-
- Check library docs if needed (`resolve_library` / `get_library_docs`)
|
|
20
|
+
## Vision Mapping
|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
Decide the approach based on the actual scope:
|
|
27
23
|
|
|
28
|
-
|
|
24
|
+
**If the work spans multiple milestones:** Map the full landscape:
|
|
25
|
+
1. Propose a milestone sequence — names, one-line intents, rough dependencies
|
|
26
|
+
2. Print this in chat as the working milestone sequence
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
- Make your best-guess decision based on the spec's intent, codebase patterns, and domain conventions
|
|
32
|
-
- Document each assumption clearly in the Context file
|
|
28
|
+
**If the work fits in a single milestone:** Proceed directly to investigation.
|
|
33
29
|
|
|
34
|
-
|
|
30
|
+
**Anti-reduction rule:** If the spec describes a big vision, plan the big vision. Do not reduce scope. Phase complex/risky work into later milestones — do not cut it. The spec's ambition is the target, and your job is to sequence it intelligently, not shrink it.
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
- Is this a single milestone or multiple milestones?
|
|
38
|
-
- If multi-milestone: plan the full sequence with dependencies
|
|
32
|
+
## Mandatory Investigation
|
|
39
33
|
|
|
40
|
-
|
|
34
|
+
Do a mandatory investigation pass before making any decisions. This is not optional.
|
|
41
35
|
|
|
42
|
-
**
|
|
36
|
+
1. **Scout the codebase** — `ls`, `find`, `rg`, or `scout` for broad unfamiliar areas. Understand what already exists, what patterns are established, what constraints current code imposes.
|
|
37
|
+
2. **Check library docs** — `resolve_library` / `get_library_docs` for any tech mentioned in the spec. Get current facts about capabilities, constraints, API shapes, version-specific behavior.
|
|
38
|
+
3. **Web search** — `search-the-web` if the domain is unfamiliar, if you need current best practices, or if the spec references external services/APIs you need facts about. Use `fetch_page` for full content when snippets aren't enough.
|
|
43
39
|
|
|
44
|
-
|
|
40
|
+
**Web search budget:** Budget carefully across investigation + focused research:
|
|
41
|
+
- Prefer `resolve_library` / `get_library_docs` over `web_search` for library documentation.
|
|
42
|
+
- Prefer `search_and_read` for one-shot topic research.
|
|
43
|
+
- Target 2-3 web searches in this investigation pass. Save remaining budget for focused research.
|
|
44
|
+
- Do NOT repeat the same or similar queries.
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
The goal: your decisions should reflect what's actually true in the codebase and ecosystem, not what you assume.
|
|
47
|
+
|
|
48
|
+
## Autonomous Decision-Making
|
|
49
|
+
|
|
50
|
+
For every area where the spec is ambiguous, vague, or silent:
|
|
51
|
+
|
|
52
|
+
- Apply the depth checklist (below) to identify what needs resolution
|
|
53
|
+
- Make your best-judgment call based on: the spec's intent, codebase patterns, domain conventions, and investigation findings
|
|
54
|
+
- **Document every assumption** in the Context file under an "Assumptions" section
|
|
55
|
+
- For each assumption, note: what the spec said (or didn't say), what you decided, and why
|
|
56
|
+
|
|
57
|
+
### Depth Checklist
|
|
58
|
+
|
|
59
|
+
Ensure ALL of these are resolved before writing artifacts — from the spec + investigation, not by asking:
|
|
60
|
+
|
|
61
|
+
- [ ] **What is being built** — concrete enough that you could explain it to a stranger
|
|
62
|
+
- [ ] **Why it needs to exist** — the problem it solves or the desire it fulfills
|
|
63
|
+
- [ ] **Who it's for** — even if just the spec author
|
|
64
|
+
- [ ] **What "done" looks like** — observable outcomes, not abstract goals
|
|
65
|
+
- [ ] **The biggest technical unknowns / risks** — what could fail, what hasn't been proven
|
|
66
|
+
- [ ] **What external systems/services this touches** — APIs, databases, third-party services, hardware
|
|
67
|
+
|
|
68
|
+
If the spec leaves any of these unresolved, make your best-judgment call and document it.
|
|
69
|
+
|
|
70
|
+
## Depth Verification
|
|
71
|
+
|
|
72
|
+
Print a structured depth summary in chat covering:
|
|
73
|
+
- What you understood the spec to describe
|
|
74
|
+
- Key technical findings from investigation
|
|
75
|
+
- Assumptions you made and why
|
|
76
|
+
- Areas where you're least confident
|
|
77
|
+
|
|
78
|
+
This is your audit trail. Print it — do not skip it.
|
|
79
|
+
|
|
80
|
+
## Focused Research
|
|
81
|
+
|
|
82
|
+
Do a focused research pass before roadmap creation.
|
|
83
|
+
|
|
84
|
+
Research is advisory, not auto-binding. Use the spec + investigation to identify:
|
|
85
|
+
- table stakes the product space usually expects
|
|
86
|
+
- domain-standard behaviors that may be implied but not stated
|
|
87
|
+
- likely omissions that would make the product feel incomplete
|
|
88
|
+
- plausible anti-features or scope traps
|
|
89
|
+
- differentiators worth preserving
|
|
90
|
+
|
|
91
|
+
For multi-milestone visions, research should cover the full landscape, not just the first milestone. Research findings may affect milestone sequencing, not just slice ordering within M001.
|
|
92
|
+
|
|
93
|
+
**Key difference from interactive flow:** Where the interactive flow would present research-surfaced candidate requirements for the user to confirm/defer/reject, you instead apply your best judgment. If a research finding clearly aligns with the spec's intent, include it. If it's tangential or would expand scope beyond what the spec describes, defer it or mark it out of scope. Document the reasoning.
|
|
94
|
+
|
|
95
|
+
## Capability Contract
|
|
96
|
+
|
|
97
|
+
Before writing a roadmap, produce `.gsd/REQUIREMENTS.md`.
|
|
98
|
+
|
|
99
|
+
Use it as the project's explicit capability contract.
|
|
100
|
+
|
|
101
|
+
Requirements must be organized into:
|
|
102
|
+
- Active
|
|
103
|
+
- Validated
|
|
104
|
+
- Deferred
|
|
105
|
+
- Out of Scope
|
|
106
|
+
- Traceability
|
|
107
|
+
|
|
108
|
+
Each requirement should include:
|
|
109
|
+
- stable ID (`R###`)
|
|
110
|
+
- title
|
|
111
|
+
- class
|
|
112
|
+
- status
|
|
113
|
+
- description
|
|
114
|
+
- why it matters
|
|
115
|
+
- source (`spec`, `inferred`, `research`, or `execution`)
|
|
116
|
+
- primary owning slice
|
|
117
|
+
- supporting slices
|
|
118
|
+
- validation status
|
|
119
|
+
- notes
|
|
120
|
+
|
|
121
|
+
Rules:
|
|
122
|
+
- Keep requirements capability-oriented, not a giant feature inventory
|
|
123
|
+
- Every Active requirement must either be mapped to a roadmap owner, explicitly deferred, blocked with reason, or moved out of scope
|
|
124
|
+
- Product-facing work should capture launchability, primary user loop, continuity, and failure visibility when relevant
|
|
125
|
+
- Later milestones may have provisional ownership, but the first planned milestone should map requirements to concrete slices wherever possible
|
|
126
|
+
|
|
127
|
+
For multi-milestone projects, requirements should span the full vision. Requirements owned by later milestones get provisional ownership. The full requirement set captures the spec's complete vision — milestones are the sequencing strategy, not the scope boundary.
|
|
128
|
+
|
|
129
|
+
**Print the requirements in chat before writing the roadmap.** Print a markdown table with columns: ID, Title, Status, Owner, Source. Group by status (Active, Deferred, Out of Scope).
|
|
130
|
+
|
|
131
|
+
## Scope Assessment
|
|
132
|
+
|
|
133
|
+
Confirm the size estimate from your reflection still holds. Investigation and research often reveal hidden complexity or simplify things. If the scope grew or shrank significantly, adjust the milestone and slice counts accordingly.
|
|
134
|
+
|
|
135
|
+
## Output Phase
|
|
136
|
+
|
|
137
|
+
### Roadmap Preview
|
|
138
|
+
|
|
139
|
+
Before writing any files, **print the planned roadmap in chat**. Print a markdown table with columns: Slice, Title, Risk, Depends, Demo. One row per slice. Below the table, print the milestone definition of done as a bullet list.
|
|
140
|
+
|
|
141
|
+
This is the user's audit trail in the TUI scrollback — do not skip it.
|
|
142
|
+
|
|
143
|
+
### Naming Convention
|
|
47
144
|
|
|
48
|
-
|
|
145
|
+
Directories use bare IDs. Files use ID-SUFFIX format. Titles live inside file content, not in names.
|
|
146
|
+
- Milestone dir: `.gsd/milestones/{{milestoneId}}/`
|
|
147
|
+
- Milestone files: `{{milestoneId}}-CONTEXT.md`, `{{milestoneId}}-ROADMAP.md`
|
|
148
|
+
- Slice dirs: `S01/`, `S02/`, etc.
|
|
149
|
+
|
|
150
|
+
### Single Milestone
|
|
151
|
+
|
|
152
|
+
In a single pass:
|
|
49
153
|
1. `mkdir -p .gsd/milestones/{{milestoneId}}/slices`
|
|
50
|
-
2. Write `.gsd/PROJECT.md`
|
|
51
|
-
3. Write `.gsd/REQUIREMENTS.md`
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
154
|
+
2. Write or update `.gsd/PROJECT.md` — use the **Project** output template below. Describe what the project is, its current state, and list the milestone sequence.
|
|
155
|
+
3. Write or update `.gsd/REQUIREMENTS.md` — use the **Requirements** output template below. Confirm requirement states, ownership, and traceability before roadmap creation.
|
|
156
|
+
|
|
157
|
+
**Depth-Preservation Guidance for context.md:**
|
|
158
|
+
Preserve the specification's exact terminology, emphasis, and specific framing. Do not paraphrase domain-specific language into generics. If the spec said "craft feel," write "craft feel" — not "high-quality user experience." The context file is downstream agents' only window into this conversation — flattening specifics into generics loses the signal that shaped every decision.
|
|
159
|
+
|
|
160
|
+
4. Write `{{contextPath}}` — use the **Context** output template below. Preserve key risks, unknowns, existing codebase constraints, integration points, and relevant requirements surfaced during research. Include an "Assumptions" section documenting every judgment call.
|
|
161
|
+
5. Call `gsd_plan_milestone` to create the roadmap. Decompose into demoable vertical slices with risk, depends, demo sentences, proof strategy, verification classes, milestone definition of done, requirement coverage, and a boundary map. If the milestone crosses multiple runtime boundaries, include an explicit final integration slice that proves the assembled system works end-to-end in a real environment. Use the **Roadmap** output template below to structure the tool call parameters.
|
|
162
|
+
6. For each architectural or pattern decision, call `gsd_decision_save` — the tool auto-assigns IDs and regenerates `.gsd/DECISIONS.md` automatically.
|
|
55
163
|
7. {{commitInstruction}}
|
|
56
|
-
9. Say exactly: "Milestone {{milestoneId}} ready."
|
|
57
164
|
|
|
58
|
-
|
|
165
|
+
After writing the files, say exactly: "Milestone {{milestoneId}} ready." — nothing else. Auto-mode will start automatically.
|
|
166
|
+
|
|
167
|
+
### Multi-Milestone
|
|
168
|
+
|
|
169
|
+
#### Phase 1: Shared artifacts
|
|
170
|
+
|
|
59
171
|
1. For each milestone, call `gsd_milestone_generate_id` to get its ID — never invent milestone IDs manually. Then `mkdir -p .gsd/milestones/<ID>/slices` for each.
|
|
60
|
-
2. Write `.gsd/PROJECT.md` —
|
|
61
|
-
3. Write `.gsd/REQUIREMENTS.md` —
|
|
62
|
-
4.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
172
|
+
2. Write `.gsd/PROJECT.md` — use the **Project** output template below.
|
|
173
|
+
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.
|
|
174
|
+
4. For any architectural or pattern decisions, call `gsd_decision_save` — the tool auto-assigns IDs and regenerates `.gsd/DECISIONS.md` automatically.
|
|
175
|
+
|
|
176
|
+
#### Phase 2: Primary milestone
|
|
177
|
+
|
|
178
|
+
5. Write a full `CONTEXT.md` for the primary milestone (the first in sequence). Include an "Assumptions" section.
|
|
179
|
+
6. Call `gsd_plan_milestone` for **only the primary milestone** — detail-planning later milestones now is waste because the codebase will change. Include requirement coverage and a milestone definition of done.
|
|
180
|
+
|
|
181
|
+
#### MANDATORY: depends_on Frontmatter in CONTEXT.md
|
|
182
|
+
|
|
183
|
+
Every CONTEXT.md for a milestone that depends on other milestones MUST have YAML frontmatter with `depends_on`. The auto-mode state machine reads this field to determine execution order — without it, milestones may execute out of order or in parallel when they shouldn't.
|
|
184
|
+
|
|
185
|
+
```yaml
|
|
186
|
+
---
|
|
187
|
+
depends_on: [M001, M002]
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
# M003: Title
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
If a milestone has no dependencies, omit the frontmatter. Do NOT rely on QUEUE.md or PROJECT.md for dependency tracking — the state machine only reads CONTEXT.md frontmatter.
|
|
194
|
+
|
|
195
|
+
#### Phase 3: Remaining milestones
|
|
196
|
+
|
|
197
|
+
For each remaining milestone, in dependency order, autonomously decide the best readiness mode:
|
|
198
|
+
|
|
199
|
+
- **Write full context** — if the spec provides enough detail for this milestone and investigation confirms feasibility. Write a full `CONTEXT.md` with technical assumptions verified against the actual codebase.
|
|
200
|
+
- **Write draft for later** — if the spec has seed material but the milestone needs its own investigation/research in a future session. Write a `CONTEXT-DRAFT.md` capturing seed material, key ideas, provisional scope, and open questions. **Downstream:** Auto-mode pauses at this milestone and prompts the user to discuss.
|
|
201
|
+
- **Just queue it** — if the milestone is identified but the spec provides no actionable detail. No context file written. **Downstream:** Auto-mode pauses and starts a full discussion from scratch.
|
|
202
|
+
|
|
203
|
+
**Default to writing full context** when the spec is detailed enough. Default to draft when the spec mentions the milestone but is vague. Default to queue when the milestone is implied by the vision but not described.
|
|
204
|
+
|
|
205
|
+
**Technical Assumption Verification is still MANDATORY** for full-context milestones:
|
|
206
|
+
1. Read the actual code for every file or module you reference. Confirm APIs exist, check what functions actually do.
|
|
207
|
+
2. Check for stale assumptions — verify referenced modules still work as described.
|
|
208
|
+
3. Print findings in chat before writing each milestone's CONTEXT.md.
|
|
209
|
+
|
|
210
|
+
Each context file (full or draft) should be rich enough that a future agent encountering it fresh — with no memory of this conversation — can understand the intent, constraints, dependencies, what this milestone unlocks, and what "done" looks like.
|
|
211
|
+
|
|
212
|
+
#### Milestone Gate Tracking (MANDATORY for multi-milestone)
|
|
213
|
+
|
|
214
|
+
After deciding each milestone's readiness, immediately write or update `.gsd/DISCUSSION-MANIFEST.json`:
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"primary": "M001",
|
|
219
|
+
"milestones": {
|
|
220
|
+
"M001": { "gate": "discussed", "context": "full" },
|
|
221
|
+
"M002": { "gate": "discussed", "context": "full" },
|
|
222
|
+
"M003": { "gate": "queued", "context": "none" }
|
|
223
|
+
},
|
|
224
|
+
"total": 3,
|
|
225
|
+
"gates_completed": 3
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Write this file AFTER each gate decision, not just at the end. Update `gates_completed` incrementally. The system reads this file and BLOCKS auto-start if `gates_completed < total`.
|
|
230
|
+
|
|
231
|
+
For single-milestone projects, do NOT write this file.
|
|
232
|
+
|
|
233
|
+
#### Phase 4: Finalize
|
|
234
|
+
|
|
235
|
+
7. {{multiMilestoneCommitInstruction}}
|
|
236
|
+
|
|
237
|
+
After writing the files, say exactly: "Milestone {{milestoneId}} ready." — nothing else. Auto-mode will start automatically.
|
|
76
238
|
|
|
77
239
|
## Critical Rules
|
|
78
240
|
|
|
79
|
-
- **DO NOT ask the user any questions** — this is headless mode
|
|
241
|
+
- **DO NOT ask the user any questions** — this is headless mode. Make judgment calls and document them.
|
|
80
242
|
- **Preserve the specification's terminology** — don't paraphrase domain-specific language
|
|
81
|
-
- **Document assumptions** —
|
|
82
|
-
- **Investigate
|
|
83
|
-
- **
|
|
84
|
-
- **
|
|
85
|
-
- **
|
|
243
|
+
- **Document assumptions** — every judgment call gets noted in CONTEXT.md under "Assumptions" with reasoning
|
|
244
|
+
- **Investigate thoroughly** — scout codebase, check library docs, web search. Same rigor as interactive mode.
|
|
245
|
+
- **Do focused research** — identify table stakes, domain standards, omissions, scope traps. Same rigor as interactive mode.
|
|
246
|
+
- **Use proper tools** — `gsd_plan_milestone` for roadmaps, `gsd_decision_save` for decisions, `gsd_milestone_generate_id` for IDs
|
|
247
|
+
- **Print artifacts in chat** — requirements table, roadmap preview, depth summary. The TUI scrollback is the user's audit trail.
|
|
248
|
+
- **Use depends_on frontmatter** for multi-milestone sequences
|
|
249
|
+
- **Anti-reduction rule** — if the spec describes a big vision, plan the big vision. Phase complexity — don't cut it.
|
|
250
|
+
- **Naming convention** — always use `gsd_milestone_generate_id` for IDs. Directories use bare IDs, files use ID-SUFFIX format.
|
|
86
251
|
- **End with "Milestone {{milestoneId}} ready."** — this triggers auto-start detection
|
|
252
|
+
|
|
253
|
+
{{inlinedTemplates}}
|
|
@@ -29,7 +29,7 @@ You are the UAT runner. Execute every check defined in `{{uatPath}}` as deeply a
|
|
|
29
29
|
- `runtime-executable` — execute the specified command or script. Capture stdout/stderr as evidence. Record pass/fail based on exit code and output.
|
|
30
30
|
- `live-runtime` — exercise the real runtime path. Start or connect to the app/service if needed, use browser/runtime/network checks, and verify observable behavior.
|
|
31
31
|
- `mixed` — run all automatable artifact-driven and live-runtime checks. Separate any remaining human-only checks explicitly.
|
|
32
|
-
- `human-experience` — automate setup, preconditions, screenshots, logs, and objective checks, but do **not** invent subjective PASS results. Mark taste-based, experiential, or purely human-judgment checks as `NEEDS-HUMAN
|
|
32
|
+
- `human-experience` — automate setup, preconditions, screenshots, logs, and objective checks, but do **not** invent subjective PASS results. Mark taste-based, experiential, or purely human-judgment checks as `NEEDS-HUMAN`. Use an overall verdict of `PASS` when all automatable checks succeed (even if human-only checks remain as `NEEDS-HUMAN`). Use `PARTIAL` only when automatable checks themselves were inconclusive.
|
|
33
33
|
|
|
34
34
|
### Evidence tools
|
|
35
35
|
|
|
@@ -51,9 +51,9 @@ For each check, record:
|
|
|
51
51
|
- `PASS`, `FAIL`, or `NEEDS-HUMAN`
|
|
52
52
|
|
|
53
53
|
After running all checks, compute the **overall verdict**:
|
|
54
|
-
- `PASS` — all
|
|
55
|
-
- `FAIL` — one or more checks failed
|
|
56
|
-
- `PARTIAL` —
|
|
54
|
+
- `PASS` — all automatable checks passed. Any remaining checks that honestly require human judgment are marked `NEEDS-HUMAN` with clear instructions for the human reviewer. (This is the correct verdict for mixed/human-experience/live-runtime modes when all automatable checks succeed.)
|
|
55
|
+
- `FAIL` — one or more automatable checks failed
|
|
56
|
+
- `PARTIAL` — one or more automatable checks were skipped or returned inconclusive results (not the same as `NEEDS-HUMAN` — use PARTIAL only when the agent itself could not determine pass/fail for a check it was supposed to automate)
|
|
57
57
|
|
|
58
58
|
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:
|
|
59
59
|
|
|
@@ -183,8 +183,8 @@ test("single milestone worktree is merged to main when all complete (#962)", (t)
|
|
|
183
183
|
"milestone branch should be deleted",
|
|
184
184
|
);
|
|
185
185
|
|
|
186
|
-
// Verify squash commit on main
|
|
187
|
-
const log = run("git log
|
|
186
|
+
// Verify squash commit on main (milestone ID is in trailer, not subject)
|
|
187
|
+
const log = run("git log -3", tempDir);
|
|
188
188
|
assert.ok(
|
|
189
189
|
log.includes("M001"),
|
|
190
190
|
"squash commit on main should reference M001",
|
|
@@ -76,7 +76,7 @@ test("#2151 bug 1: auto-stash unblocks merge when unrelated files are dirty", ()
|
|
|
76
76
|
|
|
77
77
|
// Should succeed — the dirty README.md is auto-stashed before merge.
|
|
78
78
|
const result = mergeMilestoneToMain(repo, "M200", roadmap);
|
|
79
|
-
assert.ok(result.commitMessage.includes("feat(M200
|
|
79
|
+
assert.ok(result.commitMessage.includes("feat:") && result.commitMessage.includes("GSD-Milestone: M200"), "merge succeeds with dirty unrelated file");
|
|
80
80
|
assert.ok(existsSync(join(repo, "stash-test.ts")), "milestone code merged to main");
|
|
81
81
|
|
|
82
82
|
// Verify the dirty file was restored (stash popped).
|
|
@@ -160,15 +160,17 @@ describe("auto-worktree-milestone-merge", () => {
|
|
|
160
160
|
|
|
161
161
|
const result = mergeMilestoneToMain(repo, "M020", roadmap);
|
|
162
162
|
|
|
163
|
-
assert.match(result.commitMessage, /^feat
|
|
163
|
+
assert.match(result.commitMessage, /^feat:/, "subject has conventional commit prefix without milestone ID");
|
|
164
164
|
assert.ok(result.commitMessage.includes("Backend foundation"), "subject includes milestone title");
|
|
165
165
|
assert.ok(result.commitMessage.includes("- S01: Core API"), "body lists S01");
|
|
166
166
|
assert.ok(result.commitMessage.includes("- S02: Error handling"), "body lists S02");
|
|
167
167
|
assert.ok(result.commitMessage.includes("- S03: Logging infra"), "body lists S03");
|
|
168
|
+
assert.ok(result.commitMessage.includes("GSD-Milestone: M020"), "body has GSD-Milestone trailer");
|
|
168
169
|
assert.ok(result.commitMessage.includes("Branch: milestone/M020"), "body has branch metadata");
|
|
169
170
|
|
|
170
171
|
const gitMsg = run("git log -1 --format=%B main", repo).trim();
|
|
171
|
-
assert.match(gitMsg, /^feat
|
|
172
|
+
assert.match(gitMsg, /^feat:/, "git commit message starts with feat:");
|
|
173
|
+
assert.ok(gitMsg.includes("GSD-Milestone: M020"), "git commit has GSD-Milestone trailer");
|
|
172
174
|
assert.ok(gitMsg.includes("- S01: Core API"), "git commit body has S01");
|
|
173
175
|
});
|
|
174
176
|
|
|
@@ -213,11 +215,11 @@ describe("auto-worktree-milestone-merge", () => {
|
|
|
213
215
|
const result = mergeMilestoneToMain(repo, "M040", roadmap);
|
|
214
216
|
|
|
215
217
|
const mainLog = run("git log --oneline main", repo);
|
|
216
|
-
assert.ok(mainLog.includes("feat
|
|
218
|
+
assert.ok(mainLog.includes("feat:"), "milestone commit on main");
|
|
217
219
|
|
|
218
220
|
run("git push origin main", repo);
|
|
219
221
|
const remoteLog = run("git log --oneline main", bareDir);
|
|
220
|
-
assert.ok(remoteLog.includes("feat
|
|
222
|
+
assert.ok(remoteLog.includes("feat:"), "milestone commit reachable on remote after manual push");
|
|
221
223
|
|
|
222
224
|
assert.strictEqual(typeof result.pushed, "boolean", "pushed flag remains boolean");
|
|
223
225
|
});
|
|
@@ -248,7 +250,7 @@ describe("auto-worktree-milestone-merge", () => {
|
|
|
248
250
|
let threw = false;
|
|
249
251
|
try {
|
|
250
252
|
const result = mergeMilestoneToMain(repo, "M050", roadmap);
|
|
251
|
-
assert.ok(result.commitMessage.includes("feat(M050
|
|
253
|
+
assert.ok(result.commitMessage.includes("feat:") && result.commitMessage.includes("GSD-Milestone: M050"), "merge commit created despite .gsd conflict");
|
|
252
254
|
} catch (err) {
|
|
253
255
|
threw = true;
|
|
254
256
|
}
|
|
@@ -274,7 +276,7 @@ describe("auto-worktree-milestone-merge", () => {
|
|
|
274
276
|
let threw = false;
|
|
275
277
|
try {
|
|
276
278
|
const result = mergeMilestoneToMain(repo, "M060", roadmap);
|
|
277
|
-
assert.ok(result.commitMessage.includes("feat(M060
|
|
279
|
+
assert.ok(result.commitMessage.includes("feat:") && result.commitMessage.includes("GSD-Milestone: M060"), "merge commit created");
|
|
278
280
|
} catch (err) {
|
|
279
281
|
threw = true;
|
|
280
282
|
}
|
|
@@ -312,7 +314,7 @@ describe("auto-worktree-milestone-merge", () => {
|
|
|
312
314
|
let errMsg = "";
|
|
313
315
|
try {
|
|
314
316
|
const result = mergeMilestoneToMain(dir, "M070", roadmap);
|
|
315
|
-
assert.ok(result.commitMessage.includes("feat(M070
|
|
317
|
+
assert.ok(result.commitMessage.includes("feat:") && result.commitMessage.includes("GSD-Milestone: M070"), "merge commit created on master");
|
|
316
318
|
} catch (err) {
|
|
317
319
|
threw = true;
|
|
318
320
|
errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -392,7 +394,7 @@ describe("auto-worktree-milestone-merge", () => {
|
|
|
392
394
|
let threw = false;
|
|
393
395
|
try {
|
|
394
396
|
const result = mergeMilestoneToMain(repo, "M090", roadmap);
|
|
395
|
-
assert.ok(result.commitMessage.includes("feat(M090
|
|
397
|
+
assert.ok(result.commitMessage.includes("feat:") && result.commitMessage.includes("GSD-Milestone: M090"), "#1738 merge succeeds after cleaning synced dirs");
|
|
396
398
|
} catch (err: unknown) {
|
|
397
399
|
threw = true;
|
|
398
400
|
}
|
|
@@ -419,7 +421,7 @@ describe("auto-worktree-milestone-merge", () => {
|
|
|
419
421
|
let threw = false;
|
|
420
422
|
try {
|
|
421
423
|
const result = mergeMilestoneToMain(repo, "M100", roadmap);
|
|
422
|
-
assert.ok(result.commitMessage.includes("feat(M100
|
|
424
|
+
assert.ok(result.commitMessage.includes("feat:") && result.commitMessage.includes("GSD-Milestone: M100"), "#2151: merge succeeds after stashing dirty files");
|
|
423
425
|
} catch {
|
|
424
426
|
threw = true;
|
|
425
427
|
}
|
|
@@ -519,7 +521,7 @@ describe("auto-worktree-milestone-merge", () => {
|
|
|
519
521
|
let errMsg = "";
|
|
520
522
|
try {
|
|
521
523
|
const result = mergeMilestoneToMain(repo, "M140", roadmap);
|
|
522
|
-
assert.ok(result.commitMessage.includes("feat(M140
|
|
524
|
+
assert.ok(result.commitMessage.includes("feat:") && result.commitMessage.includes("GSD-Milestone: M140"), "merge commit created");
|
|
523
525
|
} catch (err) {
|
|
524
526
|
threw = true;
|
|
525
527
|
errMsg = err instanceof Error ? err.message : String(err);
|
|
@@ -589,7 +591,7 @@ describe("auto-worktree-milestone-merge", () => {
|
|
|
589
591
|
assert.ok(existsSync(squashMsgPath), "SQUASH_MSG planted before merge");
|
|
590
592
|
|
|
591
593
|
const result = mergeMilestoneToMain(repo, "M160", roadmap);
|
|
592
|
-
assert.ok(result.commitMessage.includes("feat(M160
|
|
594
|
+
assert.ok(result.commitMessage.includes("feat:") && result.commitMessage.includes("GSD-Milestone: M160"), "merge commit created");
|
|
593
595
|
|
|
594
596
|
assert.ok(!existsSync(squashMsgPath), "#1853: SQUASH_MSG must not persist after successful squash-merge");
|
|
595
597
|
});
|
|
@@ -609,7 +611,7 @@ describe("auto-worktree-milestone-merge", () => {
|
|
|
609
611
|
]);
|
|
610
612
|
|
|
611
613
|
const result = mergeMilestoneToMain(repo, "M170", roadmap);
|
|
612
|
-
assert.ok(result.commitMessage.includes("feat(M170
|
|
614
|
+
assert.ok(result.commitMessage.includes("feat:") && result.commitMessage.includes("GSD-Milestone: M170"), "merge commit created");
|
|
613
615
|
|
|
614
616
|
assert.ok(
|
|
615
617
|
existsSync(join(repo, "uncommitted-agent-code.ts")),
|
|
@@ -252,7 +252,7 @@ describe('feature-branch-lifecycle-integration', async () => {
|
|
|
252
252
|
// Exactly one new commit on feature branch (the squash merge)
|
|
253
253
|
const featureLog = run(`git log --oneline ${featureBranch}`, repo);
|
|
254
254
|
assert.ok(
|
|
255
|
-
featureLog.includes(
|
|
255
|
+
featureLog.includes("feat:"),
|
|
256
256
|
"feature branch has milestone merge commit",
|
|
257
257
|
);
|
|
258
258
|
|
|
@@ -215,10 +215,12 @@ describe('git-service', async () => {
|
|
|
215
215
|
oneLiner: "Added JWT-based auth with refresh token rotation",
|
|
216
216
|
keyFiles: ["src/auth.ts", "src/middleware/jwt.ts"],
|
|
217
217
|
});
|
|
218
|
-
assert.ok(msg.startsWith("feat
|
|
218
|
+
assert.ok(msg.startsWith("feat:"), "message starts with type: (no scope)");
|
|
219
|
+
assert.ok(!msg.includes("(S01/T02)"), "no GSD ID in subject line");
|
|
219
220
|
assert.ok(msg.includes("JWT-based auth"), "message includes one-liner content");
|
|
220
221
|
assert.ok(msg.includes("- src/auth.ts"), "message body includes key files");
|
|
221
222
|
assert.ok(msg.includes("- src/middleware/jwt.ts"), "message body includes second key file");
|
|
223
|
+
assert.ok(msg.includes("GSD-Task: S01/T02"), "GSD-Task trailer in body");
|
|
222
224
|
});
|
|
223
225
|
|
|
224
226
|
{
|
|
@@ -226,9 +228,9 @@ describe('git-service', async () => {
|
|
|
226
228
|
taskId: "S02/T01",
|
|
227
229
|
taskTitle: "fix login redirect bug",
|
|
228
230
|
});
|
|
229
|
-
assert.ok(msg.startsWith("fix
|
|
231
|
+
assert.ok(msg.startsWith("fix:"), "infers fix type from title");
|
|
230
232
|
assert.ok(msg.includes("fix login redirect bug"), "uses task title when no one-liner");
|
|
231
|
-
assert.ok(
|
|
233
|
+
assert.ok(msg.includes("GSD-Task: S02/T01"), "GSD-Task trailer present");
|
|
232
234
|
}
|
|
233
235
|
|
|
234
236
|
{
|
|
@@ -237,7 +239,8 @@ describe('git-service', async () => {
|
|
|
237
239
|
taskTitle: "add tests",
|
|
238
240
|
oneLiner: "Unit tests for auth module with coverage",
|
|
239
241
|
});
|
|
240
|
-
assert.ok(msg.startsWith("test
|
|
242
|
+
assert.ok(msg.startsWith("test:"), "infers test type");
|
|
243
|
+
assert.ok(msg.includes("GSD-Task: S01/T03"), "GSD-Task trailer present");
|
|
241
244
|
}
|
|
242
245
|
|
|
243
246
|
// ─── RUNTIME_EXCLUSION_PATHS ───────────────────────────────────────────
|
|
@@ -478,10 +481,10 @@ describe('git-service', async () => {
|
|
|
478
481
|
|
|
479
482
|
// Without task context, autoCommit uses generic chore message
|
|
480
483
|
const msg = svc.autoCommit("task", "T01");
|
|
481
|
-
assert.deepStrictEqual(msg, "chore
|
|
484
|
+
assert.deepStrictEqual(msg, "chore: auto-commit after task\n\nGSD-Unit: T01", "autoCommit returns generic format with trailer");
|
|
482
485
|
|
|
483
486
|
const log = run("git log --oneline -1", repo);
|
|
484
|
-
assert.ok(log.includes("chore
|
|
487
|
+
assert.ok(log.includes("chore: auto-commit after task"), "generic commit message is in git log");
|
|
485
488
|
|
|
486
489
|
// With task context, autoCommit uses meaningful message
|
|
487
490
|
createFile(repo, "src/auth.ts", "export function login() {}");
|
|
@@ -492,8 +495,9 @@ describe('git-service', async () => {
|
|
|
492
495
|
keyFiles: ["src/auth.ts"],
|
|
493
496
|
});
|
|
494
497
|
assert.ok(msg2 !== null, "autoCommit with task context returns a message");
|
|
495
|
-
assert.ok(msg2!.startsWith("feat
|
|
498
|
+
assert.ok(msg2!.startsWith("feat:"), "meaningful commit uses feat type without scope");
|
|
496
499
|
assert.ok(msg2!.includes("JWT-based auth"), "meaningful commit includes one-liner content");
|
|
500
|
+
assert.ok(msg2!.includes("GSD-Task: S01/T02"), "meaningful commit has GSD-Task trailer");
|
|
497
501
|
|
|
498
502
|
rmSync(repo, { recursive: true, force: true });
|
|
499
503
|
});
|
|
@@ -529,7 +533,7 @@ describe('git-service', async () => {
|
|
|
529
533
|
|
|
530
534
|
// Auto-commit with .gsd/ excluded (simulates pre-switch)
|
|
531
535
|
const msg = svc.autoCommit("pre-switch", "main", [".gsd/"]);
|
|
532
|
-
assert.deepStrictEqual(msg, "chore
|
|
536
|
+
assert.deepStrictEqual(msg, "chore: auto-commit after pre-switch\n\nGSD-Unit: main", "pre-switch autoCommit with .gsd/ exclusion commits");
|
|
533
537
|
|
|
534
538
|
// Verify .gsd/ file was NOT committed
|
|
535
539
|
const show = run("git show --stat HEAD", repo);
|
|
@@ -1295,7 +1299,12 @@ describe('git-service', async () => {
|
|
|
1295
1299
|
issueNumber: 42,
|
|
1296
1300
|
});
|
|
1297
1301
|
assert.ok(msg.includes("Resolves #42"), "buildTaskCommitMessage includes Resolves #N trailer when issueNumber is set");
|
|
1298
|
-
assert.ok(msg.startsWith("fix
|
|
1302
|
+
assert.ok(msg.startsWith("fix:"), "buildTaskCommitMessage infers fix type");
|
|
1303
|
+
assert.ok(msg.includes("GSD-Task: S01/T03"), "GSD-Task trailer present");
|
|
1304
|
+
// GSD-Task should come before Resolves
|
|
1305
|
+
const taskIdx = msg.indexOf("GSD-Task: S01/T03");
|
|
1306
|
+
const resolvesIdx = msg.indexOf("Resolves #42");
|
|
1307
|
+
assert.ok(taskIdx < resolvesIdx, "GSD-Task trailer before Resolves trailer");
|
|
1299
1308
|
});
|
|
1300
1309
|
|
|
1301
1310
|
{
|
|
@@ -1305,6 +1314,7 @@ describe('git-service', async () => {
|
|
|
1305
1314
|
taskTitle: "add dashboard widget",
|
|
1306
1315
|
});
|
|
1307
1316
|
assert.ok(!msg.includes("Resolves"), "buildTaskCommitMessage omits Resolves trailer when issueNumber is absent");
|
|
1317
|
+
assert.ok(msg.includes("GSD-Task: S01/T04"), "GSD-Task trailer still present");
|
|
1308
1318
|
}
|
|
1309
1319
|
|
|
1310
1320
|
// ─── runPreMergeCheck: skips when no package.json ────────────────────────
|
|
@@ -94,8 +94,8 @@ test("worktree swap on milestone transition: merge old, create new", () => {
|
|
|
94
94
|
assert.equal(process.cwd(), tempDir, "cwd restored to project root after merge");
|
|
95
95
|
assert.ok(!isInAutoWorktree(tempDir), "no longer in auto-worktree after merge");
|
|
96
96
|
|
|
97
|
-
// Verify M001 work was merged to main
|
|
98
|
-
const mainLog = run("git log
|
|
97
|
+
// Verify M001 work was merged to main (milestone ID is in trailer, not subject)
|
|
98
|
+
const mainLog = run("git log -3", tempDir);
|
|
99
99
|
assert.ok(mainLog.includes("M001"), "M001 squash commit should be on main");
|
|
100
100
|
|
|
101
101
|
// Phase 3: Create new worktree for M002 (simulates new milestone)
|
|
@@ -168,7 +168,7 @@ test("formatMergeResults — empty results", () => {
|
|
|
168
168
|
|
|
169
169
|
test("formatMergeResults — successful merge", () => {
|
|
170
170
|
const results: MergeResult[] = [
|
|
171
|
-
{ milestoneId: "M001", success: true, commitMessage: "feat
|
|
171
|
+
{ milestoneId: "M001", success: true, commitMessage: "feat: Auth\n\nGSD-Milestone: M001\nBranch: milestone/M001", pushed: true },
|
|
172
172
|
];
|
|
173
173
|
const output = formatMergeResults(results);
|
|
174
174
|
assert.ok(output.includes("M001"));
|
|
@@ -178,7 +178,7 @@ test("formatMergeResults — successful merge", () => {
|
|
|
178
178
|
|
|
179
179
|
test("formatMergeResults — successful merge without push", () => {
|
|
180
180
|
const results: MergeResult[] = [
|
|
181
|
-
{ milestoneId: "M001", success: true, commitMessage: "feat
|
|
181
|
+
{ milestoneId: "M001", success: true, commitMessage: "feat: Auth\n\nGSD-Milestone: M001\nBranch: milestone/M001", pushed: false },
|
|
182
182
|
];
|
|
183
183
|
const output = formatMergeResults(results);
|
|
184
184
|
assert.ok(output.includes("merged successfully"));
|
|
@@ -213,7 +213,7 @@ test("formatMergeResults — generic failure without conflict files", () => {
|
|
|
213
213
|
|
|
214
214
|
test("formatMergeResults — mixed results", () => {
|
|
215
215
|
const results: MergeResult[] = [
|
|
216
|
-
{ milestoneId: "M001", success: true, commitMessage: "feat
|
|
216
|
+
{ milestoneId: "M001", success: true, commitMessage: "feat: OK\n\nGSD-Milestone: M001\nBranch: milestone/M001", pushed: false },
|
|
217
217
|
{ milestoneId: "M002", success: false, error: "conflict", conflictFiles: ["a.ts"] },
|
|
218
218
|
];
|
|
219
219
|
const output = formatMergeResults(results);
|
|
@@ -281,9 +281,9 @@ test("mergeCompletedMilestone — clean merge, session status cleaned up", async
|
|
|
281
281
|
// Verify file merged to main
|
|
282
282
|
assert.ok(existsSync(join(repo, "auth.ts")), "auth.ts should be on main");
|
|
283
283
|
|
|
284
|
-
// Verify commit on main
|
|
285
|
-
const log = run("git log --
|
|
286
|
-
assert.ok(log.includes("M010"), "commit message should reference M010");
|
|
284
|
+
// Verify commit on main (M010 is now in the body as a GSD-Milestone trailer)
|
|
285
|
+
const log = run("git log -1 --format=%B main", repo);
|
|
286
|
+
assert.ok(log.includes("GSD-Milestone: M010"), "commit message should reference M010 in trailer");
|
|
287
287
|
|
|
288
288
|
// Verify session status cleaned up
|
|
289
289
|
const statusAfter = readSessionStatus(repo, "M010");
|