@pmelab/gtd 15.1.0 → 15.2.1
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/gtd.bundle.mjs +656 -114
- package/package.json +2 -1
- package/src/workflows/prose.ts +27 -4
- package/src/workflows/text.test.ts +40 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pmelab/gtd",
|
|
3
|
-
"version": "15.1
|
|
3
|
+
"version": "15.2.1",
|
|
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": {
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"build": "tsdown --filter web && npx @tailwindcss/cli -i src/web/styles.css -o dist/web/main.css --minify && node scripts/inline-web-client.mjs && tsdown --filter gtd",
|
|
39
39
|
"postbuild": "jiti scripts/generate-schema.ts && node scripts/assert-no-test-doubles.mjs",
|
|
40
40
|
"watch": "tsdown --watch",
|
|
41
|
+
"bench:replay": "node scripts/bench-replay.mjs",
|
|
41
42
|
"test": "turbo run format:check typecheck lint lint:sh lint:boundaries test:unit test:e2e:inmem test:e2e:live test:web analyze",
|
|
42
43
|
"test:unit": "vitest run --project unit",
|
|
43
44
|
"test:watch": "vitest",
|
package/src/workflows/prose.ts
CHANGED
|
@@ -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
|
|
130
|
-
concrete options
|
|
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
|
-
|
|
135
|
-
|
|
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({
|