@pmelab/gtd 41.0.0 → 42.0.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.
Files changed (54) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +38 -28
  3. package/dist/chunks/{reserved-Ch4LLrEj.mjs → reserved-D7MNY9QJ.mjs} +58 -14
  4. package/dist/chunks/{steering-DVILanVf.mjs → steering-oCDdQlpP.mjs} +1 -1
  5. package/dist/chunks/tail-D2o2h5cc.mjs +246 -0
  6. package/dist/flows.mjs +11 -1
  7. package/dist/gtd.bundle.mjs +12 -2
  8. package/dist/steering.mjs +1 -1
  9. package/dist/testkit.mjs +15 -3
  10. package/dist/web/main.js +1 -1
  11. package/dist/workflow-kit.mjs +1 -1
  12. package/dist/workflows/cleanup.mjs +2 -2
  13. package/dist/workflows/feature.mjs +44 -24
  14. package/dist/workflows/fix.mjs +3 -3
  15. package/dist/workflows/review.mjs +2 -2
  16. package/dist/workflows/rollout.mjs +1 -1
  17. package/dist/workflows.mjs +245 -36
  18. package/package.json +1 -1
  19. package/src/flows/scripts.ts +15 -0
  20. package/src/steering/review.test.ts +2 -20
  21. package/src/steering/review.ts +2 -1
  22. package/src/testkit/FlowScripts.test.ts +17 -0
  23. package/src/testkit/FlowScripts.ts +15 -0
  24. package/src/testkit/steps.ts +27 -0
  25. package/src/workflows/feature/coverage.feature +3 -3
  26. package/src/workflows/feature/fast-path.feature +2 -2
  27. package/src/workflows/feature/feature.test.ts +2 -37
  28. package/src/workflows/feature/feature.ts +12 -14
  29. package/src/workflows/feature/flag-mode.feature +2 -2
  30. package/src/workflows/feature/lens-review.feature +234 -0
  31. package/src/workflows/feature/one-step-land.feature +2 -2
  32. package/src/workflows/feature/quality-unresolved.feature +47 -32
  33. package/src/workflows/feature/refactor-absorbs.feature +3 -3
  34. package/src/workflows/feature/refactor-first.feature +5 -5
  35. package/src/workflows/feature/refactor-rebuild.feature +7 -7
  36. package/src/workflows/feature/refactor-review.feature +19 -100
  37. package/src/workflows/feature/refactor.test.ts +12 -27
  38. package/src/workflows/feature/refactor.ts +0 -1
  39. package/src/workflows/feature/review-gate.feature +6 -6
  40. package/src/workflows/feature/review.test.ts +29 -25
  41. package/src/workflows/feature/review.ts +53 -10
  42. package/src/workflows/feature/seam-rejection.feature +2 -2
  43. package/src/workflows/feature/seam.feature +2 -2
  44. package/src/workflows/feature/seam.test.ts +1 -1
  45. package/src/workflows/feature/slicing.feature +6 -6
  46. package/src/workflows/index.ts +1 -0
  47. package/src/workflows/shared/lenses.test.ts +39 -0
  48. package/src/workflows/shared/lenses.ts +176 -0
  49. package/src/workflows/shared/options.ts +1 -0
  50. package/src/workflows/shared/tail.ts +19 -4
  51. package/src/workflows/shared/text.test.ts +68 -1
  52. package/src/workflows/shared/text.ts +126 -13
  53. package/src/workflows/shared/vars.ts +1 -0
  54. package/dist/chunks/tail-lY1UmJrU.mjs +0 -112
