@pmelab/gtd 33.0.0 → 34.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.
@@ -3,15 +3,16 @@ import {
3
3
  changesSince,
4
4
  head,
5
5
  human,
6
+ read,
6
7
  revertScript,
7
8
  run,
8
9
  slice,
9
10
  start,
10
11
  } from "../../flows/index.js"
11
- import { gate } from "../shared/health.js"
12
+ import { gate, healthy } from "../shared/health.js"
12
13
  import { flagModeOn, shipped } from "../shared/options.js"
13
- import { FEEDBACK } from "../shared/steps.js"
14
- import { refusePendingHandOff } from "../shared/tail.js"
14
+ import { FEEDBACK, fixCheck, QUALITY } from "../shared/steps.js"
15
+ import { refusePendingHandOff, tail } from "../shared/tail.js"
15
16
  import * as t from "../shared/text.js"
16
17
  import { skipDesign, statedCriteria, type StatedCriteria } from "./criteria.js"
17
18
  import { design } from "./planning.js"
@@ -22,9 +23,8 @@ import { recordRisk, riskTierOn } from "./tier.js"
22
23
  // The ordinary start. Any change to the tree starts a process: `idle` →
23
24
  // `unwind` reverts the sketch (its intent survives in history) → a
24
25
  // green-baseline gate → design → a judged naive pass (refactoring first when
25
- // the judge asks; with `flagMode` off, neither) → implement → the quality lap
26
- // → human review, which signs off (the episode ends back at `idle`) or sends a
27
- // design → implement lap.
26
+ // the judge asks; with `flagMode` off, neither) → implement → the quality lap,
27
+ // and the episode ends back at `idle`. Human review is the `review` door's.
28
28
 
29
29
  /** Revert the sketch that started the process out of the working tree; its intent survives in history. */
