@pmelab/gtd 51.0.1 → 52.1.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 (74) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +46 -40
  3. package/dist/chunks/{reserved-CUkk9XgL.mjs → reserved-DxB5CROj.mjs} +73 -38
  4. package/dist/chunks/{review-BaCXlIT2.mjs → review-CKMSdvgW.mjs} +11 -9
  5. package/dist/chunks/{steering-DtUVUz9l.mjs → steering-CLhu8C7q.mjs} +262 -52
  6. package/dist/chunks/{tail-CX6Pdsql.mjs → tail-QZc7Uz2U.mjs} +1 -1
  7. package/dist/gtd.bundle.mjs +198 -43
  8. package/dist/steering.mjs +2 -2
  9. package/dist/testkit.mjs +1 -1
  10. package/dist/web/main.css +1 -1
  11. package/dist/web/main.js +510 -147
  12. package/dist/workflow-kit.mjs +1 -1
  13. package/dist/workflows/cleanup.mjs +2 -2
  14. package/dist/workflows/feature.mjs +228 -222
  15. package/dist/workflows/fix.mjs +3 -3
  16. package/dist/workflows/review.mjs +3 -3
  17. package/dist/workflows/rollout.mjs +1 -1
  18. package/dist/workflows.mjs +570 -317
  19. package/package.json +5 -4
  20. package/src/steering/FORMATS.md +4 -1
  21. package/src/steering/SteeringFormat.ts +7 -1
  22. package/src/steering/contract.test.ts +2 -1
  23. package/src/steering/fixtures/review/sectioned.md +31 -0
  24. package/src/steering/fixtures/review/sectioned.read.json +119 -0
  25. package/src/steering/index.ts +4 -0
  26. package/src/steering/qa.ts +1 -1
  27. package/src/steering/review.test.ts +270 -4
  28. package/src/steering/review.ts +328 -69
  29. package/src/testkit/steps.ts +13 -0
  30. package/src/workflows/feature/acceptance-criteria.feature +159 -3
  31. package/src/workflows/feature/continuation-cap.feature +4 -0
  32. package/src/workflows/feature/continuation-rebuild.feature +4 -0
  33. package/src/workflows/feature/continuation-slices.feature +4 -0
  34. package/src/workflows/feature/continuation.feature +4 -0
  35. package/src/workflows/feature/criteria.test.ts +60 -1
  36. package/src/workflows/feature/criteria.ts +87 -8
  37. package/src/workflows/feature/fast-path.feature +5 -6
  38. package/src/workflows/feature/feature.test.ts +6 -5
  39. package/src/workflows/feature/feature.ts +1 -2
  40. package/src/workflows/feature/flag-mode.feature +5 -6
  41. package/src/workflows/feature/green.ts +22 -0
  42. package/src/workflows/feature/hands-on-skip.feature +384 -0
  43. package/src/workflows/feature/lens-models.feature +4 -4
  44. package/src/workflows/feature/lens-review.feature +10 -8
  45. package/src/workflows/feature/naive-acceptance.feature +4 -0
  46. package/src/workflows/feature/one-step-land.feature +5 -6
  47. package/src/workflows/feature/planning.ts +2 -1
  48. package/src/workflows/feature/prototype-rounds.feature +5 -1
  49. package/src/workflows/feature/prototype.feature +18 -9
  50. package/src/workflows/feature/prototype.ts +48 -17
  51. package/src/workflows/feature/refactor-absorbs.feature +7 -8
  52. package/src/workflows/feature/refactor-first.feature +7 -8
  53. package/src/workflows/feature/refactor-lens-models.feature +4 -0
  54. package/src/workflows/feature/refactor-rebuild.feature +11 -12
  55. package/src/workflows/feature/refactor-review.feature +72 -72
  56. package/src/workflows/feature/review-answers.feature +94 -5
  57. package/src/workflows/feature/review-gate.feature +23 -53
  58. package/src/workflows/feature/reviewer-walkthrough.feature +33 -12
  59. package/src/workflows/feature/risk-tier.feature +32 -0
  60. package/src/workflows/feature/seam-rejection.feature +9 -6
  61. package/src/workflows/feature/seam.feature +9 -6
  62. package/src/workflows/feature/seam.test.ts +5 -3
  63. package/src/workflows/feature/seam.ts +2 -3
  64. package/src/workflows/feature/severity-budget.feature +11 -7
  65. package/src/workflows/feature/severity-sorted-review.feature +54 -54
  66. package/src/workflows/feature/slice-findings.feature +9 -15
  67. package/src/workflows/feature/slicing.feature +36 -24
  68. package/src/workflows/feature/standards.feature +4 -4
  69. package/src/workflows/feature/tier-pin.feature +4 -0
  70. package/src/workflows/shared/review.ts +26 -6
  71. package/src/workflows/shared/text.test.ts +85 -11
  72. package/src/workflows/shared/text.ts +95 -42
  73. package/src/workflows/feature/coverage.feature +0 -165
  74. package/src/workflows/feature/coverage.ts +0 -120
