@pmelab/gtd 27.3.0 → 27.5.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 (79) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +22 -1
  3. package/dist/chunks/{Schema-CyImoHTj.mjs → Schema-DO5ttJaI.mjs} +1 -1
  4. package/dist/chunks/{reserved-BPdVZkgv.mjs → reserved-DM5W_LAt.mjs} +64 -3
  5. package/dist/chunks/{tail-iLAMoigl.mjs → tail-6gvLXXcg.mjs} +1 -1
  6. package/dist/chunks/{wire-BY1S-obW.mjs → wire-C12dahrH.mjs} +6 -4
  7. package/dist/flows.mjs +27 -2
  8. package/dist/gtd.bundle.mjs +218 -32
  9. package/dist/testkit.mjs +358 -37
  10. package/dist/wire.mjs +1 -1
  11. package/dist/workflows/cleanup.mjs +2 -2
  12. package/dist/workflows/feature.mjs +140 -64
  13. package/dist/workflows/fix.mjs +2 -2
  14. package/dist/workflows/review.mjs +2 -2
  15. package/dist/workflows/rollout.mjs +1 -1
  16. package/dist/workflows.mjs +346 -107
  17. package/package.json +1 -1
  18. package/src/cli/Cli.test.ts +49 -1
  19. package/src/cli/Cli.ts +25 -8
  20. package/src/cli/History.ts +5 -0
  21. package/src/cli/Program.ts +1 -0
  22. package/src/edge/Edge.test.ts +205 -1
  23. package/src/edge/Edge.ts +105 -12
  24. package/src/emit/GitScript.test.ts +39 -3
  25. package/src/emit/GitScript.ts +25 -1
  26. package/src/emit/index.ts +2 -0
  27. package/src/flags/index.test.ts +14 -1
  28. package/src/flags/index.ts +14 -3
  29. package/src/flows/helpers.ts +34 -2
  30. package/src/flows/runtime.ts +7 -0
  31. package/src/install/Install.ts +2 -1
  32. package/src/platform/Git.test.ts +25 -1
  33. package/src/platform/Git.ts +30 -0
  34. package/src/platform/index.ts +1 -0
  35. package/src/replay/Format.test.ts +16 -1
  36. package/src/replay/Replay.test.ts +91 -0
  37. package/src/replay/Replay.ts +22 -2
  38. package/src/replay/Trailers.test.ts +110 -1
  39. package/src/replay/Trailers.ts +37 -1
  40. package/src/replay/index.ts +2 -0
  41. package/src/replay/judgeDiffShape.test.ts +43 -0
  42. package/src/replay/seamGate.test.ts +62 -0
  43. package/src/step/LandStep.ts +1 -0
  44. package/src/step/planStart.ts +3 -2
  45. package/src/step/planStep.test.ts +32 -0
  46. package/src/step/planStep.ts +3 -1
  47. package/src/testkit/EmittedScriptRecognizer.test.ts +27 -0
  48. package/src/testkit/EmittedScriptRecognizer.ts +12 -0
  49. package/src/testkit/FakeGitOperations.ts +5 -0
  50. package/src/testkit/GitTiers.ts +158 -0
  51. package/src/testkit/InMemRepo.ts +38 -3
  52. package/src/testkit/TestRepo.ts +3 -0
  53. package/src/testkit/steps.ts +18 -0
  54. package/src/wire/BeatDocument.test.ts +25 -0
  55. package/src/wire/BeatDocument.ts +4 -2
  56. package/src/wire/BeatPlain.test.ts +1 -0
  57. package/src/wire/BeatStatus.test.ts +2 -0
  58. package/src/wire/BeatStatus.ts +13 -2
  59. package/src/wire/WireSchema.test.ts +10 -0
  60. package/src/wire/types.ts +8 -0
  61. package/src/workflow/Workflow.ts +1 -0
  62. package/src/workflow/index.ts +8 -1
  63. package/src/workflow/vars.test.ts +23 -1
  64. package/src/workflow/vars.ts +24 -0
  65. package/src/workflows/feature/feature.ts +9 -2
  66. package/src/workflows/feature/index.ts +1 -0
  67. package/src/workflows/feature/seam-pin.feature +52 -0
  68. package/src/workflows/feature/seam.feature +387 -0
  69. package/src/workflows/feature/seam.test.ts +88 -0
  70. package/src/workflows/feature/seam.ts +110 -0
  71. package/src/workflows/index.ts +1 -0
  72. package/src/workflows/shared/options.test.ts +12 -1
  73. package/src/workflows/shared/options.ts +1 -0
  74. package/src/workflows/shared/skills.test.ts +20 -2
  75. package/src/workflows/shared/skills.ts +6 -0
  76. package/src/workflows/shared/text.test.ts +59 -0
  77. package/src/workflows/shared/text.ts +86 -0
  78. package/src/workflows/shared/vars.ts +4 -0
  79. package/src/workflows/shared/workflows.steps.ts +39 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtd",