@@ -90,6 +90,117 @@ What each change does next (then run \`gtd land\`):
90
90
  - **Continue** — having undone the sketch by hand, check the test baseline is green and start triage (**start-gate.check**).
91
91
  `
92
92
 
93
+ export const qualityTriageMessage = (): string =>
94
+ `Judging each quality finding. Run \`gtd judge answer\` and pipe a
95
+ \`mechanical\`/\`noise\`/\`design\` verdict per finding ID — or land without a
96
+ verdict, which hands every finding to the review as a note.
97
+ `
98
+
99
+ /** \`.gtd/QUALITY.md\` holding only \`findings\`, one \`## \` chunk each. */
100
+ export const qualityFindingsFile = (
101
+ findings: readonly { readonly title: string; readonly body: string }[],
102
+ ): string =>
103
+ `# Quality\n\n${findings.map((f) => `## ${f.title}\n\n${f.body}`.trim()).join("\n\n")}\n`
104
+
105
+ export interface ComposedReview {
106
+ readonly notes: readonly {
107
+ readonly title: string
108
+ readonly body: string
109
+ readonly unresolved?: true
110
+ /** A fix turn removed its chunk while renaming another. */
111
+ readonly unclear?: true
112
+ }[]
113
+ readonly fixed: readonly { readonly title: string; readonly commit: string }[]
114
+ readonly noise: readonly { readonly title: string; readonly p: number }[]
115
+ /** `Then` lines a `.feature` file at the review base no longer carries. */
116
+ readonly weakened?: readonly RemovedAssertions[]
117
+ /** The commit that fixed the human's nits, on the review that follows it. */
118
+ readonly nitFix?: string | undefined
119
+ }
120
+
121
+ const SUMMARY_TITLES = [
122
+ "Weakened scenarios",
123
+ "Fixed from your notes",
124
+ "Fixed automatically",
125
+ "Dropped as noise",
126
+ "Nothing to note",
127
+ ]
128
+
129
+ // A lens body is pasted as prose: a task box would read as a broken hunk pointer, or a `Risk:` one.
130
+ const unboxed = (body: string): string =>
131
+ body.replace(/^(\s*(?:>\s*)*(?:[-*+]|\d+[.)])\s+)\[[ xX]\]\s?/gm, "$1")
132
+
133
+ const short = (hash: string): string => hash.slice(0, 7)
134
+
135
+ /**
136
+ * \`.gtd/REVIEW.md\` as gtd writes it: one chunk per note, then the fixed and
137
+ * noise lists, with no tick boxes. Chunk titles are made unique — notes are
138
+ * matched to their chunk by title.
139
+ */
140
+ export const composedReview = (base: string, report: ComposedReview): string => {
141
+ // Summary titles are taken first, so no note can claim one.
142
+ const used = new Set(SUMMARY_TITLES)
143
+ const unique = (title: string): string => {
144
+ let name = title
145
+ for (let n = 2; used.has(name); n++) name = `${title} (${n})`
146
+ used.add(name)
147
+ return name
148
+ }
149
+ const chunk = (title: string, body: string): string => `## ${title}\n\n${body.trim()}`
150
+ const weakened = (report.weakened ?? []).flatMap(({ path, lines }) =>
151
+ lines.map((a) => `- ${path} — ${a.scenario} — ${a.line}`),
152
+ )
153
+ const chunks = [
154
+ ...(report.nitFix === undefined
155
+ ? []
156
+ : [
157
+ chunk(
158
+ "Fixed from your notes",
159
+ `Your nits were fixed in ${short(report.nitFix)}; the list below is from before.`,
160
+ ),
161
+ ]),
162
+ ...(weakened.length === 0
163
+ ? []
164
+ : [
165
+ chunk(
166
+ "Weakened scenarios",
167
+ `These scenarios lost an assertion the review base had:\n\n${weakened.join("\n")}`,
168
+ ),
169
+ ]),
170
+ ...report.notes.map((n) =>
171
+ chunk(
172
+ unique(n.title),
173
+ n.unresolved === true
174
+ ? `_The fixer left this unresolved._\n\n${unboxed(n.body)}`
175
+ : n.unclear === true
176
+ ? `_A fix turn removed this while renaming another finding — check whether it was fixed._\n\n${unboxed(n.body)}`
177
+ : unboxed(n.body) || "_No detail given._",
178
+ ),
179
+ ),
180
+ ...(report.fixed.length === 0
181
+ ? []
182
+ : [
183
+ chunk(
184
+ "Fixed automatically",
185
+ report.fixed.map((f) => `- ${f.title} (${short(f.commit)})`).join("\n"),
186
+ ),
187
+ ]),
188
+ ...(report.noise.length === 0
189
+ ? []
190
+ : [chunk("Dropped as noise", report.noise.map((f) => `- ${f.title} (p=${f.p})`).join("\n"))]),
191
+ ]
192
+ const body =
193
+ chunks.length > 0
194
+ ? chunks
195
+ : [
196
+ chunk(
197
+ "Nothing to note",
198
+ "No quality lens raised anything for you — or none ran. Review the diff yourself.",
199
+ ),
200
+ ]
201
+ return `# Review: ${short(base)}\n\n<!-- base: ${base} -->\n\n${body.join("\n\n")}\n`
202
+ }
203
+
93
204
  export const qualityUnresolvedMessage = (): string =>
