@pmelab/gtd 17.0.0 → 17.3.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.
@@ -1,9 +1,11 @@
1
1
  import { describe, expect, it } from "vitest"
2
2
  import {
3
+ buildReviewReviewingPrompt,
3
4
  agentWithSkills,
4
5
  architectureAuthorPrompt,
5
6
  architectureGateAnswerMessage,
6
7
  buildFixQualityPrompt,
8
+ buildQualityReviewingPrompt,
7
9
  buildReviewAwaitReviewMessage,
8
10
  buildReviewCollectingPrompt,
9
11
  designGateAnswerMessage,
@@ -101,6 +103,25 @@ describe("the shared open-question instruction", () => {
101
103
  })
102
104
  })
103
105
 
106
+ describe("answered-entry footnote exception", () => {
107
+ const exception = "carrying a human footnote on its"
108
+ it("is in the design return lap and loop-back, keeping the deleted-question rule", () => {
109
+ const prompt = renderText(() => designTriagePrompt("base"))
110
+ expect(prompt).toContain("Never re-raise a deleted question")
111
+ expect(prompt).toContain(`entry stays settled, except one ${exception}`)
112
+ expect(prompt).toContain(`stays settled, except\n one ${exception}`)
113
+ expect(prompt).not.toContain("never re-open a settled")
114
+ expect(prompt).not.toContain("never re-open one")
115
+ })
116
+
117
+ it("is in the architecture return lap and keeps the first-lap settled line", () => {
118
+ const prompt = renderText(() => architectureAuthorPrompt())
119
+ expect(prompt).toContain(`entry stays settled, except one ${exception}`)
120
+ expect(prompt).not.toContain("never re-open a settled")
121
+ expect(prompt).toMatch(/treat every decision[\s\S]*as settled — never re-open it/)
122
+ })
123
+ })
124
+
104
125
  describe("architectureAuthorPrompt", () => {
105
126
  it("qualifies the PERMISSIVE default with a settled-requirements exception", () => {
106
127
  const prompt = renderText(() => architectureAuthorPrompt())
@@ -116,6 +137,37 @@ describe("buildFixQualityPrompt", () => {
116
137
  })
117
138
  })
118
139
 
140
+ describe("buildFixQualityPrompt scope", () => {
141
+ it("fixes every finding, blocking or not", () => {
142
+ const prompt = renderText(() => buildFixQualityPrompt())
143
+ expect(prompt).toContain("fix every finding in every chunk, blocking or not")
144
+ expect(prompt).not.toContain("found something blocking")
145
+ })
146
+ })
147
+
148
+ describe("buildQualityReviewingPrompt", () => {
149
+ const prompt = renderText(() => buildQualityReviewingPrompt("x"))
150
+ it("traces, reads touched paths on a large change, and judges tests by reasoning", () => {
151
+ expect(prompt).toContain("Trace, do not skim")
152
+ expect(prompt).toContain("order of external calls against")
153
+ expect(prompt).toContain("read the touched code")
154
+ expect(prompt).toContain("would fail with the guarded")
155
+ expect(prompt).toContain("never by running a")
156
+ expect(prompt).toContain("pins a bug is a finding")
157
+ })
158
+ it("writes every finding and approves only when nothing was found", () => {
159
+ expect(prompt).toContain("APPEND every finding you have, blocking or not")
160
+ expect(prompt).toContain("only when this lens found nothing at all")
161
+ expect(prompt).not.toContain("Write nothing when nothing is blocking")
162
+ })
163
+ it("adds a brief block only when given one", () => {
164
+ expect(prompt).not.toContain("This lens's brief")
165
+ expect(renderText(() => buildQualityReviewingPrompt("x", "BRIEF-TEXT"))).toContain(
166
+ "This lens's brief:\nBRIEF-TEXT",
167
+ )
168
+ })
169
+ })
170
+
119
171
  describe("summaryPrompt", () => {
120
172
  it("puts the total and every model's cost on a line of its own", () => {
121
173
  const prompt = summaryPrompt({
@@ -198,3 +250,13 @@ describe("code threads in prompts", () => {
198
250
  }
199
251
  })
200
252
  })
253
+
254
+ describe("buildReviewReviewingPrompt risk marking", () => {
255
+ it("states the Risk: marker rule and the re-review rule", () => {
256
+ const prompt = renderText(() => buildReviewReviewingPrompt("base"))
257
+ expect(prompt).toContain("Open a hunk's note with `Risk:` only for a concrete defect")
258
+ expect(prompt).toContain("never a style remark")
259
+ expect(prompt).toContain("describe what the fix changed under the")
260
+ expect(prompt).toContain("re-mark only a risk the fix did not resolve")
261
+ })
262
+ })
@@ -191,8 +191,10 @@ ${questionBarReturn}
191
191
  \`git show ${base}\`. (On the first lap that hash
192
192
  is the process's own diff base, \`${start()}\` — this
193
193
  branch doesn't apply)
194
- - Every decision under \`## Answered Questions\` stays settled —
195
- never re-open one. A genuinely open PRODUCT point may still
194
+ - Every decision under \`## Answered Questions\` stays settled, except
195
+ one carrying a human footnote on its \`### \` heading — the human's
196
+ review-round edit is where such notes arrive; fold the note in and
197
+ rewrite the answer or move it back to \`## Open Questions\`. A genuinely open PRODUCT point may still
196
198
  raise a fresh \`## Open Questions\` entry: the product gate sits
197
199
  on this path exactly as on the first lap
198
200
  - Before grouping anything, run \`${env.testCommand}\`
@@ -505,28 +507,70 @@ ${agentConduct}`
505
507
 
506
508
  export const buildFixQualityPrompt = (): string =>
507
509
  `${stateFileRules}
508
- - Read \`.gtd/QUALITY.md\` — one \`## \` chunk per quality dimension
509
- that found something blocking. Merge duplicate findings across
510
- dimensions FIRST, then fix every chunk
510
+ - Read \`.gtd/QUALITY.md\` — one \`## \` chunk per finding a quality
511
+ dimension wrote. Merge duplicate findings across dimensions
512
+ FIRST, then fix every finding in every chunk, blocking or not
511
513
  - When findings conflict, missing test signal beats line count —
512
514
  a test is never deleted to satisfy a simplification finding
513
515
  - Delete \`.gtd/QUALITY.md\` once every finding is resolved
514
516
  - Leave everything else uncommitted and finish your turn
515
517
  `
516
518
 
517
- export const buildQualityReviewingPrompt = (lens: string): string =>
519
+ /** Brief for a lens the workflow defines itself; a lens with none is just a skill of that name. */
520
+ export const correctnessBrief = `- Trace partial-failure and retry paths: what a step that fails
521
+ after saving an id leaves behind, and whether the retry resumes
522
+ it or starts over
523
+ - Check validation done before an external side effect — its
524
+ format, not just its presence
525
+ - Check invariants that parallel write paths share: every path
526
+ that writes the same data must enforce what the main path
527
+ enforces
528
+ - New mock behaviour without a contract test against the live
529
+ behaviour (mock/live drift) is a finding`
530
+
531
+ export const conventionsBrief = `- Read every \`AGENTS.md\` and \`CLAUDE.md\` in the repository root
532
+ and in each touched file's directory ancestry, end to end, plus
533
+ every file they pull in by \`@path\`
534
+ - Every violation of them in the change is a finding — quote the
535
+ rule it breaks`
536
+
537
+ export const specChallengeBrief = `- Find this process's planning documents in history:
538
+ \`git log <start>..HEAD\` (\`<start>\` is the commit above) over the steering directory
539
+ (\`.gtd/\`), then \`git show\` the last version of the
540
+ requirements, architecture and package files
541
+ - Flag a spec decision that conflicts with a system invariant — an
542
+ existing test, a documented constraint, or a data invariant
543
+ other code relies on — naming the decision and the invariant
544
+ - With no planning documents in history, write nothing`
545
+
546
+ export const buildQualityReviewingPrompt = (lens: string, brief?: string): string =>
518
547
  `${stateFileRules}
519
548
  - The only state file this turn writes is \`.gtd/QUALITY.md\` — no
520
549
  other files for notes or output
521
550
  - Review the whole assembled change, from \`${start()}\`
522
551
  to the working tree, through this ONE quality lens only —
523
- \`${lens}\`, already loaded as this turn's own skill
524
- - Where you find something blocking, APPEND a \`## \` chunk to
525
- \`.gtd/QUALITY.md\` describing it — never overwrite what an
526
- earlier dimension already wrote there
527
- - Write nothing when nothing is blocking under this lens — a
552
+ \`${lens}\`
553
+ - Trace, do not skim: follow the order of external calls against
554
+ the resume/retry logic. On a large change, read the touched code
555
+ paths, not just the diff hunks
556
+ - A test counts as coverage only if it would fail with the guarded
557
+ behaviour removed — decide that by reasoning, never by running a
558
+ mutation-testing tool. A test that pins a bug is a finding, not
559
+ praise
560
+ - APPEND every finding you have, blocking or not, as a \`## \`
561
+ chunk to \`.gtd/QUALITY.md\` — never overwrite what an earlier
562
+ dimension already wrote there
563
+ - Write nothing only when this lens found nothing at all — then a
528
564
  clean turn IS this dimension's approval
529
- - Touch no other state file, and leave everything uncommitted
565
+ ${
566
+ brief
567
+ ? `
568
+ This lens's brief:
569
+ ${brief}
570
+
571
+ `
572
+ : ""
573
+ }- Touch no other state file, and leave everything uncommitted
530
574
  `
531
575
 
532
576
  export const reviewerSystem = (): string =>
@@ -642,6 +686,17 @@ The nit notes are:
642
686
 
643
687
  ${notesCapture(notes)}`
644
688
 
689
+ export const buildReviewFixRisksPrompt = (notes: readonly NoteInput[]): string =>
690
+ `${stateFileRules}
691
+ - Fix every risk below in this one turn, all together
692
+ - Where a risk is behavioural, add a test that fails without the fix
693
+ - Leave \`.gtd/REVIEW.md\` untouched — the re-review rewrites it
694
+ - Leave everything uncommitted and finish your turn
695
+
696
+ The risk notes are:
697
+
698
+ ${notesCapture(notes)}`
699
+
645
700
  export const reviewEditNotesCapture = (
646
701
  commit: string,
647
702
  edits: readonly NoteInput[],
@@ -767,6 +822,14 @@ review the changes:
767
822
  - [ ] ./path/to/file.ts#42-70 — what this hunk does
768
823
  and here is more detail, continued below it
769
824
 
825
+ Open a hunk's note with \`Risk:\` only for a concrete defect
826
+ the change introduces, never a style remark — each such note is
827
+ fixed automatically before the human sees the review. On the
828
+ re-review after a fix, describe what the fix changed under the
829
+ hunk it touched, and re-mark only a risk the fix did not resolve:
830
+
831
+ - [ ] ./path/to/file.ts#42-70 — Risk: what is wrong
832
+
770
833
  A note sitting entirely on the line(s) beneath the pointer is
771
834
  also valid. Either way, the note must never start with a bare \`./path\` token
772
835
  — that parses as a second pointer, not a note
@@ -16,7 +16,8 @@ export const defaults: Readonly<Record<string, string>> = {
16
16
  // `./review.ts`) rather than naming one step's skill list, so it is a
17
17
  // setting — pairing with `build.quality.reviewing`'s entry in
18
18
  // `./skills.ts`, which replaces the lens on every one of those turns.
19
- qualityReviews: "owasp-security, ponytail-review, test-audit",
19
+ qualityReviews:
20
+ "correctness, owasp-security, ponytail-review, test-audit, conventions, spec-challenge",
20
21
  }
21
22
 
22
23
  /** Environment settings. */