3
- "version": "27.3.0",
3
+ "version": "27.5.0",
4
4
  "description": "Drive the gtd loop inside Claude Code: agent turns run as subagents, gtd decides every beat",
5
5
  "author": {
6
6
  "name": "Philipp Melab"
package/README.md CHANGED
@@ -552,6 +552,14 @@ unknown key. `gtd.config.ts` is the only repository config: gtd 23 removed the
552
552
  home directory — fails every command until you delete it. See
553
553
  [Migrating from `.gtdrc`](https://github.com/pmelab/gtd/blob/main/docs/configuration.md#migrating-from-gtdrc).
554
554
 
555
+ **Naive pass (flag `GTD_FLAG_NAIVE_PASS=1`, off by default).** Instead of an
556
+ architecture and package files, the agent first implements the requirements
557
+ directly. A judge checks whether it changed anything outside the expected set
558
+ (tests, steering files; see `seamAllow`). Incidental changes land; a needed seam
559
+ change goes to you for approval and is refactored first; a wrong approach is
560
+ retried twice, then rests for you to steer or overrule it. Details in
561
+ [the naive-pass path](docs/configuration.md#the-naive-pass-path).
562
+
555
563
  ### Rolling out and cleaning up release flags
556
564
 
557
565
  Two more bundled workflows retire a release flag:
@@ -581,7 +589,7 @@ kept before it squashes.
581
589
 
582
590
  ```sh
583
591
  gtd history list # one line per kept process
584
- gtd history show <ref> # replay its steps and judge verdicts
592
+ gtd history show <ref> # replay its steps, judge verdicts and approvals
585
593
  gtd history fetch | sh # pull every kept ref from origin
586
594
  ```
587
595
 
@@ -601,6 +609,19 @@ of on the branch (see [Kept tips](docs/configuration.md#kept-tips)). Those refs
601
609
  are not process history: `gtd history` does not list them, and the
602
610
  `refs/gtd/history/*` refspecs above neither push nor fetch them.
603
611
 
612
+ A workflow can rest at an approval gate (`human(name, { approve: true })`; the
613
+ `seamGate` helper does this after a `refactor-first` diff-shape judgment). Only
614
+ a clean `gtd land` approves it, and the commit records who in a
615
+ `Gtd-Approved: {"step":"<step>#<n>","name":"<name>","email":"<email>"}` trailer;
616
+ a landing with any edit is refused (as is a script run after the tree changed —
617
+ revert and land again), and `gtd abandon` is the only other way out. `approve`
618
+ cannot be combined with `mode` or `file` (refused when replay reaches the step).
619
+ Git hooks do not run on the approval commit; `commit.gpgSign` still applies, so
620
+ a failing signer refuses the approval. `gtd next --json` lists the process's
621
+ approvals as `approvals` (`[]` when none), and `gtd history show` prints each as
622
+ `approved <step>#<n> by <name> <<email>>`. See
623
+ [Step options](docs/configuration.md#step-options).
624
+
604
625
  ## Entrypoints
605
626
 
606
627
  gtd is one package, `@pmelab/gtd`, and every entrypoint shares its version and
@@ -25449,4 +25449,4 @@ Struct({
25449
25449
  description: "Represents an issue returned by the ArrayFormatter formatter"
25450
25450
  });
25451
25451
  //#endregion
25452
- export { logWarning as $, format$4 as $i, map$4 as $n, make$20 as $r, provide$1 as $t, catchIf as A, get$7 as Ai, scopeMake as An, flatMap$3 as Ar, suspend$1 as At, flatMap$1 as B, isNone as Bi, get as Bn, sync$1 as Br, unsafeMakeLatch as Bt, all as C, toArray$1 as Ci, isInterrupted as Cn, exitFail as Cr, sandbox as Ct, async as D, GenericTag as Di, squash as Dn, exitZipRight as Dr, scopedWith as Dt, asVoid as E, unsafeHead as Ei, reduceWithContext as En, exitVoid$1 as Er, scoped as Et, ensuring as F, splitAt as Fi, join as Fn, map$5 as Fr, tryMap as Ft, forkIn as G, some as Gi, TypeId$4 as Gn, whenEffect as Gr, zipLeft as Gt, fnUntraced as H, map$11 as Hi, modify as Hn, uninterruptible$1 as Hr, when as Ht, exit as I, filter$3 as Ii, sequential$1 as In, onInterrupt as Ir, tryPromise as It, ignore as J, match$7 as Ji, all$2 as Jn, DeferredTypeId as Jr, spaced as Jt, forkScoped as K, isLeft as Ki, Class as Kn, withFiberRuntime$1 as Kr, zipRight as Kt, fail$1 as L, flatMap$6 as Li, bind as Ln, scopeClose as Lr, try_ as Lt, die as M, omit$1 as Mi, _await as Mn, interruptAsFiber as Mr, tap as Mt, dieMessage as N, empty$22 as Ni, inheritAll as Nn, interruptFiber as Nr, tapErrorCause as Nt, catchAll as O, Tag as Oi, ensuring$1 as On, fiberId as Or, serviceOption as Ot, either as P, fromIterable$6 as Pi, interruptAllAs as Pn, interruptible$2 as Pr, timeout as Pt, isEffect as Q, NodeInspectSymbol as Qi, isFailure as Qn, ids as Qr, mergeAll as Qt, failCause as R, fromNullable as Ri, bindTo as Rn, succeed$5 as Rr, uninterruptible as Rt, addFinalizer as S, takeRight as Si, isDieType as Sn, exitAs as Sr, runtime as St, as as T, unsafeGet as Ti, map$3 as Tn, exitSucceed$1 as Tr, scopeWith as Tt, forEach as U, match$6 as Ui, set as Un, uninterruptibleMask$2 as Ur, withFiberRuntime as Ut, flatten as V, isSome as Vi, make$12 as Vn, tap$1 as Vr, unsafeMakeSemaphore as Vt, forkDaemon as W, none$4 as Wi, update$1 as Wn, void_$2 as Wr, zip as Wt, interruptible as X, pipeArguments as Xi, fail$4 as Xn, combine$5 as Xr, tagged as Xt, interrupt as Y, right as Yi, die$2 as Yn, empty$15 as Yr, TaggedError$1 as Yt, intoDeferred as Z, BaseProto as Zi, failCause$2 as Zn, combineAll as Zr, fromEffect as Zt, decodeSync as _, of$1 as _i, fail$2 as _n, deferredIsDone as _r, raceWith as _t, Literal as a, constUndefined as aa, size$2 as ai, updateContext as an, zipRight$1 as ar, matchCause as at, acquireRelease as b, reverse$1 as bi, interrupt$1 as bn, deferredUnsafeDone as br, runPromise as bt, String$ as c, identity as ca, append as ci, Scope as cn, asyncInterrupt as cr, negate as ct, Union as d, empty$19 as di, close as dn, deferredAwait as dr, orElse$1 as dt, toJSON as ea, none$2 as ei, provideMerge as en, mapBoth$2 as er, makeSemaphore as et, between as f, filter$1 as fi, extend$1 as fn, deferredComplete as fr, orElseSucceed as ft, standardSchemaV1 as g, map$9 as gi, die$1 as gn, deferredInterruptWith as gr, race as gt, optionalWith as h, isEmpty$5 as hi, NoSuchElementException as hn, deferredInterrupt as hr, provideService as ht, Defect as i, constTrue as ia, has$1 as ii, unsafeRunPromiseExit as in, zip$1 as ir, match as it, catchTag as j, getOption as ji, scopeTag as jn, interrupt$2 as jr, sync as jt, catchAllCause as k, add$2 as ki, forEachConcurrentDiscard as kn, fiberRefLocally as kr, succeed$1 as kt, Struct as l, pipe as la, appendAll$1 as li, addFinalizer$1 as ln, catchAll$1 as lr, onError as lt, optional as m, isChunk as mi, make$2 as mn, deferredFailCause as mr, provide as mt, Boolean$ as n, hasProperty as na, make$22 as ni, succeed$2 as nn, succeed$4 as nr, mapBoth$1 as nt, Never as o, constVoid as oa, decode as oi, forkIn$1 as on, as$1 as or, matchCauseEffect as ot, decodeUnknown as p, fromIterable$5 as pi, fork as pn, deferredFail as pr, promise as pt, gen as q, left as qi, spanTag as qn, zipRight$2 as qr, zipWith as qt, Cause as r, constFalse as ra, set$3 as ri, unsafeFork as rn, void_$1 as rr, mapError$1 as rt, Number$ as s, dual as sa, isFinite as si, unsafeMakeSemaphore$1 as sn, asVoid$1 as sr, matchEffect as st, Array$ as t, equals$2 as ta, get$4 as ti, scoped$1 as tn, match$3 as tr, map$1 as tt, TaggedError as u, drop as ui, addFinalizerExit as un, currentContext as ur, orDie as ut, encodeSync as v, prepend$1 as vi, failureOrCause as vn, deferredMake as vr, repeat as vt, annotateLogs as w, unsafeFromArray as wi, isInterruptedOnly as wn, exitFailCause$1 as wr, scope as wt, acquireUseRelease as x, take as xi, interruptors as xn, deferredUnsafeMake as xr, runPromiseExit as xt, _void as y, reduce$6 as yi, flipCauseOption as yn, deferredSucceed as yr, retry as yt, fiberIdWith as z, getOrElse as zi, sleep as zn, suspend$3 as zr, uninterruptibleMask as zt };
25452
+ export { logWarning as $, NodeInspectSymbol as $i, map$4 as $n, make$20 as $r, provide$1 as $t, catchIf as A, add$2 as Ai, scopeMake as An, flatMap$3 as Ar, suspend$1 as At, flatMap$1 as B, getOrElse as Bi, get as Bn, sync$1 as Br, unsafeMakeLatch as Bt, all as C, toArray$1 as Ci, isInterrupted as Cn, exitFail as Cr, sandbox as Ct, async as D, GenericTag as Di, squash as Dn, exitZipRight as Dr, scopedWith as Dt, asVoid as E, unsafeHead as Ei, reduceWithContext as En, exitVoid$1 as Er, scoped as Et, ensuring as F, fromIterable$6 as Fi, join as Fn, map$5 as Fr, tryMap as Ft, forkIn as G, none$4 as Gi, TypeId$4 as Gn, whenEffect as Gr, zipLeft as Gt, fnUntraced as H, isSome as Hi, modify as Hn, uninterruptible$1 as Hr, when as Ht, exit as I, splitAt as Ii, sequential$1 as In, onInterrupt as Ir, tryPromise as It, ignore as J, left as Ji, all$2 as Jn, DeferredTypeId as Jr, spaced as Jt, forkScoped as K, some as Ki, Class as Kn, withFiberRuntime$1 as Kr, zipRight as Kt, fail$1 as L, filter$3 as Li, bind as Ln, scopeClose as Lr, try_ as Lt, die as M, getOption as Mi, _await as Mn, interruptAsFiber as Mr, tap as Mt, dieMessage as N, omit$1 as Ni, inheritAll as Nn, interruptFiber as Nr, tapErrorCause as Nt, catchAll as O, Reference as Oi, ensuring$1 as On, fiberId as Or, serviceOption as Ot, either as P, empty$22 as Pi, interruptAllAs as Pn, interruptible$2 as Pr, timeout as Pt, isEffect as Q, BaseProto as Qi, isFailure as Qn, ids as Qr, mergeAll as Qt, failCause as R, flatMap$6 as Ri, bindTo as Rn, succeed$5 as Rr, uninterruptible as Rt, addFinalizer as S, takeRight as Si, isDieType as Sn, exitAs as Sr, runtime as St, as as T, unsafeGet as Ti, map$3 as Tn, exitSucceed$1 as Tr, scopeWith as Tt, forEach as U, map$11 as Ui, set as Un, uninterruptibleMask$2 as Ur, withFiberRuntime as Ut, flatten as V, isNone as Vi, make$12 as Vn, tap$1 as Vr, unsafeMakeSemaphore as Vt, forkDaemon as W, match$6 as Wi, update$1 as Wn, void_$2 as Wr, zip as Wt, interruptible as X, right as Xi, fail$4 as Xn, combine$5 as Xr, tagged as Xt, interrupt as Y, match$7 as Yi, die$2 as Yn, empty$15 as Yr, TaggedError$1 as Yt, intoDeferred as Z, pipeArguments as Zi, failCause$2 as Zn, combineAll as Zr, fromEffect as Zt, decodeSync as _, of$1 as _i, fail$2 as _n, deferredIsDone as _r, raceWith as _t, Literal as a, constTrue as aa, size$2 as ai, updateContext as an, zipRight$1 as ar, matchCause as at, acquireRelease as b, reverse$1 as bi, interrupt$1 as bn, deferredUnsafeDone as br, runPromise as bt, String$ as c, dual as ca, append as ci, Scope as cn, asyncInterrupt as cr, negate as ct, Union as d, empty$19 as di, close as dn, deferredAwait as dr, orElse$1 as dt, format$4 as ea, none$2 as ei, provideMerge as en, mapBoth$2 as er, makeSemaphore as et, between as f, filter$1 as fi, extend$1 as fn, deferredComplete as fr, orElseSucceed as ft, standardSchemaV1 as g, map$9 as gi, die$1 as gn, deferredInterruptWith as gr, race as gt, optionalWith as h, isEmpty$5 as hi, NoSuchElementException as hn, deferredInterrupt as hr, provideService as ht, Defect as i, constFalse as ia, has$1 as ii, unsafeRunPromiseExit as in, zip$1 as ir, match as it, catchTag as j, get$7 as ji, scopeTag as jn, interrupt$2 as jr, sync as jt, catchAllCause as k, Tag as ki, forEachConcurrentDiscard as kn, fiberRefLocally as kr, succeed$1 as kt, Struct as l, identity as la, appendAll$1 as li, addFinalizer$1 as ln, catchAll$1 as lr, onError as lt, optional as m, isChunk as mi, make$2 as mn, deferredFailCause as mr, provide as mt, Boolean$ as n, equals$2 as na, make$22 as ni, succeed$2 as nn, succeed$4 as nr, mapBoth$1 as nt, Never as o, constUndefined as oa, decode as oi, forkIn$1 as on, as$1 as or, matchCauseEffect as ot, decodeUnknown as p, fromIterable$5 as pi, fork as pn, deferredFail as pr, promise as pt, gen as q, isLeft as qi, spanTag as qn, zipRight$2 as qr, zipWith as qt, Cause as r, hasProperty as ra, set$3 as ri, unsafeFork as rn, void_$1 as rr, mapError$1 as rt, Number$ as s, constVoid as sa, isFinite as si, unsafeMakeSemaphore$1 as sn, asVoid$1 as sr, matchEffect as st, Array$ as t, toJSON as ta, get$4 as ti, scoped$1 as tn, match$3 as tr, map$1 as tt, TaggedError as u, pipe as ua, drop as ui, addFinalizerExit as un, currentContext as ur, orDie as ut, encodeSync as v, prepend$1 as vi, failureOrCause as vn, deferredMake as vr, repeat as vt, annotateLogs as w, unsafeFromArray as wi, isInterruptedOnly as wn, exitFailCause$1 as wr, scope as wt, acquireUseRelease as x, take as xi, interruptors as xn, deferredUnsafeMake as xr, runPromiseExit as xt, _void as y, reduce$6 as yi, flipCauseOption as yn, deferredSucceed as yr, retry as yt, fiberIdWith as z, fromNullable as zi, sleep as zn, suspend$3 as zr, uninterruptibleMask as zt };
@@ -1,6 +1,6 @@
1
- import { accessFor, agent, answered, changesSince, check, codeThreads, defineWorkflow, env, head, human, judge, numeric, quote, skillsFor, start, vars } from "../flows.mjs";
1
+ import { accessFor, agent, answered, changesSince, check, codeThreads, defineWorkflow, displayPath, env, head, human, judge, numeric, quote, skillsFor, start, vars } from "../flows.mjs";
2
2
  import { escalation as escalation$1, gate as gate$1, healthy as healthy$1, lensScopes, lensesOf, removedAssertions } from "../workflow-kit.mjs";
3
- import { c as String$, g as standardSchemaV1, h as optionalWith, l as Struct } from "./Schema-CyImoHTj.mjs";
3
+ import { c as String$, g as standardSchemaV1, h as optionalWith, l as Struct } from "./Schema-DO5ttJaI.mjs";
4
4
  //#region src/workflows/shared/prose.ts
5
5
  const skillsPreamble = `- Load whatever's listed here that your harness actually has installed,
6
6
  skip anything it doesn't — silently, never stopping or asking about a
@@ -1071,6 +1071,60 @@ ${it.processCostByModel.map((m) => `- ${m.model}: ${m.cost}\n`).join("")}
1071
1071
  Print the closing message and stop — this writes nothing itself.
1072
1072
  `;
1073
1073
  };
1074
+ const listed = (paths, unreadable = []) => paths.map((path) => ` - \`${displayPath(path)}\`${unreadable.includes(path) ? " (unreadable)" : ""}`).join("\n");
1075
+ const naivePassPrompt = (rejection) => `${stateFileRules}
1076
+ - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
1077
+ - Implement them the quickest way that works — the naive version. Do not
1078
+ restructure, abstract or refactor around the change; a later step judges
1079
+ the shape of your diff and a reviewer makes it fit
1080
+ - Add tests as you go, and leave \`.gtd/\` untouched
1081
+ - Leave everything uncommitted and finish your turn — do not commit
1082
+ ${rejection === void 0 ? "" : `
1083
+ Your previous attempt was rejected: the judge answered \`reject\` (p=${rejection.p}),
1084
+ meaning the naive pass solved the task the wrong way — discard it and
1085
+ re-implement. It changed these files outside the expected set:
1086
+ ${listed(rejection.strays)}
1087
+ Start over and choose a different approach; do not rebuild the same diff.
1088
+ `}`;
1089
+ const implementBuildingPrompt = (reference) => `${stateFileRules}
1090
+ - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
1091
+ - Implement them properly, fitting the codebase's seams and conventions
1092
+ - Reference only: \`git show ${reference}\` shows an earlier naive attempt.
1093
+ Read it for ideas; never apply, cherry-pick or check it out — write
1094
+ the change fresh
1095
+ - Put the new behaviour behind a new release flag, following the
1096
+ repository's flag conventions
1097
+ - Write the tests first, then the code that makes them pass
1098
+ - Leave \`.gtd/REQUIREMENTS.md\` in place and everything uncommitted, then
1099
+ finish your turn
1100
+ `;
1101
+ const implementFixPrompt = () => `${stateFileRules}
1102
+ ${fixFeedbackPrompt}
1103
+ - Leave everything uncommitted and finish your turn — do not commit
1104
+ `;
1105
+ const seamSteerMessage = (rejection) => `The naive pass was rejected three times and the retries are spent.
1106
+ The last attempt changed these files outside the expected set:
1107
+ ${listed(rejection.strays)}
1108
+
1109
+ Two ways forward (then run \`gtd land\`):
1110
+ - **Steer** — edit \`.gtd/REQUIREMENTS.md\` to redirect the change; the naive
1111
+ pass runs again with a fresh retry budget
1112
+ - **Overrule** — land a clean tree to accept the last attempt's approach:
1113
+ the implementation is built against it as reference, with no seam gate
1114
+ and no refactor. Risk: this ships the seam the judge objected to,
1115
+ unaddressed — the choice is recorded in history.
1116
+
1117
+ An edit together with an approval counts as steer. Only
1118
+ \`.gtd/REQUIREMENTS.md\` is meant to change here: any other edit lands on the
1119
+ branch unjudged.
1120
+ `;
1121
+ const seamByHandMessage = (strays, unreadable, reference) => `The judge could not read some of these files, so the diff was never
1122
+ fully judged and no refactor runs. Unreadable ones are marked:
1123
+ ${listed(strays, unreadable)}
1124
+
1125
+ See them with \`git show ${reference} -- <path>\` and review by hand. Land a
1126
+ clean tree to go on to the implementation, which runs next.
1127
+ `;
1074
1128
  //#endregion
1075
1129
  //#region src/workflows/shared/steps.ts
1076
1130
  const FEEDBACK = ".gtd/FEEDBACK.md";
@@ -1257,6 +1311,8 @@ const defaults = {
1257
1311
  reviewNoteActionable: "0.7",
1258
1312
  reviewSeverityMinP: "0.7",
1259
1313
  refactorFlagPattern: "\\bGTD_FLAG_\\w+",
1314
+ seamAllow: "**/*.test.*, **/*.spec.*, **/*.feature, **/tests/**, **/test/**, **/__tests__/**, .gtd/**",
1315
+ flag_naive_pass: "0",
1260
1316
  qualityReviews: "correctness, owasp-security, ponytail-review, test-audit, conventions, spec-challenge"
1261
1317
  };
1262
1318
  /** Environment settings. */
@@ -1275,6 +1331,7 @@ const ShippedOptions = Struct({
1275
1331
  reviewNoteActionable: setting,
1276
1332
  reviewSeverityMinP: setting,
1277
1333
  refactorFlagPattern: setting,
1334
+ seamAllow: setting,
1278
1335
  qualityReviews: setting,
1279
1336
  testCommand: setting,
1280
1337
  plannerModel: setting,
@@ -1339,6 +1396,10 @@ const skills = (vars) => ({
1339
1396
  refactor: ["ponytail"],
1340
1397
  "refactor.fix.suite": ["ponytail", "debugging-and-error-recovery"],
1341
1398
  "refactor.health": ["ponytail", "debugging-and-error-recovery"],
1399
+ seam: ["incremental-implementation"],
1400
+ implement: ["test-driven-development", "incremental-implementation"],
1401
+ "implement.fix.suite": ["debugging-and-error-recovery"],
1402
+ "implement.health": ["debugging-and-error-recovery"],
1342
1403
  ...lensScopes("build.quality", lensesOf(vars.qualityReviews ?? ""), (lens) => builtInLenses[lens]?.skills ?? [lens])
1343
1404
  });
1344
1405
  //#endregion
@@ -1362,4 +1423,4 @@ const steering = {
1362
1423
  [REVIEW]: "review"
1363
1424
  };
1364
1425
  //#endregion
1365
- export { reviewGateBlockedMessage as $, architectureAuthorPrompt as A, designGateAnswerMessage as B, fixRisks as C, reviewing as D, reviewQuality as E, buildReviewCollectingPrompt as F, handOffAwaitReviewMessage as G, designTriagePrompt as H, buildReviewFixNitsPrompt as I, packagesItemFixSuitePrompt as J, idleMessage as K, buildReviewTriageMessage as L, architectureGateAnswerMessage as M, buildReviewAnswerQuestionsPrompt as N, agentWithSkills as O, buildReviewAwaitAfterDeferMessage as P, reviewEditsCapture as Q, builderSystem as R, fixQuality as S, reviewMissing as T, finisherSystem as U, designSystem as V, fixReviewNotesPrompt as W, refactorFixPrompt as X, refactorBuildingPrompt as Y, reviewEditNotesCapture as Z, REQUIREMENTS as _, flagShipped as a, coder as b, envDefaults as c, gate as d, reviewNotesCapture as et, guarded as f, QUALITY as g, FEEDBACK as h, skills as i, unwindFailure as it, architectureDecomposePrompt as j, architectSystem as k, baseline as l, ARCHITECTURE as m, summary as n, startGateBlockedMessage as nt, shipped as o, healthy as p, packagesItemBuildingPrompt as q, access as r, unwindFailedMessage as rt, defaults as s, steering as t, reviewerSystem as tt, escalation as u, REVIEW as v, planner as w, fixCheck as x, awaitReview as y, deferNotesScript as z };
1426
+ export { refactorFixPrompt as $, architectureAuthorPrompt as A, designGateAnswerMessage as B, fixRisks as C, reviewing as D, reviewQuality as E, buildReviewCollectingPrompt as F, handOffAwaitReviewMessage as G, designTriagePrompt as H, buildReviewFixNitsPrompt as I, implementFixPrompt as J, idleMessage as K, buildReviewTriageMessage as L, architectureGateAnswerMessage as M, buildReviewAnswerQuestionsPrompt as N, agentWithSkills as O, buildReviewAwaitAfterDeferMessage as P, refactorBuildingPrompt as Q, builderSystem as R, fixQuality as S, reviewMissing as T, finisherSystem as U, designSystem as V, fixReviewNotesPrompt as W, packagesItemBuildingPrompt as X, naivePassPrompt as Y, packagesItemFixSuitePrompt as Z, REQUIREMENTS as _, flagShipped as a, seamByHandMessage as at, coder as b, envDefaults as c, unwindFailedMessage as ct, gate as d, reviewEditNotesCapture as et, guarded as f, QUALITY as g, FEEDBACK as h, skills as i, reviewerSystem as it, architectureDecomposePrompt as j, architectSystem as k, baseline as l, unwindFailure as lt, ARCHITECTURE as m, summary as n, reviewGateBlockedMessage as nt, shipped as o, seamSteerMessage as ot, healthy as p, implementBuildingPrompt as q, access as r, reviewNotesCapture as rt, defaults as s, startGateBlockedMessage as st, steering as t, reviewEditsCapture as tt, escalation as u, REVIEW as v, planner as w, fixCheck as x, awaitReview as y, deferNotesScript as z };
@@ -1,6 +1,6 @@
1
1
  import { read, refuse, removeScript, run, scope, showScript, vars } from "../flows.mjs";
2
2
  import { fixFindings, lensesOf, qualityLap as qualityLap$1, reviewToHandOff } from "../workflow-kit.mjs";
3
- import { C as fixRisks, D as reviewing, E as reviewQuality, G as handOffAwaitReviewMessage, S as fixQuality, T as reviewMissing, f as guarded, g as QUALITY, p as healthy, u as escalation, v as REVIEW, x as fixCheck, y as awaitReview } from "./reserved-BPdVZkgv.mjs";
3
+ import { C as fixRisks, D as reviewing, E as reviewQuality, G as handOffAwaitReviewMessage, S as fixQuality, T as reviewMissing, f as guarded, g as QUALITY, p as healthy, u as escalation, v as REVIEW, x as fixCheck, y as awaitReview } from "./reserved-DM5W_LAt.mjs";
4
4
  //#region src/workflows/shared/tail.ts
5
5
  /** The reviewer's own `.gtd/REVIEW.md`, kept beside the human's notes so `fix` can tell them apart. */
6
6
  const REVIEW_ORIG = ".gtd/REVIEW.orig.md";
@@ -110,7 +110,7 @@ const demandOf = (input) => {
110
110
  * instead of failing loudly.
111
111
  */
112
112
  const statusOf = (input) => {
113
- const { rendered, idle, initial, workflow, log, changes, next, cost, costByModel } = input;
113
+ const { rendered, idle, initial, workflow, log, changes, next, cost, costByModel, approvals } = input;
114
114
  return {
115
115
  idle,
116
116
  initial,
@@ -128,7 +128,8 @@ const statusOf = (input) => {
128
128
  next,
129
129
  cost,
130
130
  costByModel,
131
- judge: rendered.judge
131
+ judge: rendered.judge,
132
+ approvals
132
133
  };
133
134
  };
134
135
  //#endregion
@@ -168,7 +169,7 @@ const dispatchFieldsOf = (demand) => {
168
169
  }
169
170
  };
170
171
  /**
171
- * Flatten a `Demand` plus its `BeatStatus` into the single 24-key document
172
+ * Flatten a `Demand` plus its `BeatStatus` into the single 25-key document
172
173
  * `gtd next --json` emits — the ONLY place the two are joined. `cost`/
173
174
  * `costByModel` are omitted together, exactly when no cost was recorded
174
175
  * (`cost <= 0`).
@@ -200,7 +201,8 @@ const beatDocument = (demand, status) => {
200
201
  costByModel: hasCost ? status.costByModel : void 0,
201
202
  judge: status.judge,
202
203
  skills,
203
- access
204
+ access,
205
+ approvals: status.approvals
204
206
  };
205
207
  };
206
208
  const renderBeatJson = (document) => JSON.stringify(document) + "\n";
package/dist/flows.mjs CHANGED
@@ -354,7 +354,8 @@ const strayContent = (strays) => strays.map((c) => [
354
354
  */
355
355
  const judgeDiffShape = async (name, options) => {
356
356
  const { changes: all, allow } = options;
357
- const isStray = (c) => !allow.some((pattern) => matches(c.path, pattern));
357
+ const exempt = options.exempt ?? [];
358
+ const isStray = (c) => !exempt.includes(c.path) && !allow.some((pattern) => matches(c.path, pattern));
358
359
  const strayChanges = all.filter(isStray).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0).map(({ path, status, before, after }) => ({
359
360
  path,
360
361
  status,
@@ -400,6 +401,28 @@ const judgeDiffShape = async (name, options) => {
400
401
  judgment
401
402
  };
402
403
  };
404
+ const displayPath = (path) => /[\x00-\x1f\x7f-\x9f]/.test(path) ? JSON.stringify(path) : path;
405
+ const seamReport = (shape) => {
406
+ const unreadable = new Set(shape.unreadable);
407
+ return [
408
+ "Seam report: the judge found the diff's shape needs a refactor first. These paths stray outside the allowlist:",
409
+ "",
410
+ ...shape.strays.map((path) => `- ${displayPath(path)}${unreadable.has(path) ? " (unreadable — not judged)" : ""}`),
411
+ "",
412
+ "Landing a clean tree with `gtd land` approves this diff exactly as judged; edits are refused. To stop instead, run `gtd abandon`."
413
+ ].join("\n");
414
+ };
415
+ /**
416
+ * Rest at an approval gate `name` showing the seam report — only when
417
+ * `shape.choice` is `refactor-first`. `land` and `reject` reach no step.
418
+ */
419
+ const seamGate = async (name, shape) => {
420
+ if (shape.choice !== "refactor-first") return;
421
+ await human(name, {
422
+ message: seamReport(shape),
423
+ approve: true
424
+ });
425
+ };
403
426
  //#endregion
404
427
  //#region src/flows/factory.ts
405
428
  const FACTORY = Symbol.for("@pmelab/gtd/workflow-factory");
@@ -578,6 +601,7 @@ var flows_exports = /* @__PURE__ */ __exportAll({
578
601
  configure: () => configure,
579
602
  defineConfig: () => defineConfig,
580
603
  defineWorkflow: () => defineWorkflow,
604
+ displayPath: () => displayPath,
581
605
  env: () => env,
582
606
  glob: () => glob,
583
607
  hasThreadFor: () => hasThreadFor,
@@ -607,6 +631,7 @@ var flows_exports = /* @__PURE__ */ __exportAll({
607
631
  revertScript: () => revertScript,
608
632
  run: () => run,
609
633
  scope: () => scope,
634
+ seamGate: () => seamGate,
610
635
  sectionBodies: () => sectionBodies,
611
636
  sections: () => sections,
612
637
  showScript: () => showScript,
@@ -617,4 +642,4 @@ var flows_exports = /* @__PURE__ */ __exportAll({
617
642
  wrote: () => wrote
618
643
  });
619
644
  //#endregion
620
- export { FLOWS_PROTOCOL, FlowsProtocolError, accessFor, agent, answered, changes, changesSince, check, checkScript, codeChanges, codeThreads, configProblems, configure, defineConfig, defineWorkflow, env, glob, hasThreadFor, head, human, installContext, isFactoryWorkflow, judge, judgeDiffShape, kept, matches, moveScript, numeric, openQuestions, protocolMismatch, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restart, restoreScript, revertScript, run, scope, sectionBodies, sections, showScript, skillsFor, start, flows_exports as t, threads, vars, wrote };
645
+ export { FLOWS_PROTOCOL, FlowsProtocolError, accessFor, agent, answered, changes, changesSince, check, checkScript, codeChanges, codeThreads, configProblems, configure, defineConfig, defineWorkflow, displayPath, env, glob, hasThreadFor, head, human, installContext, isFactoryWorkflow, judge, judgeDiffShape, kept, matches, moveScript, numeric, openQuestions, protocolMismatch, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restart, restoreScript, revertScript, run, scope, seamGate, sectionBodies, sections, showScript, skillsFor, start, flows_exports as t, threads, vars, wrote };