94
205
  `The quality lap's fix turns hit their cap and \`.gtd/QUALITY.md\` still
95
206
  holds findings. Nothing after this fixes them, so the process will not
@@ -488,8 +599,10 @@ ${agentConduct}`
488
599
  export const buildFixQualityPrompt = (rules?: string): string =>
489
600
  `${stateFileRules}
490
601
  - Read \`.gtd/QUALITY.md\` — one \`## \` chunk per finding a quality
491
- dimension wrote. Merge duplicate findings across dimensions
492
- FIRST, then fix every finding in every chunk, blocking or not
602
+ dimension wrote. Fix every finding in every chunk, blocking or not;
603
+ where dimensions repeat one, fix the shared cause once
604
+ - Delete each finding's \`## \` chunk once it is resolved; never
605
+ rename, merge or reword a chunk you leave
493
606
  - When findings conflict, missing test signal beats line count —
494
607
  a test is never deleted to satisfy a simplification finding
495
608
  - Delete \`.gtd/QUALITY.md\` once every finding is resolved
@@ -541,9 +654,9 @@ export const buildQualityReviewingPrompt = (
541
654
  behaviour removed — decide that by reasoning, never by running a
542
655
  mutation-testing tool. A test that pins a bug is a finding, not
543
656
  praise
544
- - APPEND every finding you have, blocking or not, as a \`## \`
545
- chunk to \`.gtd/QUALITY.md\` — never overwrite what an earlier
546
- dimension already wrote there
657
+ - APPEND every finding you have, blocking or not, as its own
658
+ \`## <one-line title>\` chunk to \`.gtd/QUALITY.md\` — one finding per
659
+ chunk, never overwrite what an earlier dimension already wrote there
547
660
  - Write nothing only when this lens found nothing at all — then a
548
661
  clean turn IS this dimension's approval
549
662
  ${
@@ -563,12 +676,12 @@ export const reviewerSystem = (): string =>
563
676
  ${agentConduct}`
564
677
 
565
678
  export const buildReviewAwaitReviewMessage = (base: string): string =>