@@ -1,11 +1,21 @@
1
- import { changes, human, read, refuse, scope, vars } from "../../flows/index.js"
1
+ import { answered, changes, human, judge, read, refuse, scope, vars } from "../../flows/index.js"
2
2
  import { HEADING, markFenced } from "../shared/fences.js"
3
3
  import { coder, PROTOTYPE, REQUIREMENTS } from "../shared/steps.js"
4
4
  import * as t from "../shared/text.js"
5
5
  import { defaults } from "../shared/vars.js"
6
6
  import type { Guard } from "../shared/health.js"
7
- import { green } from "./coverage.js"
8
- import { criterionIds, requireCriteriaFrozen, sliceIds, type CriteriaSlice } from "./criteria.js"
7
+ import { green } from "./green.js"
8
+ import {
9
+ criteriaOf,
10
+ criterionIds,
11
+ handsOnIds,
12
+ requireCriteriaFrozen,
13
+ sliceIds,
14
+ type CriteriaSlice,
15
+ } from "./criteria.js"
16
+
17
+ /** Only a `no` at this or above skips a criterion: no verdict, or an unsure one, sends it to the hands-on test. */
18
+ const UNOBSERVABLE_MIN_P = 0.5
9
19
 
10
20
  /** The `prototypeRounds` setting. Anything but a non-negative integer fails rather than guess a cap. */
