@pmelab/gtd 15.1.0 → 15.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmelab/gtd",
3
- "version": "15.1.0",
3
+ "version": "15.2.0",
4
4
  "private": false,
5
5
  "description": "Git-aware CLI that emits the next prompt for an autonomous coding agent based on the current repository state",
6
6
  "bin": {
@@ -126,13 +126,36 @@ export const questionBar = `- The goal is shared understanding, not a quota or a
126
126
  plus a one-line rationale in prose, no checkboxes. That heading
127
127
  always comes last, after every other \`##\` section
128
128
  - Above the bar, write it under \`## Open Questions\` — always the first
129
- \`##\` section — as \`### <question>\` plus a checkbox list: two
130
- concrete options and a free-text slot, all unticked:
129
+ \`##\` section — as \`### <question>\` plus a body plus a checkbox list.
130
+ The count of concrete options is your call, taken from how many
131
+ genuinely distinct answers the fork has — never padded to a fixed
132
+ number. A floor still applies: at least two real options plus the
133
+ free-text slot, or the question is not a fork
134
+ - The heading is followed by one or two lines of body naming the fork
135
+ and what actually differs, THEN the option list. The body frames the
136
+ fork; each option's own content carries that option's impacts — they
137
+ never repeat each other. A body that lists the options back is
138
+ padding, and an option line that restates the fork is padding too
139
+ - Each option is one short line plus its own impacts nested under it as
140
+ a bullet list: one to four bullets, free-form — what it costs, what
141
+ it buys, how it differs from the others. Write the dimensions this
142
+ fork actually has and skip the ones it does not; no \`Costs:\`/\`Buys:\`/
143
+ \`Differs:\` labels, no fixed bullet count. The free-text slot is last,
144
+ mandatory, identified by position rather than by label:
131
145
 
132
146
  ### <the question>
133
147
 
134
- - [ ] <first option — a concrete answer, a few words of rationale>
135
- - [ ] <second option>
148
+ <one or two lines: what the fork is, what actually differs>
149
+
150
+ - [ ] <first option — a short line>
151
+ - <impact>
152
+ - <impact>
153
+ - [ ] <second option — a short line>
154
+ - <impact>
155
+ - [ ] <third option — a short line>
156
+ - <impact>
157
+ - <impact>
158
+ - <impact>
136
159
  - [ ] _your answer_
137
160
 
138
161
  - Never tick a box yourself — the human ticks exactly one per question`
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "vitest"
2
- import { summaryPrompt, withSkills } from "./text.js"
2
+ import { architectureAuthorPrompt, designTriagePrompt, summaryPrompt, withSkills } from "./text.js"
3
3
  import { renderText } from "./text.fixture.js"
4
4
 
5
5
  describe("withSkills", () => {
@@ -18,6 +18,45 @@ describe("withSkills", () => {
18
18
  })
19
19
  })
20
20
 
21
+ describe("the shared open-question instruction", () => {
22
+ it("leaves the option count to the agent, floored at two real options", () => {
23
+ const prompt = renderText(() => designTriagePrompt("base"))
24
+ expect(prompt).toContain("count of concrete options is your call")
25
+ expect(prompt).toContain("at least two real options plus the")
26
+ expect(prompt).not.toContain("plus a checkbox list: two")
27
+ })
28
+
29
+ it("requires a body between the heading and the option list", () => {
30
+ const prompt = renderText(() => designTriagePrompt("base"))
31
+ expect(prompt).toContain("followed by one or two lines of body naming the fork")
32
+ expect(prompt).toContain("what actually differs, THEN the option list")
33
+ })
34
+
35
+ it("requires per-option impacts, free-form with no fixed count", () => {
36
+ const prompt = renderText(() => designTriagePrompt("base"))
37
+ expect(prompt).toContain("its own impacts nested under it")
38
+ expect(prompt).toContain("one to four bullets, free-form")
39
+ expect(prompt).toContain("labels, no fixed bullet count")
40
+ })
41
+
42
+ it("is the identical block, full text, in both the design gate and the architecture gate", () => {
43
+ // Anchored at questionBar's own first and last lines (not a bullet
44
+ // partway in) so the whole ~3.4k-char block is compared — a divergence
45
+ // anywhere inside, including its opening bullets, fails this test the
46
+ // moment either gate's copy drifts from the shared source.
47
+ const start = "The goal is shared understanding, not a quota or an empty section"
48
+ const end = "Never tick a box yourself — the human ticks exactly one per question"
49
+ const sharedBlock = (prompt: string): string =>
50
+ prompt.slice(prompt.indexOf(start), prompt.indexOf(end) + end.length)
51
+
52
+ const designBlock = sharedBlock(renderText(() => designTriagePrompt("base")))
53
+ const architectureBlock = sharedBlock(renderText(() => architectureAuthorPrompt()))
54
+
55
+ expect(designBlock.length).toBeGreaterThan(3000)
56
+ expect(designBlock).toBe(architectureBlock)
57
+ })
58
+ })
59
+
21
60
  describe("summaryPrompt", () => {
22
61
  it("puts the total and every model's cost on a line of its own", () => {
23
62
  const prompt = summaryPrompt({