566
- `\`.gtd/REVIEW.md\` holds the review record for the process — one
567
- \`- [ ]\` checkbox per reviewable item, grouped into chunks. Tick a box
568
- (\`- [x]\`) as you review each hunk; ticking only records that you've read
569
- it, it is not sign-off. Ticks are read-progress only: landing clears
570
- every box back to \`- [ ]\` on disk and nothing records which hunks
571
- you read — there is no persisted trail of it.
679
+ `\`.gtd/REVIEW.md\` holds what the quality lenses left for you: one chunk
680
+ per finding that needs your judgement — it questions a criterion or the
681
+ approach, or the fixer could not resolve it — then, for reference only, what
682
+ was **fixed automatically** (with the fixing commit) and what was **dropped
683
+ as noise**. Nothing there needs an answer; disagreeing with a fix or a drop
684
+ means writing a note of your own.
572
685
 
573
686
  Review the diff yourself, with whatever tool you like — gtd checks
574
687
  nothing out and touches no ref; \`gtd base\` prints this same hash any
@@ -579,8 +692,8 @@ working tree:
579
692
  When you've been through the whole diff, run \`gtd land\`:
580
693
 
581
694
  - **Sign off** — leave no comment — no note in
582
- \`.gtd/REVIEW.md\`, no code edit — to close the process,
583
- whatever the boxes say. Every turn commit stays on the
695
+ \`.gtd/REVIEW.md\`, no code edit — to close the process.
696
+ Every turn commit stays on the
584
697
  branch; run \`gtd summary\` afterward for a closing-message
585
698
  prompt.
586
699
  - **Ask a question** — start a thread (\`- H: <question>\`) on a
@@ -23,6 +23,7 @@ export const defaults: Readonly<Record<string, string>> = {
23
23
  judgeIdenticalMinP: "0.7",
24
24
  reviewNoteActionable: "0.7",
25
25
  reviewSeverityMinP: "0.7",
26
+ qualityTriageMinP: "0.7",
26
27
  refactorFlagPattern: "\\bGTD_FLAG_\\w+",
27
28
  seamAllow: [...testFileGlobs, ".gtd/**"].join(", "),
28
29
  flagMode: "true",
@@ -1,112 +0,0 @@
1
- import { read, refuse, removeScript, run, scope, showScript, vars } from "../flows.mjs";
2
- import { fixFindings, lensEntriesOf, lensOf, qualityLap as qualityLap$1, reviewToHandOff } from "../workflow-kit.mjs";
3
- import { C as fixCheck, D as reviewMissing, O as reviewQuality, T as fixRisks, Y as handOffAwaitReviewMessage, d as guarded, f as healthy, h as QUALITY, k as reviewing, l as escalation, w as fixQuality, x as awaitReview, y as REVIEW } from "./reserved-Ch4LLrEj.mjs";
4
- //#region src/workflows/shared/tail.ts
5
- /** The reviewer's own `.gtd/REVIEW.md`, kept beside the human's notes so `fix` can tell them apart. */
6
- const REVIEW_ORIG = ".gtd/REVIEW.orig.md";
7
- /**
8
- * One review turn per lens over the whole change, each appending every
9
- * finding to `.gtd/QUALITY.md`. Resolves `"findings"` when that file
10
- * has any.
11
- */
12
- const qualityLap = (guard, lenses, base) => qualityLap$1(lenses ?? lensEntriesOf(vars.qualityReviews ?? ""), guarded((entry) => {
13
- const { lens, model } = lensOf(entry);
14
- return reviewQuality(lens, base, model);
15
- }, guard), QUALITY);
16
- /** Resolves `true` once `.gtd/QUALITY.md` is resolved, `false` when the fix cap escalated instead. */
17
- const fixQualityFindings = (escalations, guard, guardRests = true, rules) => fixFindings({
18
- fix: guarded(() => fixQuality(rules), guard),
19
- findings: QUALITY,
20
- cap: 3,
21
- escalate: () => escalation(escalations, guard, guardRests, rules)
22
- });
23
- /** `.gtd/REVIEW.md` removed at the end of a round at commit `round`. */
24
- const closeReview = (round) => run("review.closing", removeScript([REVIEW, REVIEW_ORIG]), {
25
- label: "Closing the review",
26
- base: round
27
- });
28
- /** Refuses while a reviewer's notes still await `gtd door fix`. */
29
- const refusePendingHandOff = () => {
30
- if (read(".gtd/REVIEW.orig.md") !== void 0) refuse(`gtd: ${REVIEW_ORIG} is a pending hand-off — run \`gtd door fix\`, or remove ${REVIEW} and ${REVIEW_ORIG}.`);
31
- };
32
- function tail(first, second, ...rest) {
33
- const form = tailForm(first, second, rest);
34
- if (form === void 0) return Promise.reject(/* @__PURE__ */ new Error("tail takes tail(finish, options?) or the deprecated tail(fixFirst, finish, green?, guard?, lenses?, guardRests?, lensBase?, rules?); this call fits neither"));
35
- const [finish, options] = form;
36
- return runTail(finish, options);
37
- }
38
- const isOptionalObject = (value) => value == null || typeof value === "object" && !Array.isArray(value) && typeof value.then !== "function";
39
- const tailForm = (first, second, rest) => {
40
- if (typeof second === "function" && typeof first !== "function") {
41
- const [green, guard, lenses, guardRests, lensBase, rules] = rest;
42
- return [second, {
43
- fixFirst: first,
44
- green,
45
- guard,
46
- lenses,
47
- guardRests,
48
- lensBase,
49
- rules
50
- }];
51
- }
52
- if (typeof first === "function" && isOptionalObject(second) && rest.every((v) => v == null)) return [first, second ?? {}];
53
- };
54
- const runTail = (finish, options) => scope("build", async () => {
55
- const { guard, lenses, lensBase, rules } = options;
56
- const guardRests = options.guardRests === void 0 ? true : options.guardRests;
57
- const escalations = { rounds: 0 };
58
- const fix = () => fixCheck(rules);
59
- const repair = async () => {
60
- await guarded(fix, guard)();
61
- await healthy(fix, {
62
- fixesSoFar: 1,
63
- escalations,
64
- guard,
65
- guardRests,
66
- rules
67
- });
68
- };
69
- if (options.fixFirst) await repair();
70
- else if (!options.green) await healthy(fix, {
71
- escalations,
72
- guard,
73
- guardRests,
74
- rules
75
- });
76
- if (await qualityLap(guard, lenses, lensBase) === "findings") if (await fixQualityFindings(escalations, guard, guardRests, rules)) await healthy(fix, {
77
- escalations,
78
- guard,
79
- guardRests,
80
- rules
81
- });
82
- else await repair();
83
- return finish(escalations);
84
- });
85
- /**
86
- * The review and fix workflows' human review since `base`: a sign-off closes
87
- * `.gtd/REVIEW.md`; a note leaves it, with the reviewer's own version as
88
- * `REVIEW_ORIG`, for the next `fix` to read — never a re-plan.
89
- */
90
- const handOffReview = (base, escalations, guard) => reviewToHandOff(base, {
91
- file: REVIEW,
92
- reviewing: guarded((b, carry) => reviewing(b, carry), guard),
93
- fixRisks: guarded(fixRisks, guard),
94
- keepGreen: () => healthy(fixCheck, {
95
- escalations,
96
- guard
97
- }),
98
- awaitReview: (b) => awaitReview(b, handOffAwaitReviewMessage(b)),
99
- reviewMissing,
100
- close: closeReview,
101
- handOff: (reviewed) => run("review.handing-off", showScript(reviewed, REVIEW, REVIEW_ORIG), { label: "Handing your notes to fix" }),
102
- threadsPending: "gtd land: open-threads: a thread in .gtd/REVIEW.md or an `H:` code comment waits on the agent — nothing answers it here; delete it, or turn it into a note for fix.",
103
- unparsed: "gtd land: review-doc: your edit to .gtd/REVIEW.md contains no note fix can read — restore the deleted chunk, or write the note as a line under its entry.",
104
- deleted: "gtd land: review-doc: .gtd/REVIEW.md was deleted — restore it, or leave a note to hand to fix."
105
- });
106
- /** The tail the review and fix workflows run: ends at the hand-off review since `base`. */
107
- const handOffTail = (fixFirst, base, guard) => tail((escalations) => handOffReview(base, escalations, guard), {
108
- fixFirst,
109
- guard
110
- });
111
- //#endregion
112
- export { refusePendingHandOff as a, isOptionalObject as i, closeReview as n, tail as o, handOffTail as r, REVIEW_ORIG as t };