@pmelab/gtd 1.6.1 → 1.8.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.
- package/README.md +99 -45
- package/dist/gtd.bundle.mjs +188 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ merge-base with the default branch (whole-history fallback when there is no
|
|
|
17
17
|
default branch, when HEAD equals the merge-base, or when there is no merge-base
|
|
18
18
|
— i.e. budgets engage on the default branch too) plus the working tree, turns
|
|
19
19
|
them into a `COMMIT[]` + single terminal `RESOLVE` event stream, and folds them
|
|
20
|
-
through the machine. The fold lands on exactly **one** of
|
|
20
|
+
through the machine. The fold lands on exactly **one** of 19 states, which
|
|
21
21
|
selects the prompt. A single run resolves to a single state.
|
|
22
22
|
|
|
23
23
|
`resolve()` returns that state plus an optional **`EdgeAction`** (a commit,
|
|
@@ -138,6 +138,11 @@ across runs:
|
|
|
138
138
|
- **ERRORS.md** — the escalation gate: persistent test-failure output that stops
|
|
139
139
|
the loop for a human (written instead of FEEDBACK.md once the fix-attempt cap
|
|
140
140
|
is hit; never auto-consumed).
|
|
141
|
+
- **HEALTH.md** — idle health-check failure output: written when `testCommand`
|
|
142
|
+
fails on a bare idle tree (no `.gtd`, no REVIEW.md, no FEEDBACK.md). Carries
|
|
143
|
+
the failure while the dedicated health-fix loop repairs it. Analogous to
|
|
144
|
+
FEEDBACK.md but lives on the default-branch idle path rather than inside a
|
|
145
|
+
build process. Never written while any other steering file is present.
|
|
141
146
|
- **.gtd/** — ordered work packages (one numbered directory each) of
|
|
142
147
|
parallelizable subtasks.
|
|
143
148
|
|
|
@@ -158,9 +163,11 @@ Every run derives the state in **three layers**:
|
|
|
158
163
|
1. **Transport pre-pass** — if HEAD is `gtd: transport`, short-circuit to the
|
|
159
164
|
Transport state (mixed-reset) before anything else is considered.
|
|
160
165
|
2. **Steering-file precedence** — the presence of `ERRORS.md` / `FEEDBACK.md` /
|
|
161
|
-
`.gtd/` / `REVIEW.md` drives the decision, authoritative
|
|
166
|
+
`HEALTH.md` / `.gtd/` / `REVIEW.md` drives the decision, authoritative
|
|
167
|
+
regardless of HEAD.
|
|
162
168
|
3. **HEAD bucket** — with no steering files in play, the last-commit bucket plus
|
|
163
|
-
working-tree cleanliness selects New Feature / Grilling / Clean /
|
|
169
|
+
working-tree cleanliness selects New Feature / Grilling / Clean / Health
|
|
170
|
+
check / Idle.
|
|
164
171
|
|
|
165
172
|
Within layers 2 and 3 the HEAD subject further disambiguates states the
|
|
166
173
|
filesystem alone cannot separate (e.g. inside the `.gtd/` lifecycle, HEAD
|
|
@@ -173,8 +180,8 @@ The complete set:
|
|
|
173
180
|
|
|
174
181
|
`gtd: new task` · `gtd: grilling` · `gtd: grilled` · `gtd: planning` ·
|
|
175
182
|
`gtd: building` · `gtd: errors` · `gtd: feedback` · `gtd: fixing` ·
|
|
176
|
-
`gtd: package done` · `gtd: awaiting review` · `gtd: done`
|
|
177
|
-
`gtd: transport` (see below).
|
|
183
|
+
`gtd: package done` · `gtd: awaiting review` · `gtd: done` · `gtd: health-check`
|
|
184
|
+
· `gtd: health-fix` — plus the hand-made `gtd: transport` (see below).
|
|
178
185
|
|
|
179
186
|
The last commit subject is bucketed two ways:
|
|
180
187
|
|
|
@@ -189,24 +196,28 @@ The last commit subject is bucketed two ways:
|
|
|
189
196
|
1. **ERRORS.md present** → Escalate (human gate; STOP).
|
|
190
197
|
2. **FEEDBACK.md present** → non-empty → Fixing; **empty** (clean agentic review
|
|
191
198
|
= approval) → Close package.
|
|
192
|
-
3.
|
|
199
|
+
3. **HEALTH.md present** → Health Fixing (idle health-fix loop; no `.gtd`,
|
|
200
|
+
REVIEW.md, or FEEDBACK.md).
|
|
201
|
+
4. **.gtd present** → build lifecycle, routed by tree + HEAD:
|
|
193
202
|
- `.gtd` modified (package files added/edited) → Planning
|
|
194
203
|
- code changes present → Testing
|
|
195
204
|
- clean tree + HEAD `gtd: fixing` (no-op fixer) → Testing (re-test)
|
|
196
205
|
- else clean, by HEAD: `gtd: planning` / `gtd: package done` → Building;
|
|
197
206
|
`gtd: building` → Agentic Review (or Close package, if force-approved)
|
|
198
|
-
|
|
207
|
+
5. **REVIEW.md present** → review lifecycle, routed by committed-ness + tree:
|
|
199
208
|
committed + clean → Done; committed + checkbox-only edits (only `[ ]`↔`[x]`
|
|
200
209
|
flips in REVIEW.md) → Done; committed + non-checkbox pending edits → Accept
|
|
201
210
|
Review; uncommitted → Await Review (commits REVIEW.md and auto-advances to
|
|
202
|
-
Done).
|
|
211
|
+
Done). 5a. **HEAD `gtd: done` + `squash` enabled + squash base present + no
|
|
203
212
|
unrelated code dirty** (a lone untracked `SQUASH_MSG.md` is allowed) →
|
|
204
213
|
Squashing; unrelated code dirty → New Feature.
|
|
205
|
-
|
|
214
|
+
6. **Boundary HEAD + pending changes** (and no `.gtd`/REVIEW/FEEDBACK), or HEAD
|
|
206
215
|
`gtd: new task` + clean tree (regenerate a lost seed) → New Feature.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
work)
|
|
216
|
+
7. **TODO.md present** → Grilling / Grilled.
|
|
217
|
+
8. **Boundary or `gtd: package done` HEAD + clean tree** → Clean (review the
|
|
218
|
+
work), **Health check** (run `testCommand` when there is nothing to review —
|
|
219
|
+
on any branch outside a process), or Idle (health check green, nothing to
|
|
220
|
+
do).
|
|
210
221
|
|
|
211
222
|
Anything matching no rule is corruption — `gtd` **hard-errors** rather than
|
|
212
223
|
guess.
|
|
@@ -221,7 +232,10 @@ flowchart TD
|
|
|
221
232
|
P1 -->|no| P2{"FEEDBACK.md?"}
|
|
222
233
|
P2 -->|"empty = approval"| Close["Close package — rm pkg dir, gtd: package done"]:::edge
|
|
223
234
|
P2 -->|"non-empty"| Fixing["Fixing — rm FEEDBACK, fixer agent"]:::agent
|
|
224
|
-
P2 -->|absent|
|
|
235
|
+
P2 -->|absent| P2b{"HEALTH.md?"}
|
|
236
|
+
P2b -->|"present"| HealthFix["Health Fixing — rm HEALTH.md, gtd: health-fix, fixer agent"]:::agent
|
|
237
|
+
HealthFix -.->|"re-resolve"| HealthCheck
|
|
238
|
+
P2b -->|absent| P3{".gtd/?"}
|
|
225
239
|
P3 -->|"modified"| Planning["Planning — gtd: planning"]:::agent
|
|
226
240
|
P3 -->|"code dirty / resume / no-op fixer"| Testing["Testing — gtd: building, run tests"]:::edge
|
|
227
241
|
P3 -->|"clean, HEAD planning/package done"| Building["Building — pick & build one package"]:::agent
|
|
@@ -234,7 +248,7 @@ flowchart TD
|
|
|
234
248
|
P4 -->|absent| P5{"boundary HEAD + dirty,<br/>or gtd: new task + clean?"}
|
|
235
249
|
Done -->|"squash enabled"| Squashing["Squashing — reset --soft base, squash commit"]:::agent
|
|
236
250
|
Done -->|"squash disabled"| Idle
|
|
237
|
-
Squashing --> Idle["Idle — nothing to do (STOP
|
|
251
|
+
Squashing --> Idle["Idle — nothing to do (STOP)"]:::gate
|
|
238
252
|
P5 -->|yes| NewFeature["New Feature — gtd: new task, revert, seed TODO"]:::edge
|
|
239
253
|
P5 -->|no| P6{"TODO.md?"}
|
|
240
254
|
P6 -->|"open markers"| GrillStop["Grilling — gtd: grilling, STOP for answers"]:::gate
|
|
@@ -242,7 +256,13 @@ flowchart TD
|
|
|
242
256
|
P6 -->|"clean, no markers"| Grilled["Grilled — gtd: grilled, decompose"]:::agent
|
|
243
257
|
P6 -->|absent| P7{"clean + boundary/package-done HEAD,<br/>reviewable diff?"}
|
|
244
258
|
P7 -->|yes| CleanState["Clean — write REVIEW.md"]:::agent
|
|
245
|
-
P7 -->|no|
|
|
259
|
+
P7 -->|"no (idle, outside a process)"| HealthCheck["Health check — run testCommand"]:::edge
|
|
260
|
+
HealthCheck -->|"green, no health-fix"| Idle
|
|
261
|
+
HealthCheck -->|"green + ≥1 health-fix, squash enabled"| Squashing
|
|
262
|
+
HealthCheck -->|"red, below cap"| HealthMd["write HEALTH.md, gtd: health-check"]:::edge
|
|
263
|
+
HealthMd -.->|"re-resolve"| HealthFix
|
|
264
|
+
HealthCheck -->|"red, at cap"| ErrorsMd["write ERRORS.md, gtd: health-check"]:::edge
|
|
265
|
+
ErrorsMd -.->|"re-resolve"| Escalate
|
|
246
266
|
classDef edge fill:#1a4a6b,color:#fff
|
|
247
267
|
classDef agent fill:#2d6a4f,color:#fff
|
|
248
268
|
classDef gate fill:#7a3b1d,color:#fff
|
|
@@ -263,6 +283,10 @@ guessing:
|
|
|
263
283
|
- FEEDBACK.md without .gtd
|
|
264
284
|
- ERRORS.md + FEEDBACK.md
|
|
265
285
|
- ERRORS.md without .gtd
|
|
286
|
+
- HEALTH.md + .gtd (health check only runs from a bare idle tree)
|
|
287
|
+
- HEALTH.md + REVIEW.md (same)
|
|
288
|
+
- HEALTH.md + FEEDBACK.md (same)
|
|
289
|
+
- HEALTH.md + ERRORS.md (escalation wins; HEALTH.md must not coexist)
|
|
266
290
|
|
|
267
291
|
Legal coexistence: `.gtd` + TODO.md (plan kept alongside packages during
|
|
268
292
|
**Planning** only — TODO.md is deleted at the first Building turn);
|
|
@@ -290,7 +314,7 @@ back into the working tree uncommitted, and re-derives state from scratch. If
|
|
|
290
314
|
the transport commit is the repository's root commit (no parent), `gtd` fails
|
|
291
315
|
immediately with a clear error instead of looping.
|
|
292
316
|
|
|
293
|
-
## The
|
|
317
|
+
## The 19 states
|
|
294
318
|
|
|
295
319
|
Each state has a **condition** (when it wins), a deterministic **action**, the
|
|
296
320
|
**commit(s)** it produces, and where it **advances**. States marked
|
|
@@ -298,34 +322,36 @@ Each state has a **condition** (when it wins), a deterministic **action**, the
|
|
|
298
322
|
human; **edge-only** states render no prompt at all — the driver performs their
|
|
299
323
|
action and re-resolves silently.
|
|
300
324
|
|
|
301
|
-
| State | Kind | Wins when
|
|
302
|
-
| ------------------ | -------------------------------- |
|
|
303
|
-
| **Transport** | edge-only, auto | HEAD `gtd: transport` (hand-made handoff commit)
|
|
304
|
-
| **Escalate** | STOP | ERRORS.md present
|
|
305
|
-
| **Fixing** | agent, auto | non-empty FEEDBACK.md present
|
|
306
|
-
| **
|
|
307
|
-
| **
|
|
308
|
-
| **
|
|
309
|
-
| **
|
|
310
|
-
| **
|
|
311
|
-
| **
|
|
312
|
-
| **
|
|
313
|
-
| **
|
|
314
|
-
| **
|
|
315
|
-
| **
|
|
316
|
-
| **
|
|
317
|
-
| **
|
|
318
|
-
| **
|
|
319
|
-
| **
|
|
325
|
+
| State | Kind | Wins when | Action & commit | Advances to |
|
|
326
|
+
| ------------------ | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
327
|
+
| **Transport** | edge-only, auto | HEAD `gtd: transport` (hand-made handoff commit) | mixed-reset HEAD (`git reset HEAD~1`), keep work in tree; **no commit** | re-derive from the restored tree |
|
|
328
|
+
| **Escalate** | STOP | ERRORS.md present | none | held until the human deletes ERRORS.md |
|
|
329
|
+
| **Fixing** | agent, auto | non-empty FEEDBACK.md present | inline FEEDBACK into the prompt, remove FEEDBACK.md; commit its removal `gtd: fixing` (FEEDBACK was committed by Testing) or `gtd: feedback` (uncommitted, written by Agentic Review) | fixer edits → Testing |
|
|
330
|
+
| **Health Fixing** | agent, auto | HEALTH.md present (no `.gtd`, REVIEW.md, or FEEDBACK.md) | read HEALTH.md into the prompt, commit its removal `gtd: health-fix` | fixer edits → Health check |
|
|
331
|
+
| **Close package** | edge-only, auto | empty FEEDBACK.md present (clean review); also reached from Agentic Review force-approve | rm FEEDBACK.md, rm the first (finished) package dir (+ the now-empty `.gtd/`); commit `gtd: package done` | more packages → Building; `.gtd` gone → Clean |
|
|
332
|
+
| **Planning** | agent, auto | `.gtd` present **and modified**; HEAD `gtd: grilled` or `gtd: planning` | commit the `.gtd/` changes `gtd: planning` | continue decomposing, else → Building |
|
|
333
|
+
| **Testing** | edge-only, auto | `.gtd` present, no FEEDBACK/ERRORS, and a reason to test: code changes, a pending ERRORS.md deletion (human resume), or a clean tree under HEAD `gtd: fixing` (no-op fixer) | commit pending tree `gtd: building`, run `testCommand`; green → proceed; red → write FEEDBACK (below cap) or ERRORS (at cap), commit `gtd: errors`; if captured output is empty/whitespace, a sentinel string is written so the file is never empty (empty FEEDBACK remains reserved for agentic-review approval) | green → Agentic Review; FEEDBACK → Fixing; ERRORS → Escalate |
|
|
334
|
+
| **Building** | agent, auto | `.gtd` present and clean, clean tree; HEAD `gtd: planning` or `gtd: package done` | if HEAD `gtd: planning` and TODO.md present, delete TODO.md and commit (prefix unchanged, fires once); select the first package, inline its tasks; agent leaves work **uncommitted** | Testing |
|
|
335
|
+
| **Agentic Review** | agent, auto | `.gtd` present and clean, clean tree; HEAD `gtd: building` | reviewer writes FEEDBACK.md (empty = approval), uncommitted — **unless** force-approved (kill-switch off or review-fix threshold hit), which routes straight to Close package | empty FEEDBACK → Close package; non-empty → Fixing |
|
|
336
|
+
| **Done** | edge-only, auto | REVIEW.md committed + clean tree, **or** committed + checkbox-only edits (only `- [ ]`→`- [x]` flips in REVIEW.md = approval) | rm REVIEW.md, commit `gtd: done` | Squashing (if enabled) or Idle |
|
|
337
|
+
| **Squashing** | agent, auto | no steering files, HEAD `gtd: done` or green Health check with ≥1 `gtd: health-fix`, `squash` enabled, squash base present, no unrelated code dirty (a lone untracked `SQUASH_MSG.md` is allowed) | agent authors a conventional-commits message from the full `<base>..HEAD` diff, then runs `git reset --soft <base>` + `git commit` — collapses all intermediate `gtd: *` commits (including any interleaved non-gtd commits) into one; **gtd then STOPs** — post-squash review fires only on the next manual `gtd` run | Idle (STOP) |
|
|
338
|
+
| **Accept Review** | edge-only, auto | REVIEW.md committed + pending **non-checkbox** edits (human annotated REVIEW.md with comments / edited code) | seed TODO.md from the changeset, `git checkout` to discard the code edits, rm REVIEW.md; **all uncommitted** | Grilling |
|
|
339
|
+
| **Await Review** | edge-only, auto | REVIEW.md present and **uncommitted** (freshly written by Clean) | commit REVIEW.md `gtd: awaiting review` | Done (auto, same run) |
|
|
340
|
+
| **New Feature** | edge-only, auto | boundary HEAD + pending changes (code and/or a new uncommitted TODO.md), **or** HEAD `gtd: new task` + clean tree (lost-seed regen) | commit the raw input verbatim `gtd: new task` (unless already there), `git revert --no-commit` it back to a clean baseline, seed TODO.md from that diff — revert + seed left **uncommitted** | Grilling |
|
|
341
|
+
| **Grilling** | agent (iterate) / STOP (answers) | TODO.md present, not New Feature | commit pending edits `gtd: grilling`. Open-question markers present → STOP for the human to answer inline; no markers but dirty → grilling agent iterates | converge (no markers, clean tree) → Grilled |
|
|
342
|
+
| **Grilled** | agent, auto | TODO.md present, no markers, clean tree | commit pending `gtd: grilled` | decompose into `.gtd/` → Planning |
|
|
343
|
+
| **Clean** | agent | no steering files, clean tree, boundary or `gtd: package done` HEAD, and the review base yields a **non-empty** diff | compute the review base (three rules — see below); agent writes REVIEW.md **uncommitted** with `# Review: <short-hash>` heading, `<!-- base: <full-hash> -->` marker, and per-hunk `- [ ]` checkboxes (ticking them signals approval → Done) | Await Review |
|
|
344
|
+
| **Health check** | edge-only, auto | no steering files, clean tree, outside a process with no reviewable diff (any branch) — the `!reviewable` case from rule 8 | run `testCommand`: green + no prior `gtd: health-fix` → Idle (no commit); red below `fixAttemptCap` → write HEALTH.md, commit `gtd: health-check` → Health Fixing; red at cap → write ERRORS.md, commit `gtd: health-check` → Escalate; green + ≥1 `gtd: health-fix` → Squashing (if `squash`) or Idle | green → Idle or Squashing; red below cap → Health Fixing; red at cap → Escalate |
|
|
345
|
+
| **Idle** | STOP | no steering files, clean tree, health check passed with no prior `gtd: health-fix` commits, and nothing to review | none (no commit — the health check edge terminates the driver loop directly) | — |
|
|
320
346
|
|
|
321
347
|
Every prompt also embeds the current `git diff HEAD` (untracked files included)
|
|
322
348
|
inline, plus the last commit subject and working-tree status, so the agent has
|
|
323
349
|
full context.
|
|
324
350
|
|
|
325
|
-
### Review base —
|
|
351
|
+
### Review base — three rules
|
|
326
352
|
|
|
327
353
|
The review base (the commit whose diff to HEAD forms the REVIEW.md) is chosen by
|
|
328
|
-
|
|
354
|
+
three rules evaluated in priority order:
|
|
329
355
|
|
|
330
356
|
1. **Within a process, first review** — a `gtd: grilling` commit exists after
|
|
331
357
|
the last `gtd: done` (or task start), but no `gtd: awaiting review` yet →
|
|
@@ -334,18 +360,16 @@ four rules evaluated in priority order:
|
|
|
334
360
|
2. **Within a process, incremental** — `gtd: awaiting review` also present in
|
|
335
361
|
the current cycle (takes precedence over rule 1) → base = last
|
|
336
362
|
`gtd: awaiting review`; `refDiff` spans only the post-review changes.
|
|
337
|
-
3. **Outside a process
|
|
338
|
-
`gtd: done
|
|
339
|
-
|
|
340
|
-
4. **Outside a process, default branch** — no process active and HEAD is on the
|
|
341
|
-
default branch → skip review; `reviewBase`/`refDiff` unset → Idle.
|
|
363
|
+
3. **Outside a process (any branch)** — no `gtd: grilling` after the last
|
|
364
|
+
`gtd: done` → skip review; `reviewBase`/`refDiff` unset → Idle (the health
|
|
365
|
+
check runs instead).
|
|
342
366
|
|
|
343
367
|
In all cases, if the diff from the chosen base to HEAD is empty,
|
|
344
368
|
`reviewBase`/`refDiff` are left unset and the machine settles in Idle.
|
|
345
369
|
|
|
346
370
|
## The fix loops & counter folds
|
|
347
371
|
|
|
348
|
-
|
|
372
|
+
Three derived counters drive the budgeted loops. All are **folded in the
|
|
349
373
|
machine** from flags on the `COMMIT[]` stream — never recomputed at the edge:
|
|
350
374
|
|
|
351
375
|
- **`testFixCount`** — `gtd: errors` commits (test-fix attempts) since the
|
|
@@ -355,6 +379,9 @@ machine** from flags on the `COMMIT[]` stream — never recomputed at the edge:
|
|
|
355
379
|
round, and every human resume starts a **fresh budget**.
|
|
356
380
|
- **`reviewFixCount`** — `gtd: feedback` commits (review-fix rounds) since the
|
|
357
381
|
most recent package start.
|
|
382
|
+
- **`healthFixCount`** — `gtd: health-check` commits since the most recent
|
|
383
|
+
commit that removed HEALTH.md (or the start of branch history if none). Reuses
|
|
384
|
+
`fixAttemptCap` — no separate config key.
|
|
358
385
|
|
|
359
386
|
### Test-fix loop (`fixAttemptCap`, default 3)
|
|
360
387
|
|
|
@@ -378,6 +405,31 @@ ERRORS.md — which **resets the fix-attempt budget** (the next run re-tests and
|
|
|
378
405
|
grants a fresh `cap` attempts before escalating again). While ERRORS.md exists,
|
|
379
406
|
every run resolves straight back to Escalate.
|
|
380
407
|
|
|
408
|
+
### Health-fix loop (`fixAttemptCap`, `squash` — no new config)
|
|
409
|
+
|
|
410
|
+
Outside a process (any branch), when there is no reviewable diff and no steering
|
|
411
|
+
files, `gtd` runs `testCommand` instead of stopping. This reuses `fixAttemptCap`
|
|
412
|
+
(default 3) and `squash` — no new config keys are introduced.
|
|
413
|
+
|
|
414
|
+
```
|
|
415
|
+
Idle path → Health check(red) → Health Fixing → Health check(red) → … → Health check(green)
|
|
416
|
+
│ │
|
|
417
|
+
└── below cap: HEALTH.md, gtd: health-check ─────────────────┘
|
|
418
|
+
└── at/over the cap: ERRORS.md, gtd: health-check → Escalate
|
|
419
|
+
green + ≥1 health-fix → Squashing (if squash enabled) → Idle
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
- **green, no prior `gtd: health-fix`** → Idle immediately (no commit).
|
|
423
|
+
- **red, below `fixAttemptCap`** → write test output to HEALTH.md, commit
|
|
424
|
+
`gtd: health-check` → **Health Fixing** agent fixes, commits removal as
|
|
425
|
+
`gtd: health-fix`, loops back to Health check.
|
|
426
|
+
- **red, at or over `fixAttemptCap`** → write test output to ERRORS.md, commit
|
|
427
|
+
`gtd: health-check` → **Escalate** (human gate). Delete ERRORS.md to reset the
|
|
428
|
+
budget and resume.
|
|
429
|
+
- **green, ≥1 `gtd: health-fix` present** → health-fix cycle converged: if
|
|
430
|
+
`squash` is enabled → **Squashing** (squash base = parent of the first
|
|
431
|
+
`gtd: health-check` of the current run); otherwise → Idle (no commit).
|
|
432
|
+
|
|
381
433
|
### Review-fix loop & agentic review (`reviewThreshold`, default 3)
|
|
382
434
|
|
|
383
435
|
After a green test run, **Agentic Review** reviews the package's accumulated
|
|
@@ -484,11 +536,13 @@ built-in defaults apply. Supported filenames (searched in this order):
|
|
|
484
536
|
### Schema
|
|
485
537
|
|
|
486
538
|
- **`testCommand`** (string, default `npm run test`) — the command the edge runs
|
|
487
|
-
in the Testing state to verify a built package
|
|
539
|
+
in the Testing state to verify a built package, and in the Health check state
|
|
540
|
+
on the default-branch idle path.
|
|
488
541
|
- **`fixAttemptCap`** (non-negative integer, default `3`) — the test-fix budget:
|
|
489
542
|
how many `gtd: errors` attempts are allowed per sub-loop before the failure is
|
|
490
543
|
escalated to ERRORS.md (Escalate). `0` disables the cap (escalates immediately
|
|
491
|
-
on the first red run).
|
|
544
|
+
on the first red run). Also reused as the health-fix budget (no separate
|
|
545
|
+
config key).
|
|
492
546
|
- **`reviewThreshold`** (integer ≥ 1, default `3`) — the review-fix budget: how
|
|
493
547
|
many `gtd: feedback` rounds are allowed per package before Agentic Review
|
|
494
548
|
force-approves.
|
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -368497,7 +368497,8 @@ var EDGE_ONLY_STATES = /* @__PURE__ */ new Set([
|
|
|
368497
368497
|
"accept-review",
|
|
368498
368498
|
"close-package",
|
|
368499
368499
|
"done",
|
|
368500
|
-
"await-review"
|
|
368500
|
+
"await-review",
|
|
368501
|
+
"health-check"
|
|
368501
368502
|
]);
|
|
368502
368503
|
var MODEL_STATE = {
|
|
368503
368504
|
grilling: "grilling",
|
|
@@ -368505,6 +368506,7 @@ var MODEL_STATE = {
|
|
|
368505
368506
|
planning: "decompose",
|
|
368506
368507
|
building: "building",
|
|
368507
368508
|
fixing: "fixing",
|
|
368509
|
+
"health-fixing": "fixing",
|
|
368508
368510
|
"agentic-review": "agentic-review",
|
|
368509
368511
|
clean: "clean",
|
|
368510
368512
|
squashing: "clean"
|
|
@@ -368545,7 +368547,8 @@ var STATE_TEMPLATE = {
|
|
|
368545
368547
|
clean: "@clean",
|
|
368546
368548
|
squashing: "@squashing",
|
|
368547
368549
|
escalate: "@escalate",
|
|
368548
|
-
idle: "@idle"
|
|
368550
|
+
idle: "@idle",
|
|
368551
|
+
"health-fixing": "@fixing"
|
|
368549
368552
|
};
|
|
368550
368553
|
var buildPrompt = (result, resolveModel = builtinResolveModel, output = "plain") => {
|
|
368551
368554
|
const { state, context: context10 } = result;
|
|
@@ -388261,8 +388264,11 @@ var GTD_DIR = ".gtd";
|
|
|
388261
388264
|
var REVIEW_FILE = "REVIEW.md";
|
|
388262
388265
|
var FEEDBACK_FILE = "FEEDBACK.md";
|
|
388263
388266
|
var ERRORS_FILE = "ERRORS.md";
|
|
388267
|
+
var HEALTH_FILE = "HEALTH.md";
|
|
388264
388268
|
var EMPTY_FAILURE_SENTINEL = "Test command failed with no output (exit code non-zero).";
|
|
388265
388269
|
var UNANSWERED_MARKER = "<!-- user answers here -->";
|
|
388270
|
+
var HEALTH_CHECK_SUBJECT = "gtd: health-check";
|
|
388271
|
+
var HEALTH_FIX_SUBJECT = "gtd: health-fix";
|
|
388266
388272
|
var NEW_TASK_SUBJECT = "gtd: new task";
|
|
388267
388273
|
var GRILLING_SUBJECT = "gtd: grilling";
|
|
388268
388274
|
var PLANNING_SUBJECT = "gtd: planning";
|
|
@@ -388279,6 +388285,7 @@ var STEERING_FILES = [
|
|
|
388279
388285
|
REVIEW_FILE,
|
|
388280
388286
|
FEEDBACK_FILE,
|
|
388281
388287
|
ERRORS_FILE,
|
|
388288
|
+
HEALTH_FILE,
|
|
388282
388289
|
SQUASH_MSG_FILE
|
|
388283
388290
|
];
|
|
388284
388291
|
var WORKFLOW_FILE_EXCLUDES = [...STEERING_FILES, GTD_DIR];
|
|
@@ -388468,7 +388475,9 @@ var gatherEvents = () => (
|
|
|
388468
388475
|
isFeedback: subject === FEEDBACK_SUBJECT,
|
|
388469
388476
|
isPackageStart: subject === PLANNING_SUBJECT || subject === PACKAGE_DONE_SUBJECT,
|
|
388470
388477
|
isWorkflowCommit: subject.startsWith("gtd: "),
|
|
388471
|
-
removedErrors
|
|
388478
|
+
removedErrors,
|
|
388479
|
+
isHealthCheck: subject === HEALTH_CHECK_SUBJECT,
|
|
388480
|
+
isHealthFix: subject === HEALTH_FIX_SUBJECT
|
|
388472
388481
|
};
|
|
388473
388482
|
}
|
|
388474
388483
|
);
|
|
@@ -388542,10 +388551,6 @@ var gatherEvents = () => (
|
|
|
388542
388551
|
candidate = firstGrilling.hash ?? EMPTY_TREE;
|
|
388543
388552
|
}
|
|
388544
388553
|
} else {
|
|
388545
|
-
const mergeBaseCandidate = Option_exports.isSome(base) && base.value !== headHash ? base.value : void 0;
|
|
388546
|
-
if (mergeBaseCandidate !== void 0) {
|
|
388547
|
-
candidate = mergeBaseCandidate;
|
|
388548
|
-
}
|
|
388549
388554
|
}
|
|
388550
388555
|
if (candidate !== void 0 && hasCommitsAfterLastDone) {
|
|
388551
388556
|
const candidateDiff = yield* git.diffRef(candidate, WORKFLOW_FILE_EXCLUDES).pipe(Effect_exports.catchAll(() => Effect_exports.succeed("")));
|
|
@@ -388601,6 +388606,31 @@ var gatherEvents = () => (
|
|
|
388601
388606
|
}
|
|
388602
388607
|
const squashMsgPresent = yield* fs9.exists(resolve5(SQUASH_MSG_FILE));
|
|
388603
388608
|
const squashMsgContent = squashMsgPresent ? yield* fs9.readFileString(resolve5(SQUASH_MSG_FILE)) : "";
|
|
388609
|
+
const healthPresent = yield* fs9.exists(resolve5(HEALTH_FILE));
|
|
388610
|
+
const healthContent = healthPresent ? yield* fs9.readFileString(resolve5(HEALTH_FILE)) : "";
|
|
388611
|
+
const healthCommitted = healthPresent && !isUncommitted(HEALTH_FILE);
|
|
388612
|
+
let healthFixBase;
|
|
388613
|
+
if (config2.squash) {
|
|
388614
|
+
const subjectOf = (c7) => (c7.message.split("\n")[0] ?? "").trim();
|
|
388615
|
+
let firstHealthCheckHash;
|
|
388616
|
+
let healthCheckCount = 0;
|
|
388617
|
+
for (const commit3 of history) {
|
|
388618
|
+
const s5 = subjectOf(commit3);
|
|
388619
|
+
const isPackageStart = s5 === PLANNING_SUBJECT || s5 === PACKAGE_DONE_SUBJECT;
|
|
388620
|
+
if (isPackageStart || commit3.removedErrors) {
|
|
388621
|
+
firstHealthCheckHash = void 0;
|
|
388622
|
+
healthCheckCount = 0;
|
|
388623
|
+
}
|
|
388624
|
+
if (s5 === HEALTH_CHECK_SUBJECT) {
|
|
388625
|
+
healthCheckCount++;
|
|
388626
|
+
if (healthCheckCount === 1) firstHealthCheckHash = commit3.hash;
|
|
388627
|
+
}
|
|
388628
|
+
}
|
|
388629
|
+
if (healthCheckCount > 0 && firstHealthCheckHash !== void 0) {
|
|
388630
|
+
const base2 = yield* git.resolveRef(`${firstHealthCheckHash}~1`).pipe(Effect_exports.catchAll(() => Effect_exports.succeed(EMPTY_TREE)));
|
|
388631
|
+
healthFixBase = base2;
|
|
388632
|
+
}
|
|
388633
|
+
}
|
|
388604
388634
|
const payload = {
|
|
388605
388635
|
todoExists,
|
|
388606
388636
|
todoCommitted,
|
|
@@ -388632,7 +388662,11 @@ var gatherEvents = () => (
|
|
|
388632
388662
|
...squashBase !== void 0 ? { squashBase } : {},
|
|
388633
388663
|
...squashDiff !== void 0 ? { squashDiff } : {},
|
|
388634
388664
|
squashMsgPresent,
|
|
388635
|
-
squashMsgContent
|
|
388665
|
+
squashMsgContent,
|
|
388666
|
+
healthPresent,
|
|
388667
|
+
healthContent,
|
|
388668
|
+
healthCommitted,
|
|
388669
|
+
...healthFixBase !== void 0 ? { healthFixBase } : {}
|
|
388636
388670
|
};
|
|
388637
388671
|
const resolveEvent = { type: "RESOLVE", payload };
|
|
388638
388672
|
return [...commitEvents, resolveEvent];
|
|
@@ -388662,7 +388696,7 @@ var perform = (action) => (
|
|
|
388662
388696
|
// work in the tree, then re-derive. (No producer command — consume only.)
|
|
388663
388697
|
case "transportReset": {
|
|
388664
388698
|
yield* git.mixedResetHead();
|
|
388665
|
-
return;
|
|
388699
|
+
return { stop: false };
|
|
388666
388700
|
}
|
|
388667
388701
|
// New Feature: capture the raw input as `gtd: new task` (unless HEAD is
|
|
388668
388702
|
// already there — the lost-seed regenerate case), revert it back to a
|
|
@@ -388670,7 +388704,7 @@ var perform = (action) => (
|
|
|
388670
388704
|
case "seedNewFeature": {
|
|
388671
388705
|
const captured = yield* captureAndRevert(git, NEW_TASK_SUBJECT, Effect_exports.void);
|
|
388672
388706
|
yield* fs9.writeFileString(resolve5(TODO_FILE), seedTodo(captured));
|
|
388673
|
-
return;
|
|
388707
|
+
return { stop: false };
|
|
388674
388708
|
}
|
|
388675
388709
|
// Accept Review: capture the human's pending changeset durably as
|
|
388676
388710
|
// `gtd: review feedback` (annotations, code edits, and new files alike),
|
|
@@ -388682,7 +388716,7 @@ var perform = (action) => (
|
|
|
388682
388716
|
const captured = yield* captureAndRevert(git, REVIEW_FEEDBACK_SUBJECT, git.resetHard());
|
|
388683
388717
|
yield* fs9.remove(resolve5(REVIEW_FILE)).pipe(Effect_exports.catchAll(() => Effect_exports.void));
|
|
388684
388718
|
yield* fs9.writeFileString(resolve5(TODO_FILE), seedTodo(captured));
|
|
388685
|
-
return;
|
|
388719
|
+
return { stop: false };
|
|
388686
388720
|
}
|
|
388687
388721
|
// Grilling capture: the plan is already committed and the user sketched
|
|
388688
388722
|
// code during the round. Fold the code diff (untracked included, steering
|
|
@@ -388705,7 +388739,7 @@ var perform = (action) => (
|
|
|
388705
388739
|
}
|
|
388706
388740
|
yield* fs9.writeFileString(resolve5(TODO_FILE), appendCapturedInput(todoNow, captured));
|
|
388707
388741
|
yield* git.commitAllWithPrefix(GRILLING_SUBJECT);
|
|
388708
|
-
return;
|
|
388742
|
+
return { stop: false };
|
|
388709
388743
|
}
|
|
388710
388744
|
// Testing: commit the pending tree `gtd: building` (nothing pending in the
|
|
388711
388745
|
// no-op-fixer case), run tests; on red write FEEDBACK (below cap) or ERRORS
|
|
@@ -388722,13 +388756,46 @@ var perform = (action) => (
|
|
|
388722
388756
|
if (!committedBuilding) {
|
|
388723
388757
|
yield* git.commitAllWithPrefix(BUILDING_SUBJECT);
|
|
388724
388758
|
}
|
|
388725
|
-
return;
|
|
388759
|
+
return { stop: false };
|
|
388726
388760
|
}
|
|
388727
388761
|
const target = action.capReached ? ERRORS_FILE : FEEDBACK_FILE;
|
|
388728
388762
|
const body = /\S/.test(result.output) ? result.output : EMPTY_FAILURE_SENTINEL;
|
|
388729
388763
|
yield* fs9.writeFileString(resolve5(target), body);
|
|
388730
388764
|
yield* git.commitAllWithPrefix(ERRORS_SUBJECT);
|
|
388731
|
-
return;
|
|
388765
|
+
return { stop: false };
|
|
388766
|
+
}
|
|
388767
|
+
// Health check: run tests on an idle/clean tree.
|
|
388768
|
+
// Green, no prior fixes → stop immediately (no commit).
|
|
388769
|
+
// Green, fixes exist → stop immediately (squash handled via healthFixBase
|
|
388770
|
+
// when gatherEvents sets it; the machine resolves squashing state).
|
|
388771
|
+
// Red below cap → write HEALTH.md (uncommitted); resolveHealth will
|
|
388772
|
+
// commit it as `gtd: health-check` via `commitPending removeHealth`.
|
|
388773
|
+
// Red at cap → write ERRORS.md and commit `gtd: health-check` immediately
|
|
388774
|
+
// so the Escalate state fires on the next resolve.
|
|
388775
|
+
case "runHealthCheck": {
|
|
388776
|
+
if (action.commitErrorsReset === true) {
|
|
388777
|
+
yield* git.commitAllWithPrefix(HEALTH_FIX_SUBJECT);
|
|
388778
|
+
}
|
|
388779
|
+
const runner = yield* TestRunner;
|
|
388780
|
+
const result = yield* runner.run();
|
|
388781
|
+
if (result.exitCode === 0) {
|
|
388782
|
+
if (action.healthFixBase !== void 0 && action.commitErrorsReset !== true) {
|
|
388783
|
+
yield* fs9.writeFileString(
|
|
388784
|
+
resolve5(SQUASH_MSG_FILE),
|
|
388785
|
+
"chore: health-check cycle squash\n"
|
|
388786
|
+
);
|
|
388787
|
+
return { stop: false };
|
|
388788
|
+
}
|
|
388789
|
+
return { stop: true };
|
|
388790
|
+
}
|
|
388791
|
+
const body = /\S/.test(result.output) ? result.output : EMPTY_FAILURE_SENTINEL;
|
|
388792
|
+
if (action.capReached) {
|
|
388793
|
+
yield* fs9.writeFileString(resolve5(ERRORS_FILE), body);
|
|
388794
|
+
yield* git.commitAllWithPrefix(HEALTH_CHECK_SUBJECT);
|
|
388795
|
+
} else {
|
|
388796
|
+
yield* fs9.writeFileString(resolve5(HEALTH_FILE), body);
|
|
388797
|
+
}
|
|
388798
|
+
return { stop: false };
|
|
388732
388799
|
}
|
|
388733
388800
|
// Grilling / Grilled / Planning / Fixing: commit the pending tree under a
|
|
388734
388801
|
// fixed phase prefix. Fixing sets `removeFeedback` so FEEDBACK.md's removal
|
|
@@ -388742,11 +388809,14 @@ var perform = (action) => (
|
|
|
388742
388809
|
if (action.removeTodo === true) {
|
|
388743
388810
|
yield* fs9.remove(resolve5(TODO_FILE)).pipe(Effect_exports.catchAll(() => Effect_exports.void));
|
|
388744
388811
|
}
|
|
388812
|
+
if (action.removeHealth === true) {
|
|
388813
|
+
yield* fs9.remove(resolve5(HEALTH_FILE)).pipe(Effect_exports.catchAll(() => Effect_exports.void));
|
|
388814
|
+
}
|
|
388745
388815
|
if (action.removeTodo !== true) {
|
|
388746
388816
|
yield* formatFile(resolve5(TODO_FILE)).pipe(Effect_exports.catchAll(() => Effect_exports.void));
|
|
388747
388817
|
}
|
|
388748
388818
|
yield* git.commitAllWithPrefix(action.prefix);
|
|
388749
|
-
return;
|
|
388819
|
+
return { stop: false };
|
|
388750
388820
|
}
|
|
388751
388821
|
// Close package: remove the (maybe-empty / maybe-absent) FEEDBACK.md, rm
|
|
388752
388822
|
// the first (finished) package dir (+ the now-empty `.gtd/`), commit
|
|
@@ -388759,19 +388829,19 @@ var perform = (action) => (
|
|
|
388759
388829
|
yield* git.removePackageDir(`${GTD_DIR}/${first2.name}`);
|
|
388760
388830
|
}
|
|
388761
388831
|
yield* git.commitAllWithPrefix(PACKAGE_DONE_SUBJECT);
|
|
388762
|
-
return;
|
|
388832
|
+
return { stop: false };
|
|
388763
388833
|
}
|
|
388764
388834
|
// Await Review: commit REVIEW.md as `gtd: awaiting review`.
|
|
388765
388835
|
case "commitReview": {
|
|
388766
388836
|
yield* formatFile(resolve5(REVIEW_FILE)).pipe(Effect_exports.catchAll(() => Effect_exports.void));
|
|
388767
388837
|
yield* git.commitAllWithPrefix(AWAITING_REVIEW_SUBJECT);
|
|
388768
|
-
return;
|
|
388838
|
+
return { stop: false };
|
|
388769
388839
|
}
|
|
388770
388840
|
// Done: rm REVIEW.md, commit `gtd: done`.
|
|
388771
388841
|
case "done": {
|
|
388772
388842
|
yield* fs9.remove(resolve5(REVIEW_FILE)).pipe(Effect_exports.catchAll(() => Effect_exports.void));
|
|
388773
388843
|
yield* git.commitAllWithPrefix(DONE_SUBJECT);
|
|
388774
|
-
return;
|
|
388844
|
+
return { stop: false };
|
|
388775
388845
|
}
|
|
388776
388846
|
// Squash: rm SQUASH_MSG.md, soft-reset to squashBase, commit everything
|
|
388777
388847
|
// under the provided commit message.
|
|
@@ -388779,7 +388849,7 @@ var perform = (action) => (
|
|
|
388779
388849
|
yield* fs9.remove(resolve5(SQUASH_MSG_FILE)).pipe(Effect_exports.catchAll(() => Effect_exports.void));
|
|
388780
388850
|
yield* git.softResetTo(action.squashBase);
|
|
388781
388851
|
yield* git.commitAllWithPrefix(action.commitMessage);
|
|
388782
|
-
return;
|
|
388852
|
+
return { stop: false };
|
|
388783
388853
|
}
|
|
388784
388854
|
}
|
|
388785
388855
|
}).pipe(Effect_exports.mapError((e7) => e7 instanceof Error ? e7 : new Error(String(e7))))
|
|
@@ -388797,14 +388867,17 @@ var GtdStateError = class extends Error {
|
|
|
388797
388867
|
var foldCounters = (events) => {
|
|
388798
388868
|
let testFixCount = 0;
|
|
388799
388869
|
let reviewFixCount = 0;
|
|
388870
|
+
let healthFixCount = 0;
|
|
388800
388871
|
for (const event of events) {
|
|
388801
388872
|
if (event.type !== "COMMIT") continue;
|
|
388802
388873
|
if (event.isPackageStart || event.isFeedback || event.removedErrors) testFixCount = 0;
|
|
388803
388874
|
if (event.isErrors) testFixCount += 1;
|
|
388804
388875
|
if (event.isPackageStart) reviewFixCount = 0;
|
|
388805
388876
|
if (event.isFeedback) reviewFixCount += 1;
|
|
388877
|
+
if (event.isPackageStart || event.removedErrors) healthFixCount = 0;
|
|
388878
|
+
if (event.isHealthCheck) healthFixCount += 1;
|
|
388806
388879
|
}
|
|
388807
|
-
return { testFixCount, reviewFixCount };
|
|
388880
|
+
return { testFixCount, reviewFixCount, healthFixCount };
|
|
388808
388881
|
};
|
|
388809
388882
|
var isBoundary = (subject) => !subject.startsWith("gtd: ") || subject === "gtd: done";
|
|
388810
388883
|
var DEFAULT_PAYLOAD = {
|
|
@@ -388834,7 +388907,10 @@ var DEFAULT_PAYLOAD = {
|
|
|
388834
388907
|
reviewThreshold: 3,
|
|
388835
388908
|
squashEnabled: false,
|
|
388836
388909
|
squashMsgPresent: false,
|
|
388837
|
-
squashMsgContent: ""
|
|
388910
|
+
squashMsgContent: "",
|
|
388911
|
+
healthPresent: false,
|
|
388912
|
+
healthContent: "",
|
|
388913
|
+
healthCommitted: false
|
|
388838
388914
|
};
|
|
388839
388915
|
var buildContext = (p4, counters, grillingCase) => ({
|
|
388840
388916
|
testFixCount: counters.testFixCount,
|
|
@@ -388847,7 +388923,7 @@ var buildContext = (p4, counters, grillingCase) => ({
|
|
|
388847
388923
|
...p4.squashDiff !== void 0 ? { squashDiff: p4.squashDiff } : {},
|
|
388848
388924
|
lastCommitSubject: p4.lastCommitSubject,
|
|
388849
388925
|
workingTreeClean: p4.workingTreeClean,
|
|
388850
|
-
feedbackContent: p4.feedbackContent,
|
|
388926
|
+
feedbackContent: p4.feedbackContent !== "" ? p4.feedbackContent : p4.healthContent,
|
|
388851
388927
|
...grillingCase !== void 0 ? { grillingCase } : {}
|
|
388852
388928
|
});
|
|
388853
388929
|
var assertLegal = (p4) => {
|
|
@@ -388861,7 +388937,32 @@ var assertLegal = (p4) => {
|
|
|
388861
388937
|
if (p4.feedbackPresent && p4.reviewPresent) fail20("illegal combination: FEEDBACK.md + REVIEW.md");
|
|
388862
388938
|
if (p4.feedbackPresent && !p4.gtdDirExists) fail20("illegal combination: FEEDBACK.md without .gtd");
|
|
388863
388939
|
if (p4.errorsPresent && p4.feedbackPresent) fail20("illegal combination: ERRORS.md + FEEDBACK.md");
|
|
388864
|
-
|
|
388940
|
+
const isHealthCapEscalation = p4.lastCommitSubject === "gtd: health-check" || p4.lastCommitSubject === "gtd: health-fix";
|
|
388941
|
+
if (p4.errorsPresent && !p4.gtdDirExists && !isHealthCapEscalation)
|
|
388942
|
+
fail20("illegal combination: ERRORS.md without .gtd");
|
|
388943
|
+
if (p4.healthPresent && p4.gtdDirExists) fail20("illegal combination: HEALTH.md + .gtd");
|
|
388944
|
+
if (p4.healthPresent && p4.reviewPresent) fail20("illegal combination: HEALTH.md + REVIEW.md");
|
|
388945
|
+
if (p4.healthPresent && p4.feedbackPresent) fail20("illegal combination: HEALTH.md + FEEDBACK.md");
|
|
388946
|
+
if (p4.healthPresent && p4.errorsPresent) fail20("illegal combination: HEALTH.md + ERRORS.md");
|
|
388947
|
+
};
|
|
388948
|
+
var resolveHealth = (p4, counters) => {
|
|
388949
|
+
const hasPendingWork = !p4.healthCommitted || !p4.workingTreeClean;
|
|
388950
|
+
const prefix = p4.healthCommitted ? "gtd: health-fix" : "gtd: health-check";
|
|
388951
|
+
return {
|
|
388952
|
+
state: "health-fixing",
|
|
388953
|
+
autoAdvance: true,
|
|
388954
|
+
...hasPendingWork ? {
|
|
388955
|
+
edgeAction: {
|
|
388956
|
+
kind: "commitPending",
|
|
388957
|
+
prefix,
|
|
388958
|
+
// Always remove HEALTH.md: on gtd: health-check this clears it so
|
|
388959
|
+
// the next run re-enters resolveCleanOrIdle; on gtd: health-fix it
|
|
388960
|
+
// clears the marker left by a prior health-check run.
|
|
388961
|
+
removeHealth: true
|
|
388962
|
+
}
|
|
388963
|
+
} : {},
|
|
388964
|
+
context: buildContext(p4, counters)
|
|
388965
|
+
};
|
|
388865
388966
|
};
|
|
388866
388967
|
var resolveFeedback = (p4, counters) => {
|
|
388867
388968
|
if (p4.feedbackEmpty) {
|
|
@@ -388995,7 +389096,13 @@ var resolveGrilling = (p4, counters, head7) => {
|
|
|
388995
389096
|
};
|
|
388996
389097
|
};
|
|
388997
389098
|
var resolveCleanOrIdle = (p4, counters, head7) => {
|
|
388998
|
-
|
|
389099
|
+
const isHealthHead = head7 === "gtd: health-check" || head7 === "gtd: health-fix";
|
|
389100
|
+
if (!p4.workingTreeClean && !p4.pendingErrorsDeletion) return null;
|
|
389101
|
+
if (!p4.workingTreeClean && p4.pendingErrorsDeletion && p4.gtdDirExists) return null;
|
|
389102
|
+
if (!p4.workingTreeClean && p4.pendingErrorsDeletion && !isBoundary(head7) && head7 !== "gtd: package done" && !isHealthHead)
|
|
389103
|
+
return null;
|
|
389104
|
+
if (p4.workingTreeClean && !isBoundary(head7) && head7 !== "gtd: package done" && !isHealthHead)
|
|
389105
|
+
return null;
|
|
388999
389106
|
if (head7 === "gtd: done" && p4.squashEnabled && p4.squashBase !== void 0) {
|
|
389000
389107
|
if (p4.squashMsgPresent) {
|
|
389001
389108
|
return {
|
|
@@ -389012,9 +389119,20 @@ var resolveCleanOrIdle = (p4, counters, head7) => {
|
|
|
389012
389119
|
return { state: "squashing", autoAdvance: true, context: buildContext(p4, counters) };
|
|
389013
389120
|
}
|
|
389014
389121
|
const reviewable = p4.hasCommitsAfterLastDone && p4.reviewBase !== void 0 && (p4.refDiff ?? "").trim().length > 0;
|
|
389122
|
+
if (reviewable) {
|
|
389123
|
+
return { state: "clean", autoAdvance: false, context: buildContext(p4, counters) };
|
|
389124
|
+
}
|
|
389125
|
+
const squashHealth = p4.squashEnabled && counters.healthFixCount > 0 && p4.healthFixBase !== void 0;
|
|
389015
389126
|
return {
|
|
389016
|
-
state:
|
|
389017
|
-
autoAdvance:
|
|
389127
|
+
state: "health-check",
|
|
389128
|
+
autoAdvance: true,
|
|
389129
|
+
edgeAction: {
|
|
389130
|
+
kind: "runHealthCheck",
|
|
389131
|
+
errorCount: counters.healthFixCount,
|
|
389132
|
+
capReached: counters.healthFixCount >= p4.fixAttemptCap,
|
|
389133
|
+
...squashHealth ? { healthFixBase: p4.healthFixBase } : {},
|
|
389134
|
+
...p4.pendingErrorsDeletion ? { commitErrorsReset: true } : {}
|
|
389135
|
+
},
|
|
389018
389136
|
context: buildContext(p4, counters)
|
|
389019
389137
|
};
|
|
389020
389138
|
};
|
|
@@ -389041,6 +389159,7 @@ var resolve4 = (events) => {
|
|
|
389041
389159
|
}
|
|
389042
389160
|
if (p4.errorsPresent)
|
|
389043
389161
|
return { state: "escalate", autoAdvance: false, context: buildContext(p4, counters) };
|
|
389162
|
+
if (p4.healthPresent) return resolveHealth(p4, counters);
|
|
389044
389163
|
if (p4.feedbackPresent) return resolveFeedback(p4, counters);
|
|
389045
389164
|
if (p4.gtdDirExists) return resolveGtdLifecycle(p4, counters, head7, corrupt);
|
|
389046
389165
|
if (p4.reviewPresent) return resolveReviewLifecycle(p4, counters, head7);
|
|
@@ -389056,6 +389175,18 @@ var resolve4 = (events) => {
|
|
|
389056
389175
|
context: buildContext(p4, counters)
|
|
389057
389176
|
};
|
|
389058
389177
|
}
|
|
389178
|
+
if (p4.squashEnabled && p4.healthFixBase !== void 0 && p4.squashMsgPresent && !p4.codeDirty) {
|
|
389179
|
+
return {
|
|
389180
|
+
state: "squashing",
|
|
389181
|
+
autoAdvance: false,
|
|
389182
|
+
edgeAction: {
|
|
389183
|
+
kind: "squashCommit",
|
|
389184
|
+
squashBase: p4.healthFixBase,
|
|
389185
|
+
commitMessage: p4.squashMsgContent
|
|
389186
|
+
},
|
|
389187
|
+
context: buildContext(p4, counters)
|
|
389188
|
+
};
|
|
389189
|
+
}
|
|
389059
389190
|
if (isBoundary(head7) && !p4.workingTreeClean && !p4.todoCommitted || head7 === "gtd: new task" && p4.workingTreeClean) {
|
|
389060
389191
|
return {
|
|
389061
389192
|
state: "new-feature",
|
|
@@ -389076,7 +389207,8 @@ var EDGE_ONLY_STATES2 = /* @__PURE__ */ new Set([
|
|
|
389076
389207
|
"await-review",
|
|
389077
389208
|
"accept-review",
|
|
389078
389209
|
"close-package",
|
|
389079
|
-
"done"
|
|
389210
|
+
"done",
|
|
389211
|
+
"health-check"
|
|
389080
389212
|
]);
|
|
389081
389213
|
var isEdgeOnly = (state) => EDGE_ONLY_STATES2.has(state);
|
|
389082
389214
|
var detect = () => Effect_exports.gen(function* () {
|
|
@@ -389088,6 +389220,19 @@ var detect = () => Effect_exports.gen(function* () {
|
|
|
389088
389220
|
});
|
|
389089
389221
|
|
|
389090
389222
|
// src/program.ts
|
|
389223
|
+
var IDLE_RESULT = {
|
|
389224
|
+
state: "idle",
|
|
389225
|
+
autoAdvance: false,
|
|
389226
|
+
context: {
|
|
389227
|
+
testFixCount: 0,
|
|
389228
|
+
reviewFixCount: 0,
|
|
389229
|
+
packages: DEFAULT_PAYLOAD.packages,
|
|
389230
|
+
diff: DEFAULT_PAYLOAD.diff,
|
|
389231
|
+
lastCommitSubject: DEFAULT_PAYLOAD.lastCommitSubject,
|
|
389232
|
+
workingTreeClean: DEFAULT_PAYLOAD.workingTreeClean,
|
|
389233
|
+
feedbackContent: DEFAULT_PAYLOAD.feedbackContent
|
|
389234
|
+
}
|
|
389235
|
+
};
|
|
389091
389236
|
var MAX_EDGE_HOPS = 100;
|
|
389092
389237
|
function makeProgram(opts = {}) {
|
|
389093
389238
|
const argv = opts.argv ?? process.argv;
|
|
@@ -389138,7 +389283,22 @@ function makeProgram(opts = {}) {
|
|
|
389138
389283
|
}
|
|
389139
389284
|
const result = yield* detect();
|
|
389140
389285
|
if (result.edgeAction !== void 0) {
|
|
389141
|
-
yield* perform(result.edgeAction);
|
|
389286
|
+
const { stop: stop2 } = yield* perform(result.edgeAction);
|
|
389287
|
+
if (stop2) {
|
|
389288
|
+
const builtPrompt2 = buildPrompt(IDLE_RESULT, config2.resolveModel, json2 ? "json" : "plain");
|
|
389289
|
+
if (json2) {
|
|
389290
|
+
write4(
|
|
389291
|
+
JSON.stringify({
|
|
389292
|
+
state: "idle",
|
|
389293
|
+
autoAdvance: false,
|
|
389294
|
+
prompt: builtPrompt2
|
|
389295
|
+
}) + "\n"
|
|
389296
|
+
);
|
|
389297
|
+
} else {
|
|
389298
|
+
write4(builtPrompt2);
|
|
389299
|
+
}
|
|
389300
|
+
return;
|
|
389301
|
+
}
|
|
389142
389302
|
}
|
|
389143
389303
|
if (isEdgeOnly(result.state)) {
|
|
389144
389304
|
continue;
|