30
30
  export const unwind = (): Promise<void> => {
@@ -36,9 +36,7 @@ export const unwind = (): Promise<void> => {
36
36
 
37
37
  export { reUnwind }
38
38
 
39
- // seam.naive / seam.judge run once: a feedback lap is design → implement on every verdict, so a
40
- // seam a re-plan turns up is built unjudged and unrefactored.
41
- /** A judged naive pass (none with `flagMode` off), then implement and review until a review round signs off; design must have run since `firstBase`. */
39
+ /** A judged naive pass (none with `flagMode` off), then implement and the quality lap; design must have run since `firstBase`. */
42
40
  export const buildFeature = async (firstBase: string): Promise<void> => {
43
41
  const tier = riskTierOn() ? recordRisk() : undefined
44
42
  const seam: Omit<SeamOutcome, "reference"> & { reference?: string } = flagModeOn()
@@ -46,15 +44,31 @@ export const buildFeature = async (firstBase: string): Promise<void> => {
46
44
  : { base: firstBase }
47
45
  await slice("feature", async () => {
48
46
  await implement({ reference: seam.reference, tier, afterRefactor: seam.refactored })
49
- // An empty feature slice: the refactor carried the feature, so review the whole change.
47
+ // An empty feature slice: the refactor carried the feature, so the lap covers the whole change.
50
48
  const base =
51
49
  seam.refactored === true && !changesSince(seam.base).some((c) => isCode(c.path))
52
50
  ? firstBase
53
51
  : seam.base
54
- await featureFeedback(await buildTail(base, { green: true, lensBase: base }), base)
52
+ await tail(settleQuality, { green: true, lensBase: base })
55
53
  })
56
54
  }
57
55
 
56
+ // The lap is feature's last look: findings the fix cap left must not end the process, nor reach
57
+ // the next one's lap, which appends to the file. Empty counts as resolved, as the lap reads it.
58
+ const settleQuality = async (): Promise<void> => {
59
+ let rested = false
60
+ while ((read(QUALITY) ?? "").length > 0) {
61
+ rested = true
62
+ await human("quality-unresolved", {
63
+ message: t.qualityUnresolvedMessage(),
64
+ label: "Quality findings left unresolved",
65
+ file: QUALITY,
66
+ })
67
+ }
68
+ // The person may have fixed code by hand there; nothing else checks it before idle.
69
+ if (rested) await healthy(() => fixCheck())
70
+ }
71
+
58
72
  /** Design — skipped when the sketch's own criteria are judged clear — then `buildFeature`. */
59
73
  export const planAndBuild = async (firstBase: string, stated?: StatedCriteria): Promise<void> => {
60
74
  // GTD_FLAG_RISK_TIER: the tier is proposed at triage and set at the design gate, so no skip.
@@ -62,20 +76,19 @@ export const planAndBuild = async (firstBase: string, stated?: StatedCriteria):
62
76
  await buildFeature(firstBase)
63
77
  }
64
78
 
65
- const featureFeedback = async (first: ReviewOutcome, lensBase?: string): Promise<void> => {
79
+ // seam.naive / seam.judge never run here: a re-plan's seam is built unjudged and unrefactored.
80
+ /** After a tail that skipped planning: stop on sign-off, re-plan on feedback. Opens no slice: slices cannot nest. */
81
+ export const afterTail = async (first: ReviewOutcome): Promise<void> => {
66
82
  let outcome = first
67
83
  while (outcome.verdict === "feedback") {
68
84
  await reUnwind(outcome)
69
85
  await design(outcome.base)
70
86
  const tier = riskTierOn() ? recordRisk() : undefined
71
87
  await implement({ tier, amend: true })
72
- outcome = await buildTail(outcome.base, { green: true, lap: "feedback", lensBase })
88
+ outcome = await buildTail(outcome.base, { green: true, lap: "feedback" })
73
89
  }
74
90
  }
75
91
 
76
- /** After a tail that skipped planning: stop on sign-off, re-plan on feedback. Opens no slice: slices cannot nest. */
77
- export const afterTail = (outcome: ReviewOutcome): Promise<void> => featureFeedback(outcome)
78
-
79
92
  /** An ordinary start: wait at `idle`, unwind the sketch, check the baseline, then plan and build. */
80
93
  export const ordinaryStart = async (): Promise<void> => {
81
94
  await human("idle", { message: t.idleMessage(), label: "Idle", file: ".gtd/TODO.md" })
@@ -58,7 +58,7 @@ Feature: flag mode off builds a feature with no release flag, seam judge or refa
58
58
  And step "implement.closing" runs its script
59
59
  And step "build.quality.correctness.reviewing" changes nothing
60
60
  When the process is driven
61
- Then the process rests at "build.review.reviewing"
61
+ Then the process rests at "idle"
62
62
  And "src/calc.ts" contains "subtract"
63
63
  And the commit subjects end with:
64
64
  """
@@ -72,94 +72,5 @@ Feature: flag mode off builds a feature with no release flag, seam judge or refa
72
72
  gtd(agent): implement.building → implement.health.check
73
73
  gtd(check): implement.health.check → implement.closing
74
74
  gtd(check): implement.closing → build.quality.correctness.reviewing
75
- gtd(agent): build.quality.correctness.reviewing → build.review.reviewing
75
+ gtd(agent): build.quality.correctness.reviewing → idle
76
76
  """
77
-
78
- Scenario: flag mode off asks for no flag on a feedback lap either
79
- Given step "implement.building" writes "src/calc.ts" with:
80
- """
81
- export const add = (a: number, b: number) => a + b
82
- export const subtract = (a: number, b: number) => a - b
83
- """
84
- And step "implement.health.check" changes nothing
85
- And step "implement.closing" runs its script
86
- And step "build.quality.correctness.reviewing" changes nothing
87
- And step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
88
- """
89
- # Review: abc1234
90
-
91
- <!-- base: 0000000 -->
92
-
93
- ## calc
94
-
95
- - [ ] ./src/calc.ts#2-2
96
- new subtract function
97
- """
98
- When the process is driven
99
- Then the process rests at "build.review.await-review"
100
- Given a file ".gtd/REVIEW.md" with:
101
- """
102
- # Review: abc1234
103
-
104
- <!-- base: 0000000 -->
105
-
106
- ## calc
107
-
108
- - [ ] ./src/calc.ts#2-2
109
- new subtract function[^why]
110
-
111
- [^why]:
112
- - H: also export multiply
113
- """
114
- And step "build.review.collecting" writes ".gtd/REQUIREMENTS.md" with:
115
- """
116
- # Requirements
117
-
118
- ## Concerns
119
-
120
- ### Multiply
121
-
122
- - Export `multiply()`.
123
-
124
- ## Acceptance Criteria
125
-
126
- - AC-1: the change behaves as described.
127
- """
128
- And step "build.review.collecting" writes ".gtd/REVIEW.md" with:
129
- """
130
- # Review: abc1234
131
-
132
- <!-- base: 0000000 -->
133
-
134
- ## calc
135
-
136
- - [ ] ./src/calc.ts#2-2
137
- new subtract function[^why]
138
-
139
- [^why]:
140
- - H: also export multiply
141
- - A: noted in the requirements.
142
- """
143
- When gtd lands
144
- And the process is driven
145
- Then the process rests at "build.review.await-review"
146
- Given a file ".gtd/REVIEW.md" with:
147
- """
148
- # Review: abc1234
149
-
150
- <!-- base: 0000000 -->
151
-
152
- ## calc
153
-
154
- - [ ] ./src/calc.ts#2-2
155
- new subtract function
156
- """
157
- And step "build.review.closing" runs its script
158
- And step "re-unwind" runs its script
159
- And step "implement.building" is no longer scripted
160
- When gtd lands
161
- And the process is driven
162
- Then the process rests at "implement.building"
163
- And the resting step's content contains "Add no release flag — this project ships without release flags"
164
- And the resting step's content does not contain "release flag the earlier lap added"
165
- And no commit body contains "Gtd-Kept:"
@@ -4,8 +4,8 @@ Feature: a naive pass the judge rules land goes straight to implement with no se
4
4
 
5
5
  With GTD_FLAG_RISK_TIER off, a naive pass that
6
6
  edits an existing source file is judged land: the process skips the approval
7
- gate and any refactor, and runs implement, quality and
8
- review back to idle. The flag is pinned here so the per-flag test runs never
7
+ gate and any refactor, and runs implement and quality
8
+ back to idle, with no review. The flag is pinned here so the per-flag test runs never
9
9
  move it off this path.
10
10
 
11
11
  Background:
@@ -60,35 +60,9 @@ Feature: a naive pass the judge rules land goes straight to implement with no se
60
60
  And step "implement.health.check" changes nothing
61
61
  And step "implement.closing" runs its script
62
62
  And step "build.quality.correctness.reviewing" changes nothing
63
- And step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
64
- """
65
- # Review: abc1234
66
-
67
- <!-- base: 0000000 -->
68
-
69
- ## greeting
70
-
71
- - [ ] ./src/greeting.ts#1-1
72
- typo fixed
73
- """
74
- And step "build.review.closing" runs its script
75
63
 
76
64
  Scenario: a land judgment runs idle to idle with no seam gate or refactor
77
65
  When the process is driven
78
- Then the process rests at "build.review.await-review"
79
- Given a file ".gtd/REVIEW.md" with:
80
- """
81
- # Review: abc1234
82
-
83
- <!-- base: 0000000 -->
84
-
85
- ## greeting
86
-
87
- - [x] ./src/greeting.ts#1-1
88
- typo fixed
89
- """
90
- When gtd lands
91
- And the process is driven
92
66
  Then the process rests at "idle"
93
67
  And "src/greeting.ts" contains "Hello, world!"
94
68
  And the commit subjects end with:
@@ -104,10 +78,7 @@ Feature: a naive pass the judge rules land goes straight to implement with no se
104
78
  gtd(agent): implement.building → implement.health.check
105
79
  gtd(check): implement.health.check → implement.closing
106
80
  gtd(check): implement.closing → build.quality.correctness.reviewing
107
- gtd(agent): build.quality.correctness.reviewing → build.review.reviewing
108
- gtd(agent): build.review.reviewing → build.review.await-review
109
- gtd(human): build.review.await-review → build.review.closing
110
- gtd(check): build.review.closing → idle
81
+ gtd(agent): build.quality.correctness.reviewing → idle
111
82
  """
112
83
  And the commit "gtd(human): idle → unwind" has a body containing "Gtd-Var: flag_risk_tier=0"
113
84
  And the commit "gtd(agent): seam.naive → seam.check" has a body containing "Gtd-Kept:"
@@ -0,0 +1,96 @@
1
+ @inmem
2
+ Feature: quality findings left unresolved hold a feature process short of idle
3
+
4
+ The quality lap is the last look `feature` takes. When the fix cap leaves
5
+ `.gtd/QUALITY.md` behind, the process rests for a person instead of ending
6
+ with findings nobody acted on.
7
+
8
+ Background:
9
+ Given the shipped workflows, as gtd install scaffolds them
10
+ And an environment variable "GTD_QUALITYREVIEWS" set to "correctness"
11
+ And an environment variable "GTD_FLAGMODE" set to "false"
12
+ And a file "src/calc.ts" with:
13
+ """
14
+ export const add = (a: number, b: number) => a + b
15
+ """
16
+ And the working tree is committed as "feat: add calculator"
17
+ And a file "TODO.md" with:
18
+ """
19
+ # Add subtract
20
+
21
+ subtract(a, b) returns the difference.
22
+ """
23
+ And step "unwind" changes nothing
24
+ And step "start-gate.check" changes nothing
25
+ And step "design.triage" writes ".gtd/REQUIREMENTS.md" with:
26
+ """
27
+ # Requirements
28
+
29
+ ## Concerns
30
+
31
+ ### Subtract
32
+
33
+ - `subtract(a, b)` returns `a - b`.
34
+
35
+ ## Acceptance Criteria
36
+
37
+ - AC-1: subtract(a, b) returns a - b.
38
+ """
39
+ And step "design.gate.answer" changes nothing
40
+ And step "implement.building" writes "src/calc.ts" with:
41
+ """
42
+ export const add = (a: number, b: number) => a + b
43
+ export const subtract = (a: number, b: number) => a - b
44
+ """
45
+ And step "implement.health.check" changes nothing
46
+ And step "implement.closing" runs its script
47
+
48
+ Scenario: findings the fix cap left unresolved rest at build.quality-unresolved
49
+ Given step "build.quality.correctness.reviewing" writes ".gtd/QUALITY.md" with:
50
+ """
51
+ # Quality
52
+
53
+ - subtract has no test
54
+ """
55
+ And step "build.fix.quality.fixing" from now on changes nothing
56
+ And step "build.health.describe" writes ".gtd/ESCALATION.md" with:
57
+ """
58
+ # Escalation
59
+
60
+ The fix turn changed nothing.
61
+ """
62
+ When the process is driven
63
+ Then the process rests at "build.health.stop"
64
+ Given step "build.fix" writes "src/calc.ts" with:
65
+ """
66
+ export const add = (a: number, b: number) => a + b
67
+ export const subtract = (a: number, b: number) => a - b
68
+ // checked
69
+ """
70
+ And step "build.health.check" changes nothing
71
+ When gtd lands
72
+ And the process is driven
73
+ Then the process rests at "build.quality-unresolved"
74
+ And ".gtd/QUALITY.md" contains "subtract has no test"
75
+
76
+ When gtd lands
77
+ And the process is driven
78
+ Then the process rests at "build.quality-unresolved"
79
+
80
+ Given a file "src/calc.ts" with:
81
+ """
82
+ export const add = (a: number, b: number) => a + b
83
+ export const subtract = (a: number, b: number) => a - b
84
+ // checked by hand
85
+ """
86
+ And the file ".gtd/QUALITY.md" is deleted
87
+ And step "build.health.check" changes nothing
88
+ When gtd lands
89
+ And the process is driven
90
+ Then the process rests at "idle"
91
+ And the commit subjects end with:
92
+ """
93
+ gtd(check): build.health.check → build.quality-unresolved
94
+ gtd(human): build.quality-unresolved → build.health.check
95
+ gtd(check): build.health.check → idle
96
+ """
@@ -1,16 +1,16 @@
1
1
  # One SDLC path per file, so a changed path reds exactly one fixture — do not merge.
2
2
  @inmem
3
- Feature: a refactor that carried the whole feature still gets a feature review
3
+ Feature: a refactor that carried the whole feature still gets a quality lap over all of it
4
4
 
5
5
  With GTD_FLAG_RISK_TIER off, a refactor-first
6
6
  refactor can land everything the requirements ask for. The feature slice's
7
7
  implement turn then has nothing to author: its empty turn completes the
8
- step, and with no code changed since the refactor the feature review covers
9
- the whole change, refactor included.
8
+ step, and with no code changed since the refactor the feature slice's
9
+ quality lap covers the whole change, refactor included.
10
10
 
11
11
  Background:
12
12
  Given the shipped workflows, as gtd install scaffolds them
13
- And an environment variable "GTD_QUALITYREVIEWS" set to ""
13
+ And an environment variable "GTD_QUALITYREVIEWS" set to "correctness"
14
14
  And an environment variable "GTD_REFACTORQUALITYREVIEWS" set to ""
15
15
  And an environment variable "GTD_FLAG_RISK_TIER" set to "0"
16
16
  And a file "src/calc.ts" with:
@@ -42,7 +42,7 @@ Feature: a refactor that carried the whole feature still gets a feature review
42
42
  """
43
43
  And step "design.gate.answer" changes nothing
44
44
 
45
- Scenario: an empty implement turn after a refactor that did it all reaches review and idle
45
+ Scenario: an empty implement turn after a refactor that did it all reaches the quality lap and idle
46
46
  Given step "seam.check" changes nothing
47
47
  And step "seam.naive" writes "src/acceptance.test.ts" with:
48
48
  """
@@ -94,25 +94,14 @@ Feature: a refactor that carried the whole feature still gets a feature review
94
94
  And step "implement.building" changes nothing
95
95
  And step "implement.health.check" changes nothing
96
96
  And step "implement.closing" runs its script
97
- And step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
98
- """
99
- # Review: abc1234
100
-
101
- <!-- base: 0000000 -->
102
-
103
- ## calc
104
-
105
- - [ ] ./src/calc.ts#1-2
106
- subtract next to add
107
- """
108
97
  When gtd lands
109
98
  And the process is driven
110
- Then the process rests at "build.review.await-review"
99
+ Then the process rests at "build.quality.correctness.reviewing"
100
+ And the resting step's content contains the hash of the commit "feat: add calculator"
111
101
  And ".gtd/REQUIREMENTS.md" does not exist
112
102
 
113
- Given step "build.review.closing" runs its script
114
- When gtd lands
115
- And the process is driven
103
+ Given step "build.quality.correctness.reviewing" changes nothing
104
+ When the process is driven
116
105
  Then the process rests at "idle"
117
106
  And "src/calc.ts" contains "subtract"
118
107
  And the commit subjects end with:
@@ -121,10 +110,8 @@ Feature: a refactor that carried the whole feature still gets a feature review
121
110
  gtd(check): refactor.build.review.closing → implement.building
122
111
  gtd(agent): implement.building → implement.health.check
123
112
  gtd(check): implement.health.check → implement.closing
124
- gtd(check): implement.closing → build.review.reviewing
125
- gtd(agent): build.review.reviewing → build.review.await-review
126
- gtd(human): build.review.await-review → build.review.closing
127
- gtd(check): build.review.closing → idle
113
+ gtd(check): implement.closing → build.quality.correctness.reviewing
114
+ gtd(agent): build.quality.correctness.reviewing → idle
128
115
  """
129
116
  And the output lines of gtd "slices" start with:
130
117
  """
@@ -4,7 +4,7 @@ Feature: a refactor-first judgment walks one fixed path to idle
4
4
 
5
5
  With GTD_FLAG_RISK_TIER off, a naive pass touching
6
6
  an existing non-allowlisted file is ruled refactor-first, is refactored and
7
- that refactor reviewed, and only then implemented. The flags are
7
+ that refactor reviewed, and only then implemented, with no feature review. The flags are
8
8
  pinned so the per-flag test runs never move it off its path.
9
9
 
10
10
  Background:
@@ -95,18 +95,6 @@ Feature: a refactor-first judgment walks one fixed path to idle
95
95
  - [x] ./src/calc.ts#1-2
96
96
  add built on the shared op
97
97
  """
98
- And step "build.review.reviewing" writes ".gtd/REVIEW.md" with:
99
- """
100
- # Review: abc1234
101
-
102
- <!-- base: 0000000 -->
103
-
104
- ## calc
105
-
106
- - [ ] ./src/calc.ts#1-3
107
- subtract built on the shared op
108
- """
109
- And step "build.review.closing" runs its script
110
98
  When gtd lands
111
99
  And the process is driven
112
100
  Then the process rests at "implement.building"
@@ -121,21 +109,6 @@ Feature: a refactor-first judgment walks one fixed path to idle
121
109
  And step "implement.health.check" changes nothing
122
110
  And step "implement.closing" runs its script
123
111
  When the process is driven
124
- Then the process rests at "build.review.await-review"
125
-
126
- Given a file ".gtd/REVIEW.md" with:
127
- """
128
- # Review: abc1234
129
-
130
- <!-- base: 0000000 -->
131
-
132
- ## calc
133
-
134
- - [x] ./src/calc.ts#1-3
135
- subtract built on the shared op
136
- """
137
- When gtd lands
138
- And the process is driven
139
112
  Then the process rests at "idle"
140
113
  And "src/calc.ts" contains "op(a, b, 1)"
141
114
  And "src/calc.ts" contains "subtract"
@@ -158,10 +131,7 @@ Feature: a refactor-first judgment walks one fixed path to idle
158
131
  gtd(check): refactor.build.review.closing → implement.building
159
132
  gtd(agent): implement.building → implement.health.check
160
133
  gtd(check): implement.health.check → implement.closing
161
- gtd(check): implement.closing → build.review.reviewing
162
- gtd(agent): build.review.reviewing → build.review.await-review
163
- gtd(human): build.review.await-review → build.review.closing
164
- gtd(check): build.review.closing → idle
134
+ gtd(check): implement.closing → idle
165
135
  """
166
136
  And the commit "gtd(human): idle → unwind" has a body containing "Gtd-Var: flag_risk_tier=0"
167
137
  And the commit "gtd(agent): seam.naive → seam.check" has a body containing "Gtd-Kept:"