11
21
  export const prototypeRounds = (): number => {
@@ -55,11 +65,11 @@ export const reportOf = (text: string): Report => {
55
65
 
56
66
  /**
57
67
  * The report the testing turn left, refused unless it is the only change and
58
- * names every criterion once, with its open items: another slice's criteria
59
- * are not this slice's to fix.
68
+ * names each of `ids` once, with its open items: any other criterion is not
69
+ * this test's to fix.
60
70
  */
61
71
  const requireReport = (
62
- slice: CriteriaSlice | undefined,
72
+ ids: readonly string[],
63
73
  retest: boolean,
64
74
  ): { readonly unguided: boolean; readonly open: readonly Item[] } => {
65
75
  const others = changes()
@@ -79,27 +89,45 @@ const requireReport = (
79
89
  // Only a first test with nothing else in it: a chunk beside it was tested after all, and a
80
90
  // re-test's open items must reach a person, not slip past as untested.
81
91
  if (report.unguided && report.items.length === 0 && !retest) return { unguided: true, open: [] }
82
- const requirements = read(REQUIREMENTS) ?? ""
83
- const ids = criterionIds(requirements, slice)
84
- const elsewhere = criterionIds(requirements).filter((id) => !ids.includes(id))
92
+ const elsewhere = criterionIds(read(REQUIREMENTS) ?? "").filter((id) => !ids.includes(id))
85
93
  const count = (id: string): number => report.items.filter((i) => i.subject === id).length
86
94
  const wrong = ids.filter((id) => count(id) !== 1)
87
95
  if (wrong.length > 0)
88
96
  refuse(
89
97
  `gtd land: prototype-report: ${PROTOTYPE} needs exactly one chunk headed \`## <ID>: pass|fail|blocked\` for ${wrong.join(", ")}`,
90
98
  )
91
- // A mistyped ID stays open: only another slice's criteria are not this one's to fix.
99
+ // A mistyped ID stays open: only a known criterion outside `ids` is not this test's to fix.
92
100
  const open = report.items.filter((i) => i.verdict !== "pass" && !elsewhere.includes(i.subject))
93
101
  return { unguided: false, open }
94
102
  }
95
103
 
104
+ /**
105
+ * The slice's criteria a person can observe by using the change, by the
106
+ * judge: those tagged `(no hands-on)` are never asked about.
107
+ */
108
+ const observable = async (slice: CriteriaSlice | undefined): Promise<string[]> => {
109
+ const requirements = read(REQUIREMENTS) ?? ""
110
+ const own = handsOnIds(requirements, slice)
111
+ const criteria = (criteriaOf(requirements) ?? []).filter((c) => own.includes(c.id))
112
+ if (criteria.length === 0) return []
113
+ const { answers } = await judge("observable", {
114
+ questions: criteria.map(t.observableQuestion),
115
+ evidence: { requirements },
116
+ message: t.observableMessage(),
117
+ label: "Judging which criteria to test by hand",
118
+ })
119
+ return own.filter((id) => !answered(answers[id], "no", UNOBSERVABLE_MIN_P))
120
+ }
121
+
96
122
  const frozen = (): void => requireCriteriaFrozen(REQUIREMENTS)
97
123
 
98
124
  const testing = async (
99
125
  slice: CriteriaSlice | undefined,
126
+ ids: readonly string[],
100
127
  retest: boolean,
101
128
  ): Promise<ReturnType<typeof requireReport>> => {
102
- await t.agentWithSkills("testing", t.prototypeTestingPrompt(sliceIds(slice), retest), {
129
+ const skipped = criterionIds(read(REQUIREMENTS) ?? "", slice).filter((id) => !ids.includes(id))
130
+ await t.agentWithSkills("testing", t.prototypeTestingPrompt(sliceIds(slice), skipped, retest), {
103
131
  label: retest ? "Re-testing the slice by hand" : "Testing the slice by hand",
104
132
  file: PROTOTYPE,
105
133
  model: coder(),
@@ -107,7 +135,7 @@ const testing = async (
107
135
  // A test that saw exactly what the last round saw leaves the report as it was.
108
136
  allowEmpty: true,
109
137
  })
110
- return requireReport(slice, retest)
138
+ return requireReport(ids, retest)
111
139
  }
112
140
 
113
141
  const fixing = async (slice: CriteriaSlice | undefined, guard?: Guard): Promise<void> => {
@@ -138,10 +166,11 @@ const askPerson = async (name: string, message: string, label: string): Promise<
138
166
  }
139
167
 
140
168
  /**
141
- * Use the slice by hand: a testing turn reports on every criterion, a fix turn
142
- * takes each failure, and the slice is tested again — until the report is
143
- * clean, or `prototypeRounds` fixes are spent and a person decides. `guard`
144
- * holds every fix turn, as it holds the implementation's.
169
+ * Use the slice by hand: a testing turn reports on every criterion a person
170
+ * can observe, a fix turn takes each failure, and the slice is tested again —
171
+ * until the report is clean, or `prototypeRounds` fixes are spent and a person
172
+ * decides. A slice with no such criterion is not tested by hand. `guard` holds
173
+ * every fix turn, as it holds the implementation's.
145
174
  */
146
175
  export const prototypeTest = async (
147
176
  slice: CriteriaSlice | undefined,
@@ -150,10 +179,12 @@ export const prototypeTest = async (
150
179
  const cap = prototypeRounds()
151
180
  if (cap === 0) return
152
181
  await scope("prototype", async () => {
182
+ const ids = await observable(slice)
183
+ if (ids.length === 0) return
153
184
  let fixes = 0
154
185
  let retest = false
155
186
  for (;;) {
156
- const { unguided, open } = await testing(slice, retest)
187
+ const { unguided, open } = await testing(slice, ids, retest)
157
188
  retest = !unguided
158
189
  if (unguided) {
159
190
  const instructed = await askPerson(
@@ -37,6 +37,10 @@ Feature: a refactor that carried the whole feature still gets a quality lap over
37
37
 
38
38
  - `subtract(a, b)` returns `a - b`.
39
39
 
40
+ ## Impact
41
+
42
+ - the greeting module
43
+
40
44
  ## Acceptance Criteria
41
45
 
42
46
  - AC-1: the change behaves as described.
@@ -96,9 +100,9 @@ Feature: a refactor that carried the whole feature still gets a quality lap over
96
100
 
97
101
  <!-- base: 0000000 -->
98
102
 
99
- ## calc
103
+ ## Walkthrough
100
104
 
101
- _critical: judge unsure_
105
+ ### calc
102
106
 
103
107
  - [x] ./src/calc.ts#1-2
104
108
  subtract next to add
@@ -109,10 +113,6 @@ Feature: a refactor that carried the whole feature still gets a quality lap over
109
113
  """
110
114
  And step "implement.health.check" changes nothing
111
115
  And step "implement.closing" runs its script
112
- And judge step "implement.coverage" answers:
113
- """
114
- [{"id":"AC-1","answer":"yes","p":0.9}]
115
- """
116
116
  When gtd lands
117
117
  And the process is driven
118
118
  Then the process rests at "build.quality.correctness.reviewing"
@@ -128,8 +128,7 @@ Feature: a refactor that carried the whole feature still gets a quality lap over
128
128
  gtd(human): refactor.build.review.await-review → refactor.build.review.closing
129
129
  gtd(check): refactor.build.review.closing → implement.building
130
130
  gtd(agent): implement.building → implement.health.check
131
- gtd(check): implement.health.check → implement.coverage
132
- gtd(judge): implement.coverage → implement.closing
131
+ gtd(check): implement.health.check → implement.closing
133
132
  gtd(check): implement.closing → build.quality.correctness.reviewing
134
133
  gtd(agent): build.quality.correctness.reviewing → build.review.reviewing
135
134
  """
@@ -36,6 +36,10 @@ Feature: a refactor-first judgment walks one fixed path to the review gate
36
36
 
37
37
  - `subtract(a, b)` returns `a - b`.
38
38
 
39
+ ## Impact
40
+
41
+ - the greeting module
42
+
39
43
  ## Acceptance Criteria
40
44
 
41
45
  - AC-1: the change behaves as described.
@@ -100,9 +104,9 @@ Feature: a refactor-first judgment walks one fixed path to the review gate
100
104
 
101
105
  <!-- base: 0000000 -->
102
106
 
103
- ## calc
107
+ ## Walkthrough
104
108
 
105
- _critical: judge unsure_
109
+ ### calc
106
110
 
107
111
  - [x] ./src/calc.ts#1-2
108
112
  add built on the shared op
@@ -124,10 +128,6 @@ Feature: a refactor-first judgment walks one fixed path to the review gate
124
128
  """
125
129
  And step "implement.health.check" changes nothing
126
130
  And step "implement.closing" runs its script
127
- And judge step "implement.coverage" answers:
128
- """
129
- [{"id":"AC-1","answer":"yes","p":0.9}]
130
- """
131
131
  When the process is driven
132
132
  Then the process rests at "build.review.reviewing"
133
133
  And "src/calc.ts" contains "op(a, b, 1)"
@@ -153,8 +153,7 @@ Feature: a refactor-first judgment walks one fixed path to the review gate
153
153
  gtd(human): refactor.build.review.await-review → refactor.build.review.closing
154
154
  gtd(check): refactor.build.review.closing → implement.building
155
155
  gtd(agent): implement.building → implement.health.check
156
- gtd(check): implement.health.check → implement.coverage
157
- gtd(judge): implement.coverage → implement.closing
156
+ gtd(check): implement.health.check → implement.closing
158
157
  gtd(check): implement.closing → build.review.reviewing
159
158
  """
160
159
  And the commit "gtd(human): idle → unwind" has a body containing "Gtd-Var: flag_risk_tier=0"
@@ -29,6 +29,10 @@ Feature: the refactor review's lenses take their model hints from refactorQualit
29
29
 
30
30
  - `subtract(a, b)` returns `a - b`.
31
31
 
32
+ ## Impact
33
+
34
+ - the greeting module
35
+
32
36
  ## Acceptance Criteria
33
37
 
34
38
  - AC-1: the change behaves as described.
@@ -37,6 +37,10 @@ Feature: refactor-first writes a refactor note, refactors on its own, then rebui
37
37
 
38
38
  - `subtract(a, b)` returns `a - b`.
39
39
 
40
+ ## Impact
41
+
42
+ - the greeting module
43
+
40
44
  ## Acceptance Criteria
41
45
 
42
46
  - AC-1: subtract returns the difference.
@@ -110,9 +114,9 @@ Feature: refactor-first writes a refactor note, refactors on its own, then rebui
110
114
 
111
115
  <!-- base: 0000000 -->
112
116
 
113
- ## calc
117
+ ## Walkthrough
114
118
 
115
- _critical: judge unsure_
119
+ ### calc
116
120
 
117
121
  - [x] ./src/calc.ts#1-2
118
122
  add built on the shared op
@@ -181,9 +185,9 @@ Feature: refactor-first writes a refactor note, refactors on its own, then rebui
181
185
 
182
186
  <!-- base: 0000000 -->
183
187
 
184
- ## calc
188
+ ## Walkthrough
185
189
 
186
- _critical: judge unsure_
190
+ ### calc
187
191
 
188
192
  - [x] ./src/calc.ts#1-2
189
193
  add built on the shared op
@@ -234,9 +238,9 @@ Feature: refactor-first writes a refactor note, refactors on its own, then rebui
234
238
 
235
239
  <!-- base: 0000000 -->
236
240
 
237
- ## calc
241
+ ## Walkthrough
238
242
 
239
- _critical: judge unsure_
243
+ ### calc
240
244
 
241
245
  - [x] ./src/calc.ts#1-2
242
246
  add built on the shared op
@@ -259,10 +263,6 @@ Feature: refactor-first writes a refactor note, refactors on its own, then rebui
259
263
  """
260
264
  And step "implement.health.check" changes nothing
261
265
  And step "implement.closing" runs its script
262
- And judge step "implement.coverage" answers:
263
- """
264
- [{"id":"AC-1","answer":"yes","p":0.9}]
265
- """
266
266
  When the process is driven
267
267
  Then the process rests at "build.review.reviewing"
268
268
  And "src/calc.ts" contains "op(a, b, -1)"
@@ -282,8 +282,7 @@ Feature: refactor-first writes a refactor note, refactors on its own, then rebui
282
282
  gtd(human): refactor.build.review.await-review → refactor.build.review.closing
283
283
  gtd(check): refactor.build.review.closing → implement.building
284
284
  gtd(agent): implement.building → implement.health.check
285
- gtd(check): implement.health.check → implement.coverage
286
- gtd(judge): implement.coverage → implement.closing
285
+ gtd(check): implement.health.check → implement.closing
287
286
  gtd(check): implement.closing → build.review.reviewing
288
287
  """
289
288
  And the output lines of gtd "slices" start with: