@pmelab/gtd 35.0.0 → 36.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +31 -27
  3. package/dist/chunks/{reserved-C8Fm3s72.mjs → reserved-BAv71I4i.mjs} +10 -9
  4. package/dist/chunks/{tail-oaYewBN_.mjs → tail-kar1zhOb.mjs} +1 -1
  5. package/dist/flows.mjs +15 -10
  6. package/dist/gtd.bundle.mjs +14 -9
  7. package/dist/testkit.mjs +43 -23
  8. package/dist/workflows/cleanup.mjs +2 -2
  9. package/dist/workflows/feature.mjs +18 -11
  10. package/dist/workflows/fix.mjs +2 -2
  11. package/dist/workflows/review.mjs +2 -2
  12. package/dist/workflows/rollout.mjs +1 -1
  13. package/dist/workflows.mjs +37 -25
  14. package/package.json +1 -1
  15. package/skills/authoring/SKILL.md +3 -3
  16. package/src/flows/scripts.ts +31 -9
  17. package/src/testkit/FlowScripts.test.ts +58 -0
  18. package/src/testkit/FlowScripts.ts +61 -24
  19. package/src/workflows/feature/acceptance-criteria.feature +5 -1
  20. package/src/workflows/feature/coverage.feature +6 -3
  21. package/src/workflows/feature/fast-path.feature +11 -19
  22. package/src/workflows/feature/feature.test.ts +24 -4
  23. package/src/workflows/feature/feature.ts +8 -8
  24. package/src/workflows/feature/flag-mode.feature +17 -9
  25. package/src/workflows/feature/lens-models.feature +7 -1
  26. package/src/workflows/feature/naive-acceptance.feature +2 -2
  27. package/src/workflows/feature/one-step-land.feature +10 -12
  28. package/src/workflows/feature/quality-unresolved.feature +7 -1
  29. package/src/workflows/feature/risk-tier.feature +12 -4
  30. package/src/workflows/feature/seam-rejection.feature +4 -9
  31. package/src/workflows/feature/seam.feature +29 -26
  32. package/src/workflows/feature/seam.test.ts +9 -3
  33. package/src/workflows/feature/seam.ts +40 -22
  34. package/src/workflows/review/review-door.feature +12 -4
  35. package/src/workflows/shared/text.test.ts +8 -0
  36. package/src/workflows/shared/text.ts +11 -8
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtd",
3
- "version": "35.0.0",
3
+ "version": "36.0.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
@@ -430,18 +430,19 @@ you open the `review` door.
430
430
  3. **You wait.** The agent first implements the requirements the quickest way
431
431
  that works — the naive pass — and a judge reads where it changed code (see
432
432
  [the naive pass](#the-naive-pass) below; with [flag mode](#the-naive-pass)
433
- off, neither runs). Then the change is built test-first, checked against the
434
- full suite (`testCommand`) and fixed until it passes — so keep that suite
435
- fast; gtd has no lighter one. Scenario wording is not frozen: a review's
436
- `## Scenarios` chunk marks any assertion an existing scenario lost as
437
- `Weakened:` — **risk: `feature` has no review, so a build turn that weakens a
438
- scenario reaches `idle` unflagged unless you open the `review` door.** After
439
- the build the quality lap runs, and the process ends: the feature workflow
440
- has no human review of the finished change. Two points along the process (one
441
- with flag mode off) are judged rather than always asking you outright — each
442
- stops and hands you a verdict to make (`gtd judge answer`, or land with a
443
- clean tree to accept the conservative default, which never skips work;
444
- `gtd judge run --provider fixed --answers <path>` — or the
433
+ off, no judge runs). A pass the judge lets through is the implementation; one
434
+ whose seam is refactored first is built again test-first. Then it is checked
435
+ against the full suite (`testCommand`) and fixed until it passes — so keep
436
+ that suite fast; gtd has no lighter one. Scenario wording is not frozen: a
437
+ review's `## Scenarios` chunk marks any assertion an existing scenario lost
438
+ as `Weakened:` — **risk: `feature` has no review, so a build turn that
439
+ weakens a scenario reaches `idle` unflagged unless you open the `review`
440
+ door.** After the build the quality lap runs, and the process ends: the
441
+ feature workflow has no human review of the finished change. Two points along
442
+ the process (one with flag mode off) are judged rather than always asking you
443
+ outright — each stops and hands you a verdict to make (`gtd judge answer`, or
444
+ land with a clean tree to accept the conservative default, which never skips
445
+ work; `gtd judge run --provider fixed --answers <path>` — or the
445
446
  `GTD_JUDGE_ANSWERS` env var, inline JSON — answers one from a file, piped
446
447
  between `gtd judge --json` and `gtd judge answer`;
447
448
  `gtd judge run --provider jev` asks TypeSafe's Jev instead, with the key in
@@ -605,16 +606,18 @@ its ID (`[AC-3] …`) and reaches only code that existed before the pass. The pa
605
606
  is sent back while a criterion's ID is missing from its tests, a test imports a
606
607
  file it added, or the suite is red on it. A judge checks whether it changed
607
608
  anything outside the expected set (tests, steering files; see `seamAllow`).
608
- Incidental changes land; a needed seam change is refactored first, in its own
609
- slice with its own quality lap (`refactorQualityReviews`) and review, before the
610
- feature is implemented; a wrong approach is retried twice, then rests for you to
611
- steer or overrule it. `gtd slices` lists the `refactor` and `feature` slices. A
612
- refactor that already carried the whole feature leaves the implementation
613
- nothing to do: its empty turn passes, and the feature's quality lap covers the
614
- whole change. Once the implementation is green, a judge checks every criterion
615
- ID has a covering test: an uncovered one goes back to the agent (twice, then to
616
- you) instead of ending the feature, and each covered ID is recorded as a
617
- `Gtd-Mark: covers=<id>` trailer. See
609
+ Incidental changes land: the pass itself is the implementation, with no rebuild,
610
+ straight through the health loop and the quality lap. A needed seam change is
611
+ refactored first, in its own slice with its own quality lap
612
+ (`refactorQualityReviews`) and review, before the feature is implemented afresh;
613
+ a wrong approach is retried twice, then rests for you to steer or overrule it.
614
+ `gtd slices` lists the `refactor` and `feature` slices. A refactor that already
615
+ carried the whole feature leaves the implementation nothing to do: its empty
616
+ turn passes, and the feature's quality lap covers the whole change. Once the
617
+ implementation is green, a judge checks every criterion ID has a covering test:
618
+ an uncovered one goes back to the agent (twice, then to you) instead of ending
619
+ the feature, and each covered ID is recorded as a `Gtd-Mark: covers=<id>`
620
+ trailer. See
618
621
  [Acceptance-criteria coverage](docs/configuration.md#acceptance-criteria-coverage).
619
622
  Feedback at the refactor review re-runs the refactor, never design; a
620
623
  refactor-review change the refactor cannot build is refused, and the only way
@@ -623,12 +626,13 @@ out is `gtd abandon`. A process from 27.5.0 or later whose judge ruled
623
626
  that slice. Details in
624
627
  [the naive-pass path](docs/configuration.md#the-naive-pass-path).
625
628
 
626
- **Flag mode (`flagMode`, on by default).** On, the implementation is asked for a
627
- new release flag (none at a `low` risk tier) and the naive pass and its judge
628
- run as above. A project that is not publicly released turns it off explicitly —
629
+ **Flag mode (`flagMode`, on by default).** On, the naive pass's judge runs as
630
+ above and a rebuilt implementation is asked for a new release flag (none at a
631
+ `low` risk tier); a pass the judge lets land is not asked for one. A project
632
+ that is not publicly released turns it off explicitly —
629
633
  `feature({ flagMode: false })` in `gtd.config.ts`, it is never detected: then no
630
- flag is asked for, no seam judge or refactor runs, and the implementation is the
631
- naive version, straight through the health loop and the quality lap. See
634
+ flag is asked for, no seam judge or refactor runs, and the naive pass lands as
635
+ the implementation, straight through the health loop and the quality lap. See
632
636
  [`flagMode`](docs/configuration.md#the-bundled-workflows-settings).
633
637
 
634
638
  **Risk tier (flag `GTD_FLAG_RISK_TIER=1`, off by default).** Triage proposes a
@@ -391,6 +391,7 @@ const agentWithSkills = (name, prompt, options = {}) => {
391
391
  return agent(name, withSkills(skillsFor(name).join(", "), body), options);
392
392
  };
393
393
  const unwindFailure = (commit) => `gtd could not unwind ${commit} out of your working tree.`;
394
+ const naiveApplyFailure = (commit) => `gtd could not apply the naive pass ${commit} to the working tree; it is built fresh instead.`;
394
395
  const idleMessage = () => `No active gtd process.
395
396
 
396
397
  To start one, make ANY change — a hand-edit to real code, a scratch
@@ -1239,8 +1240,8 @@ const naivePassPrompt = (retry) => `${stateFileRules}
1239
1240
  - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
1240
1241
  ${criteriaBinding(false)}
1241
1242
  - Implement them the quickest way that works — the naive version. Do not
1242
- restructure, abstract or refactor around the change; a later step judges
1243
- the shape of your diff and a reviewer makes it fit
1243
+ restructure, abstract or refactor around the change; it may land exactly as
1244
+ you leave it, and a reviewer makes it fit
1244
1245
  - Work breadth-first: rough out every part of the change before finishing any
1245
1246
  one, so a run cut off midway still shows the whole shape
1246
1247
  - Write behaviour-level acceptance tests, at least one per criterion, each
@@ -1290,12 +1291,13 @@ An edit together with an approval counts as steer. Only
1290
1291
  \`.gtd/REQUIREMENTS.md\` is meant to change here: any other edit lands on the
1291
1292
  branch unjudged.
1292
1293
  `;
1293
- const seamSteerMessage = (retry) => isFault(retry) ? `The naive pass failed three times and the retries are spent. The last
1294
+ /** `judged`: whether an overruled pass still meets the seam judge (`flagMode` on). */
1295
+ const seamSteerMessage = (retry, judged = true) => isFault(retry) ? `The naive pass failed three times and the retries are spent. The last
1294
1296
  attempt's tests (\`git show ${retry.hash}\`) fall short:
1295
1297
  ${retry.fault}
1296
1298
 
1297
1299
  ${steerChoices(`land a clean tree to go on with the last attempt as it is:
1298
- \`seam.check\` runs its suite and the seam judge rules on its shape. Use it
1300
+ \`seam.check\` runs its suite and ${judged ? "the seam judge rules on its shape" : "it lands as the implementation"}. Use it
1299
1301
  when the rule cannot fit this change (tests the globs do not see, or no
1300
1302
  entry point that existed before). Risk: a criterion may have no test that
1301
1303
  survives a later refactor — the choice is recorded in history.`)}` : isRed(retry) ? `The naive pass failed three times and the retries are spent. The last
@@ -1306,7 +1308,7 @@ Two ways forward (then run \`gtd land\`):
1306
1308
  - **Steer** — edit \`.gtd/REQUIREMENTS.md\` to redirect the change; the naive
1307
1309
  pass runs again with a fresh retry budget
1308
1310
  - **Overrule** — land a clean tree to go on with the last attempt as it is:
1309
- the seam judge rules on its shape, and the implementation's health loop
1311
+ ${judged ? "the seam judge rules on its shape" : "it lands as the implementation"}, and the implementation's health loop
1310
1312
  must turn the suite green. Risk: the criteria's tests never passed on the
1311
1313
  naive pass — the choice is recorded in history.
1312
1314
 
@@ -1322,9 +1324,8 @@ Two ways forward (then run \`gtd land\`):
1322
1324
  - **Steer** — edit \`.gtd/REQUIREMENTS.md\` to redirect the change; the naive
1323
1325
  pass runs again with a fresh retry budget (the risk tier stays as the
1324
1326
  design gate settled it; a tier edit here is not applied)
1325
- - **Overrule** — land a clean tree to accept the last attempt's approach:
1326
- the implementation is built against it as reference, with no seam gate
1327
- and no refactor. Risk: this ships the seam the judge objected to,
1327
+ - **Overrule** — land a clean tree to accept the last attempt as it is: it
1328
+ lands as the implementation, with no seam gate and no refactor. Risk: this ships the seam the judge objected to,
1328
1329
  unaddressed — the choice is recorded in history.
1329
1330
 
1330
1331
  An edit together with an approval counts as steer. Only
@@ -1672,4 +1673,4 @@ const steering = {
1672
1673
  [REVIEW]: "review"
1673
1674
  };
1674
1675
  //#endregion
1675
- export { refactorBuildingPrompt as $, buildReviewCollectingPrompt as A, deferNotesScript as B, planner as C, agentWithSkills as D, reviewing as E, clearCriteriaQuestion as F, fixReviewNotesPrompt as G, designSystem as H, coverageMessage as I, implementBuildingPrompt as J, handOffAwaitReviewMessage as K, coverageQuestion as L, buildReviewTriageMessage as M, builderSystem as N, buildReviewAnswerQuestionsPrompt as O, clearCriteriaMessage as P, qualityUnresolvedMessage as Q, coveringPrompt as R, fixRisks as S, reviewQuality as T, designTriagePrompt as U, designGateAnswerMessage as V, finisherSystem as W, naivePassPrompt as X, implementFixPrompt as Y, normaliseCriteriaScript as Z, REVIEW as _, flagMentions as _t, flagModeOn as a, reviewGateBlockedMessage as at, fixCheck as b, registryEntry as bt, baseline as c, seamByHandMessage as ct, guarded as d, uncoveredMessage as dt, refactorDroppedRefusal as et, healthy as f, unwindFailedMessage as ft, REQUIREMENTS as g, testFileGlobs as gt, REFACTOR as h, envDefaults as ht, skills as i, reviewEditsCapture as it, buildReviewFixNitsPrompt as j, buildReviewAwaitAfterDeferMessage as k, escalation as l, seamSteerMessage as lt, QUALITY as m, defaults as mt, summary as n, refactorRules as nt, flagShipped as o, reviewNotesCapture as ot, FEEDBACK as p, unwindFailure as pt, idleMessage as q, access as r, reviewEditNotesCapture as rt, shipped as s, reviewerSystem as st, steering as t, refactorFixPrompt as tt, gate as u, startGateBlockedMessage as ut, awaitReview as v, flagReferences as vt, reviewMissing as w, fixQuality as x, requireFlag as xt, coder as y, flipProblems as yt, criterionLine as z };
1676
+ export { qualityUnresolvedMessage as $, buildReviewCollectingPrompt as A, deferNotesScript as B, planner as C, agentWithSkills as D, reviewing as E, clearCriteriaQuestion as F, fixReviewNotesPrompt as G, designSystem as H, coverageMessage as I, implementBuildingPrompt as J, handOffAwaitReviewMessage as K, coverageQuestion as L, buildReviewTriageMessage as M, builderSystem as N, buildReviewAnswerQuestionsPrompt as O, clearCriteriaMessage as P, normaliseCriteriaScript as Q, coveringPrompt as R, fixRisks as S, requireFlag as St, reviewQuality as T, designTriagePrompt as U, designGateAnswerMessage as V, finisherSystem as W, naiveApplyFailure as X, implementFixPrompt as Y, naivePassPrompt as Z, REVIEW as _, testFileGlobs as _t, flagModeOn as a, reviewEditsCapture as at, fixCheck as b, flipProblems as bt, baseline as c, reviewerSystem as ct, guarded as d, startGateBlockedMessage as dt, refactorBuildingPrompt as et, healthy as f, uncoveredMessage as ft, REQUIREMENTS as g, envDefaults as gt, REFACTOR as h, defaults as ht, skills as i, reviewEditNotesCapture as it, buildReviewFixNitsPrompt as j, buildReviewAwaitAfterDeferMessage as k, escalation as l, seamByHandMessage as lt, QUALITY as m, unwindFailure as mt, summary as n, refactorFixPrompt as nt, flagShipped as o, reviewGateBlockedMessage as ot, FEEDBACK as p, unwindFailedMessage as pt, idleMessage as q, access as r, refactorRules as rt, shipped as s, reviewNotesCapture as st, steering as t, refactorDroppedRefusal as tt, gate as u, seamSteerMessage as ut, awaitReview as v, flagMentions as vt, reviewMissing as w, fixQuality as x, registryEntry as xt, coder as y, flagReferences as yt, criterionLine as z };
@@ -1,6 +1,6 @@
1
1
  import { read, refuse, removeScript, run, scope, showScript, vars } from "../flows.mjs";
2
2
  import { fixFindings, lensEntriesOf, lensOf, qualityLap as qualityLap$1, reviewToHandOff } from "../workflow-kit.mjs";
3
- import { E as reviewing, K as handOffAwaitReviewMessage, S as fixRisks, T as reviewQuality, _ as REVIEW, b as fixCheck, d as guarded, f as healthy, l as escalation, m as QUALITY, v as awaitReview, w as reviewMissing, x as fixQuality } from "./reserved-C8Fm3s72.mjs";
3
+ import { E as reviewing, K as handOffAwaitReviewMessage, S as fixRisks, T as reviewQuality, _ as REVIEW, b as fixCheck, d as guarded, f as healthy, l as escalation, m as QUALITY, v as awaitReview, w as reviewMissing, x as fixQuality } from "./reserved-BAv71I4i.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";
package/dist/flows.mjs CHANGED
@@ -226,25 +226,21 @@ const checkScript = (command, options) => {
226
226
  ""
227
227
  ].join("\n");
228
228
  };
229
- /**
230
- * Reverse-apply `commit`'s changes to the working tree. A failure — the
231
- * patch no longer applies — writes `failure` and git's error to `report`,
232
- * since a failed revert and one with nothing to undo both leave the tree clean.
233
- */
234
- const revertScript = (commit, report, failure) => {
229
+ const patchScript = (commit, report, failure, reverse, exclude = []) => {
230
+ const paths = exclude.length === 0 ? "" : ` . ${exclude.map((p) => quote(`:(exclude,literal)${p}`)).join(" ")}`;
235
231
  const range = `${quote(`${commit}^`)} ${quote(commit)}`;
236
232
  const error = quote(`${report}.output`);
237
233
  return [
238
234
  "#!/usr/bin/env sh",
239
235
  "set +e",
240
- `if ! git diff --quiet ${range} --; then`,
236
+ `if ! git diff --quiet ${range} --${paths}; then`,
241
237
  ` mkdir -p "$(dirname ${quote(report)})"`,
242
- ` git diff --binary ${range} -- | git apply -R 2> ${error}`,
238
+ ` git diff --binary --no-color --no-ext-diff --no-textconv --src-prefix=a/ --dst-prefix=b/ ${range} --${paths} | git apply --whitespace=nowarn${reverse ? " -R" : ""} 2> ${error}`,
243
239
  " code=$?",
244
240
  " if [ \"$code\" -ne 0 ]; then",
245
241
  " {",
246
242
  ` printf '%s\\n\\n' ${quote(failure)}`,
247
- ` if [ -s ${error} ]; then cat ${error}; else printf 'Reverting it exited %s and produced no output.\\n' "$code"; fi`,
243
+ ` if [ -s ${error} ]; then cat ${error}; else printf '${reverse ? "Reverting" : "Applying"} it exited %s and produced no output.\\n' "$code"; fi`,
248
244
  ` } > ${quote(report)}`,
249
245
  " fi",
250
246
  ` rm -f ${error}`,
@@ -253,6 +249,14 @@ const revertScript = (commit, report, failure) => {
253
249
  ].join("\n");
254
250
  };
255
251
  /**
252
+ * Reverse-apply `commit`'s changes to the working tree. A failure — the
253
+ * patch no longer applies — writes `failure` and git's error to `report`,
254
+ * since a failed revert and one with nothing to undo both leave the tree clean.
255
+ */
256
+ const revertScript = (commit, report, failure) => patchScript(commit, report, failure, true);
257
+ /** Apply `commit`'s changes outside `exclude` to the working tree; a failure is reported as `revertScript`'s is. */
258
+ const applyScript = (commit, report, failure, exclude = []) => patchScript(commit, report, failure, false, exclude);
259
+ /**
256
260
  * Undo the edits one commit made to `paths`: each goes back to its content
257
261
  * before `commit` — or away, when `commit` added it — but only while it is
258
262
  * still exactly as `commit` left it. A path changed since is left alone.
@@ -620,6 +624,7 @@ var flows_exports = /* @__PURE__ */ __exportAll({
620
624
  accessFor: () => accessFor,
621
625
  agent: () => agent,
622
626
  answered: () => answered,
627
+ applyScript: () => applyScript,
623
628
  changes: () => changes,
624
629
  changesSince: () => changesSince,
625
630
  check: () => check,
@@ -673,4 +678,4 @@ var flows_exports = /* @__PURE__ */ __exportAll({
673
678
  wrote: () => wrote
674
679
  });
675
680
  //#endregion
676
- 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, mark, matches, moveScript, numeric, openQuestions, protocolMismatch, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restart, restoreScript, revertScript, run, scope, seamGate, sectionBodies, sections, showScript, skillsFor, slice, start, flows_exports as t, threads, vars, wrote };
681
+ export { FLOWS_PROTOCOL, FlowsProtocolError, accessFor, agent, answered, applyScript, changes, changesSince, check, checkScript, codeChanges, codeThreads, configProblems, configure, defineConfig, defineWorkflow, displayPath, env, glob, hasThreadFor, head, human, installContext, isFactoryWorkflow, judge, judgeDiffShape, kept, mark, matches, moveScript, numeric, openQuestions, protocolMismatch, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restart, restoreScript, revertScript, run, scope, seamGate, sectionBodies, sections, showScript, skillsFor, slice, start, flows_exports as t, threads, vars, wrote };
@@ -21865,25 +21865,21 @@ const checkScript = (command, options) => {
21865
21865
  ""
21866
21866
  ].join("\n");
21867
21867
  };
21868
- /**
21869
- * Reverse-apply `commit`'s changes to the working tree. A failure — the
21870
- * patch no longer applies — writes `failure` and git's error to `report`,
21871
- * since a failed revert and one with nothing to undo both leave the tree clean.
21872
- */
21873
- const revertScript = (commit, report, failure) => {
21868
+ const patchScript = (commit, report, failure, reverse, exclude = []) => {
21869
+ const paths = exclude.length === 0 ? "" : ` . ${exclude.map((p) => quote(`:(exclude,literal)${p}`)).join(" ")}`;
21874
21870
  const range = `${quote(`${commit}^`)} ${quote(commit)}`;
21875
21871
  const error = quote(`${report}.output`);
21876
21872
  return [
21877
21873
  "#!/usr/bin/env sh",
21878
21874
  "set +e",
21879
- `if ! git diff --quiet ${range} --; then`,
21875
+ `if ! git diff --quiet ${range} --${paths}; then`,
21880
21876
  ` mkdir -p "$(dirname ${quote(report)})"`,
21881
- ` git diff --binary ${range} -- | git apply -R 2> ${error}`,
21877
+ ` git diff --binary --no-color --no-ext-diff --no-textconv --src-prefix=a/ --dst-prefix=b/ ${range} --${paths} | git apply --whitespace=nowarn${reverse ? " -R" : ""} 2> ${error}`,
21882
21878
  " code=$?",
21883
21879
  " if [ \"$code\" -ne 0 ]; then",
21884
21880
  " {",
21885
21881
  ` printf '%s\\n\\n' ${quote(failure)}`,
21886
- ` if [ -s ${error} ]; then cat ${error}; else printf 'Reverting it exited %s and produced no output.\\n' "$code"; fi`,
21882
+ ` if [ -s ${error} ]; then cat ${error}; else printf '${reverse ? "Reverting" : "Applying"} it exited %s and produced no output.\\n' "$code"; fi`,
21887
21883
  ` } > ${quote(report)}`,
21888
21884
  " fi",
21889
21885
  ` rm -f ${error}`,
@@ -21892,6 +21888,14 @@ const revertScript = (commit, report, failure) => {
21892
21888
  ].join("\n");
21893
21889
  };
21894
21890
  /**
21891
+ * Reverse-apply `commit`'s changes to the working tree. A failure — the
21892
+ * patch no longer applies — writes `failure` and git's error to `report`,
21893
+ * since a failed revert and one with nothing to undo both leave the tree clean.
21894
+ */
21895
+ const revertScript = (commit, report, failure) => patchScript(commit, report, failure, true);
21896
+ /** Apply `commit`'s changes outside `exclude` to the working tree; a failure is reported as `revertScript`'s is. */
21897
+ const applyScript = (commit, report, failure, exclude = []) => patchScript(commit, report, failure, false, exclude);
21898
+ /**
21895
21899
  * Undo the edits one commit made to `paths`: each goes back to its content
21896
21900
  * before `commit` — or away, when `commit` added it — but only while it is
21897
21901
  * still exactly as `commit` left it. A path changed since is left alone.
@@ -22259,6 +22263,7 @@ var flows_exports = /* @__PURE__ */ __exportAll({
22259
22263
  accessFor: () => accessFor,
22260
22264
  agent: () => agent,
22261
22265
  answered: () => answered,
22266
+ applyScript: () => applyScript,
22262
22267
  changes: () => changes,
22263
22268
  changesSince: () => changesSince,
22264
22269
  check: () => check,
package/dist/testkit.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { checkScript, configProblems, configure, installContext, isFactoryWorkflow, moveScript, protocolMismatch, removeScript, restoreScript, revertScript, showScript, t as flows_exports } from "./flows.mjs";
1
+ import { applyScript, checkScript, configProblems, configure, installContext, isFactoryWorkflow, moveScript, protocolMismatch, removeScript, restoreScript, revertScript, showScript, t as flows_exports } from "./flows.mjs";
2
2
  import { O as ACCESS_REFUSAL, a as selectPath, d as renderLandPlain, g as statusOf, h as renderBeatJson, i as resolveSession, j as renderFormat, l as noopText, m as beatDocument, n as slicesDocument, o as judgeJson, p as renderBeatPlain, r as globMatches, s as landFields, t as renderSlicesJson, u as renderLandJson, v as beatKindOf, y as demandOf } from "./chunks/wire-D3Rc5r22.mjs";
3
3
  import { C as headingSections, S as headingSectionBodies, a as unansweredQuestions, b as parseThreadsWithFindings, n as openThreads, r as steeringFormatFor, s as parseCodeThreads, t as BUILT_IN_MODE_NAMES, w as formatFinding } from "./chunks/steering-DVILanVf.mjs";
4
4
  import { $ as logWarning, $i as NodeInspectSymbol, $n as map$5, $r as make$19, $t as provide$1, A as catchIf, Ai as add, An as scopeMake, Ar as flatMap$3, At as suspend$5, B as flatMap$4, Bi as getOrElse, Bn as get$3, Br as sync$1, Bt as unsafeMakeLatch, C as all$1, Ci as toArray, Cn as isInterrupted, Cr as exitFail, Ct as sandbox, D as async, Di as GenericTag, Dn as squash, Dr as exitZipRight, Dt as scopedWith$1, E as asVoid$1, Ei as unsafeHead, En as reduceWithContext, Er as exitVoid, Et as scoped$4, F as ensuring$2, Fi as fromIterable, Fn as join$3, Fr as map$3, Ft as tryMap, G as forkIn, Gi as none, Gn as TypeId$10, Gr as whenEffect, Gt as zipLeft, H as fnUntraced, Hi as isSome, Hn as modify$1, Hr as uninterruptible, Ht as when, I as exit, Ii as splitAt, In as sequential, Ir as onInterrupt, It as tryPromise, J as ignore, Ji as left, Jn as all, Jr as DeferredTypeId$1, Jt as spaced$1, K as forkScoped, Ki as some, Kn as Class, Kr as withFiberRuntime, Kt as zipRight$3, L as fail$5, Li as filter$1, Ln as bind, Lr as scopeClose, Lt as try_, M as die$2, Mi as getOption, Mn as _await$2, Mr as interruptAsFiber, Mt as tap, N as dieMessage, Ni as omit, Nn as inheritAll$1, Nr as interruptFiber, Nt as tapErrorCause, O as catchAll$1, Oi as Reference, On as ensuring$3, Or as fiberId, Ot as serviceOption, P as either, Pi as empty$5, Pn as interruptAllAs$1, Pr as interruptible$1, Pt as timeout, Q as isEffect, Qi as BaseProto, Qn as isFailure, Qr as ids, Qt as mergeAll$2, R as failCause$4, Ri as flatMap$5, Rn as bindTo, Rr as succeed$4, Rt as uninterruptible$1, S as addFinalizer$1, Si as takeRight, Sn as isDieType, Sr as exitAs, St as runtime, T as as, Ti as unsafeGet, Tn as map$6, Tr as exitSucceed, Tt as scopeWith, U as forEach$2, Ui as map$8, Un as set$1, Ur as uninterruptibleMask$1, Ut as withFiberRuntime$1, V as flatten$4, Vi as isNone, Vn as make$16, Vr as tap$1, Vt as unsafeMakeSemaphore$1, W as forkDaemon, Wi as match$4, Wn as update$1, Wr as void_$2, Wt as zip$1, X as interruptible, Xi as right, Xn as fail$4, Xr as combine, Xt as tagged, Y as interrupt$4, Yi as match$2, Yn as die, Yr as empty$6, Yt as TaggedError$1, Z as intoDeferred, Zi as pipeArguments, Zn as failCause$3, Zr as combineAll, Zt as fromEffect$5, _ as decodeSync, _i as of, _n as fail$3, _r as deferredIsDone, _t as raceWith, a as Literal, aa as constTrue, ai as size$2, an as updateContext$1, ar as zipRight$2, at as matchCause, b as acquireRelease, bi as reverse, bn as interrupt$3, br as deferredUnsafeDone, bt as runPromise, c as String$, ca as dual, ci as append$1, cn as Scope, cr as asyncInterrupt, ct as negate, d as Union, di as empty$4, dn as close, dr as deferredAwait, dt as orElse, ea as format, ei as none$1, en as provideMerge$1, er as mapBoth, et as makeSemaphore, f as between, fi as filter, fn as extend, fr as deferredComplete, ft as orElseSucceed, gi as map$7, gn as die$1, gr as deferredInterruptWith, gt as race, hi as isEmpty$2, hn as NoSuchElementException, hr as deferredInterrupt, ht as provideService, i as Defect, ia as constFalse, ii as has, in as unsafeRunPromiseExit, ir as zip$2, it as match$5, j as catchTag, ji as get$5, jn as scopeTag, jr as interrupt$2, jt as sync$2, k as catchAllCause, ki as Tag, kn as forEachConcurrentDiscard, kr as fiberRefLocally, kt as succeed$6, l as Struct, la as identity, li as appendAll, ln as addFinalizer, lr as catchAll, lt as onError, m as optional$1, mi as isChunk, mn as make$18, mr as deferredFailCause, mt as provide$2, n as Boolean$, na as equals, ni as make$17, nn as succeed$3, nr as succeed$5, nt as mapBoth$1, o as Never, oa as constUndefined, oi as decode, on as forkIn$1, or as as$1, ot as matchCauseEffect, p as decodeUnknown, pi as fromIterable$1, pn as fork, pr as deferredFail, pt as promise, q as gen, qi as isLeft, qn as spanTag, qr as zipRight$1, qt as zipWith, r as Cause, ra as hasProperty, ri as set$2, rn as unsafeFork, rr as void_$3, rt as mapError, s as Number$, sa as constVoid, si as isFinite, sn as unsafeMakeSemaphore, sr as asVoid, st as matchEffect, t as Array$, ta as toJSON, ti as get$4, tn as scoped$3, tr as match$3, tt as map$4, u as TaggedError, ua as pipe, ui as drop, un as addFinalizerExit, ur as currentContext$1, ut as orDie, v as encodeSync, vi as prepend, vn as failureOrCause, vr as deferredMake, vt as repeat, w as annotateLogs, wi as unsafeFromArray, wn as isInterruptedOnly, wr as exitFailCause, wt as scope, x as acquireUseRelease$2, xi as take$2, xn as interruptors, xr as deferredUnsafeMake, xt as runPromiseExit$1, y as _void, yi as reduce, yn as flipCauseOption, yr as deferredSucceed, yt as retry, z as fiberIdWith, zi as fromNullable, zn as sleep, zr as suspend$4, zt as uninterruptibleMask } from "./chunks/Schema-DO5ttJaI.mjs";
@@ -12774,37 +12774,57 @@ const recognizeRestore = (repo, script) => {
12774
12774
  return ok();
12775
12775
  };
12776
12776
  const PATCH_FAILED = "error: patch does not apply";
12777
- /**
12778
- * `git diff C^ C | git apply -R`, which is all or nothing. A file still
12779
- * exactly as `C` left it reverts; a file gone, or one `C` added and that
12780
- * changed since, fails the whole patch. A file `C` modified and that changed
12781
- * since may still apply through its context — that needs real git, so it
12782
- * throws rather than guess.
12783
- */
12784
- const reverseApply = (repo, commit) => {
12777
+ const outside = (path, exclude) => !exclude.some((e) => path === e || path.startsWith(`${e}/`));
12778
+ /** Why `path` blocks the patch, `undefined` when it is exactly as `from` has it. */
12779
+ const blocking = (repo, path, from, to) => {
12780
+ const expected = repo.fileAtRef(from, path);
12781
+ const current = repo.readFile(path) ?? null;
12782
+ if (current === expected) return void 0;
12783
+ if (current === null || expected === null || repo.fileAtRef(to, path) === null) return `${PATCH_FAILED}: ${path}`;
12784
+ throw new Error(`testkit: cannot tell in memory whether the patch of ${from}..${to} applies to the hand-edited ${path} — run this @live`);
12785
+ };
12786
+ /**
12787
+ * `git diff C^ C | git apply [-R]`, which is all or nothing. A file exactly
12788
+ * as the side being undone (`from`) has it moves to the other side (`to`); a
12789
+ * file missing where `from` has one, or present where `from` has none, fails
12790
+ * the whole patch. A file both sides have that differs from `from` may still
12791
+ * apply through its context — that needs real git, so `blocking` throws
12792
+ * rather than guess.
12793
+ */
12794
+ const patch = (repo, commit, reverse, exclude) => {
12785
12795
  const parent = `${commit}^`;
12786
- const changed = [.../* @__PURE__ */ new Set([...repo.pathsAtRef(parent), ...repo.pathsAtRef(commit)])].filter((p) => repo.fileAtRef(parent, p) !== repo.fileAtRef(commit, p));
12796
+ const [from, to] = reverse ? [commit, parent] : [parent, commit];
12797
+ const changed = [.../* @__PURE__ */ new Set([...repo.pathsAtRef(parent), ...repo.pathsAtRef(commit)])].filter((p) => repo.fileAtRef(parent, p) !== repo.fileAtRef(commit, p) && outside(p, exclude));
12787
12798
  for (const path of changed) {
12788
- const after = repo.fileAtRef(commit, path);
12789
- const current = repo.readFile(path) ?? null;
12790
- if (current === after) continue;
12791
- if (current === null || after === null || repo.fileAtRef(parent, path) === null) return `${PATCH_FAILED}: ${path}`;
12792
- throw new Error(`testkit: cannot tell in memory whether reverting ${commit} applies to the hand-edited ${path} — run this @live`);
12799
+ const error = blocking(repo, path, from, to);
12800
+ if (error !== void 0) return error;
12793
12801
  }
12794
12802
  for (const path of changed) {
12795
- const before = repo.fileAtRef(parent, path);
12796
- if (before === null) repo.deleteFile(path);
12797
- else repo.writeFile(path, before);
12803
+ const content = repo.fileAtRef(to, path);
12804
+ if (content === null) repo.deleteFile(path);
12805
+ else repo.writeFile(path, content);
12798
12806
  }
12799
12807
  };
12800
- const recognizeRevert = (repo, script) => {
12808
+ const parsePatch = (script) => {
12801
12809
  const lines = script.split("\n");
12802
- const [, commit] = words$1(lines.find((l) => l.startsWith("if ! git diff --quiet ")) ?? "");
12810
+ const [, commit, ...pathspecs] = words$1(lines.find((l) => l.startsWith("if ! git diff --quiet ")) ?? "");
12803
12811
  const [, failure] = words$1(lines.find((l) => l.includes("printf '%s\\n\\n' ")) ?? "");
12804
12812
  const [report] = words$1(lines.find((l) => l.startsWith(" } > ")) ?? "");
12805
12813
  if (commit === void 0 || failure === void 0 || report === void 0) return void 0;
12806
- if (revertScript(commit, report, failure) !== script) return void 0;
12807
- const error = reverseApply(repo, commit);
12814
+ return {
12815
+ commit,
12816
+ failure,
12817
+ report,
12818
+ exclude: pathspecs.map((p) => p.replace(/^:\(exclude,literal\)/, ""))
12819
+ };
12820
+ };
12821
+ const recognizePatch = (repo, script) => {
12822
+ const parsed = parsePatch(script);
12823
+ if (parsed === void 0) return void 0;
12824
+ const { commit, failure, report, exclude } = parsed;
12825
+ const reverse = revertScript(commit, report, failure) === script;
12826
+ if (!reverse && applyScript(commit, report, failure, exclude) !== script) return void 0;
12827
+ const error = patch(repo, commit, reverse, reverse ? [] : exclude);
12808
12828
  if (error !== void 0) repo.writeFile(report, `${failure}\n\n${error}\n`);
12809
12829
  return ok();
12810
12830
  };
@@ -12935,7 +12955,7 @@ const recognizeCheck = (repo, commands, script) => {
12935
12955
  */
12936
12956
  const applyFlowScript = (repo, commands, script) => {
12937
12957
  if (!script.startsWith(`${SHEBANG}\n`)) return void 0;
12938
- return recognizeRemove(repo, script) ?? recognizeMove(repo, script) ?? recognizeShow(repo, script) ?? recognizeRevert(repo, script) ?? recognizeRestore(repo, script) ?? recognizeCheck(repo, commands, script);
12958
+ return recognizeRemove(repo, script) ?? recognizeMove(repo, script) ?? recognizeShow(repo, script) ?? recognizePatch(repo, script) ?? recognizeRestore(repo, script) ?? recognizeCheck(repo, commands, script);
12939
12959
  };
12940
12960
  //#endregion
12941
12961
  //#region src/testkit/EmittedScriptRecognizer.ts
@@ -1,6 +1,6 @@
1
1
  import { agent, changes, glob, read, refuse, scope, start, vars } from "../flows.mjs";
2
- import { bt as registryEntry, ht as envDefaults, i as skills, mt as defaults, n as summary, o as flagShipped, r as access, t as steering, vt as flagReferences, xt as requireFlag, y as coder } from "../chunks/reserved-C8Fm3s72.mjs";
3
- import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-oaYewBN_.mjs";
2
+ import { St as requireFlag, gt as envDefaults, ht as defaults, i as skills, n as summary, o as flagShipped, r as access, t as steering, xt as registryEntry, y as coder, yt as flagReferences } from "../chunks/reserved-BAv71I4i.mjs";
3
+ import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-kar1zhOb.mjs";
4
4
  //#region src/workflows/cleanup/cleanup.ts
5
5
  const removingPrompt = (name, registry) => `Clean up the release flag "${name}", which is default-on: delete its entry in ${registry}, and remove every \`GTD_FLAG_${name.toUpperCase()}\` check from the shipped sources. Keep the flag-on path; delete code only the off path reached, including tests that pin the off path.`;
6
6
  const cleanupFlow = async () => {
@@ -1,7 +1,7 @@
1
- import { answered, changes, changesSince, check, env, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, matches, numeric, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restoreScript, revertScript, run, scope, slice, start, vars } from "../flows.mjs";
1
+ import { answered, applyScript, changes, changesSince, check, codeChanges, env, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, matches, numeric, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restoreScript, revertScript, run, scope, slice, start, vars, wrote } from "../flows.mjs";
2
2
  import { lensEntriesOf, review as review$1 } from "../workflow-kit.mjs";
3
- import { $ as refactorBuildingPrompt, A as buildReviewCollectingPrompt, B as deferNotesScript, C as planner, D as agentWithSkills, E as reviewing, F as clearCriteriaQuestion, H as designSystem, I as coverageMessage, J as implementBuildingPrompt, L as coverageQuestion, M as buildReviewTriageMessage, N as builderSystem, O as buildReviewAnswerQuestionsPrompt, P as clearCriteriaMessage, Q as qualityUnresolvedMessage, R as coveringPrompt, S as fixRisks, U as designTriagePrompt, V as designGateAnswerMessage, X as naivePassPrompt, Y as implementFixPrompt, Z as normaliseCriteriaScript, _ as REVIEW, a as flagModeOn, b as fixCheck, ct as seamByHandMessage, d as guarded, dt as uncoveredMessage, et as refactorDroppedRefusal, f as healthy, ft as unwindFailedMessage, g as REQUIREMENTS, gt as testFileGlobs, h as REFACTOR, ht as envDefaults, i as skills, it as reviewEditsCapture, j as buildReviewFixNitsPrompt, k as buildReviewAwaitAfterDeferMessage, lt as seamSteerMessage, m as QUALITY, mt as defaults, n as summary, nt as refactorRules, ot as reviewNotesCapture, p as FEEDBACK, pt as unwindFailure, q as idleMessage, r as access, rt as reviewEditNotesCapture, s as shipped, st as reviewerSystem, t as steering, tt as refactorFixPrompt, u as gate, ut as startGateBlockedMessage, v as awaitReview, w as reviewMissing, y as coder, z as criterionLine } from "../chunks/reserved-C8Fm3s72.mjs";
4
- import { a as refusePendingHandOff, i as isOptionalObject, n as closeReview, o as tail } from "../chunks/tail-oaYewBN_.mjs";
3
+ import { $ as qualityUnresolvedMessage, A as buildReviewCollectingPrompt, B as deferNotesScript, C as planner, D as agentWithSkills, E as reviewing, F as clearCriteriaQuestion, H as designSystem, I as coverageMessage, J as implementBuildingPrompt, L as coverageQuestion, M as buildReviewTriageMessage, N as builderSystem, O as buildReviewAnswerQuestionsPrompt, P as clearCriteriaMessage, Q as normaliseCriteriaScript, R as coveringPrompt, S as fixRisks, U as designTriagePrompt, V as designGateAnswerMessage, X as naiveApplyFailure, Y as implementFixPrompt, Z as naivePassPrompt, _ as REVIEW, _t as testFileGlobs, a as flagModeOn, at as reviewEditsCapture, b as fixCheck, ct as reviewerSystem, d as guarded, dt as startGateBlockedMessage, et as refactorBuildingPrompt, f as healthy, ft as uncoveredMessage, g as REQUIREMENTS, gt as envDefaults, h as REFACTOR, ht as defaults, i as skills, it as reviewEditNotesCapture, j as buildReviewFixNitsPrompt, k as buildReviewAwaitAfterDeferMessage, lt as seamByHandMessage, m as QUALITY, mt as unwindFailure, n as summary, nt as refactorFixPrompt, p as FEEDBACK, pt as unwindFailedMessage, q as idleMessage, r as access, rt as refactorRules, s as shipped, st as reviewNotesCapture, t as steering, tt as refactorDroppedRefusal, u as gate, ut as seamSteerMessage, v as awaitReview, w as reviewMissing, y as coder, z as criterionLine } from "../chunks/reserved-BAv71I4i.mjs";
4
+ import { a as refusePendingHandOff, i as isOptionalObject, n as closeReview, o as tail } from "../chunks/tail-kar1zhOb.mjs";
5
5
  import { posix } from "node:path";
6
6
  //#region src/workflows/shared/fences.ts
7
7
  const markFenced = (text) => {
@@ -883,7 +883,7 @@ const naiveGreen = (tip) => {
883
883
  return check("check", [
884
884
  ...paths,
885
885
  "dirty=$(git -c core.quotePath=false ls-files -m -d -o --exclude-standard)",
886
- `if ! git diff --binary ${quote(`${tip.hash}^`)} ${quote(tip.hash)} -- | git apply; then`,
886
+ `if ! git diff --binary --no-color --no-ext-diff --no-textconv --src-prefix=a/ --dst-prefix=b/ ${quote(`${tip.hash}^`)} ${quote(tip.hash)} -- | git apply --whitespace=nowarn; then`,
887
887
  " echo 'gtd: the kept naive pass no longer applies to HEAD; no test ran'",
888
888
  " exit 1",
889
889
  "fi",
@@ -902,6 +902,11 @@ const naiveGreen = (tip) => {
902
902
  label: "Running the naive pass's tests"
903
903
  });
904
904
  };
905
+ /** Resolves `false` when the tip no longer applies (the failure is in `.gtd/FEEDBACK.md`) or has nothing outside `.gtd/` to apply. */
906
+ const applied = async (tip) => {
907
+ await run("applying", applyScript(tip, FEEDBACK, naiveApplyFailure(tip), [".gtd"]), { label: "Applying the naive pass" });
908
+ return !wrote(".gtd/FEEDBACK.md") && codeChanges().length > 0;
909
+ };
905
910
  function implement(first, tier, amend) {
906
911
  const options = typeof first === "object" && first !== null ? first : {
907
912
  reference: first,
@@ -909,7 +914,7 @@ function implement(first, tier, amend) {
909
914
  amend
910
915
  };
911
916
  return scope("implement", async () => {
912
- await agentWithSkills("building", implementBuildingPrompt(options.reference, options.tier, options.amend, options.afterRefactor, flagModeOn()), {
917
+ if (options.naive === void 0 || !await applied(options.naive)) await agentWithSkills("building", implementBuildingPrompt(options.reference, options.tier, options.amend, options.afterRefactor, flagModeOn()), {
913
918
  label: "Implementing",
914
919
  model: coder(),
915
920
  system: builderSystem(),
@@ -924,7 +929,7 @@ function implement(first, tier, amend) {
924
929
  /** Resolves `true` when the human steered (edited), `false` on a clean overrule. */
925
930
  const steer = async (retry) => {
926
931
  await human("steer", {
927
- message: seamSteerMessage(retry),
932
+ message: seamSteerMessage(retry, flagModeOn()),
928
933
  label: "Steer or overrule the naive pass",
929
934
  file: REQUIREMENTS,
930
935
  acceptClean: true
@@ -988,7 +993,7 @@ const decide = () => scope("seam", async () => {
988
993
  for (;;) {
989
994
  await naivePass(retry);
990
995
  const tip = kept("naive");
991
- const outcome = await vetted(tip, vetting) ?? await ruled(tip, vetting.again);
996
+ const outcome = await vetted(tip, vetting) ?? (flagModeOn() ? await ruled(tip, vetting.again) : { reference: tip.hash });
992
997
  if (!("kind" in outcome)) return outcome;
993
998
  retry = outcome.kind === "retry" ? outcome.retry : void 0;
994
999
  }
@@ -997,7 +1002,8 @@ const seamBuild = async (base) => {
997
1002
  const { reference, shape } = await decide();
998
1003
  if (shape === void 0) return {
999
1004
  reference,
1000
- base
1005
+ base,
1006
+ land: true
1001
1007
  };
1002
1008
  if (shape.unreadable.length > 0) {
1003
1009
  await scope("seam", () => human("by-hand", {
@@ -1028,15 +1034,16 @@ const unwind = () => {
1028
1034
  const commit = head();
1029
1035
  return run("unwind", revertScript(commit, FEEDBACK, unwindFailure(commit)), { label: "Unwinding your input" });
1030
1036
  };
1031
- /** A judged naive pass (none with `flagMode` off), then implement and the quality lap; design must have run since `firstBase`. */
1037
+ /** A naive pass (judged only with `flagMode` on), then implement and the quality lap; design must have run since `firstBase`. */
1032
1038
  const buildFeature = async (firstBase) => {
1033
1039
  const tier = riskTierOn() ? recordRisk() : void 0;
1034
- const seam = flagModeOn() ? await seamBuild(firstBase) : { base: firstBase };
1040
+ const seam = await seamBuild(firstBase);
1035
1041
  await slice("feature", async () => {
1036
1042
  await implement({
1037
1043
  reference: seam.reference,
1038
1044
  tier,
1039
- afterRefactor: seam.refactored
1045
+ afterRefactor: seam.refactored,
1046
+ naive: seam.land === true ? seam.reference : void 0
1040
1047
  });
1041
1048
  const base = seam.refactored === true && !changesSince(seam.base).some((c) => isCode$1(c.path)) ? firstBase : seam.base;
1042
1049
  await tail(settleQuality, {
@@ -1,7 +1,7 @@
1
1
  import { read, refuse, removeScript, run, scope, start } from "../flows.mjs";
2
2
  import { d as reviewNotes, f as reviewRisks } from "../chunks/steering-DVILanVf.mjs";
3
- import { D as agentWithSkills, G as fixReviewNotesPrompt, W as finisherSystem, _ as REVIEW, c as baseline, ht as envDefaults, i as skills, mt as defaults, n as summary, r as access, s as shipped, t as steering, y as coder } from "../chunks/reserved-C8Fm3s72.mjs";
4
- import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-oaYewBN_.mjs";
3
+ import { D as agentWithSkills, G as fixReviewNotesPrompt, W as finisherSystem, _ as REVIEW, c as baseline, gt as envDefaults, ht as defaults, i as skills, n as summary, r as access, s as shipped, t as steering, y as coder } from "../chunks/reserved-BAv71I4i.mjs";
4
+ import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-kar1zhOb.mjs";
5
5
  //#region src/workflows/fix/fix.ts
6
6
  /**
7
7
  * Fix every note a human left in a handed-off `.gtd/REVIEW.md` and every
@@ -1,6 +1,6 @@
1
1
  import { start } from "../flows.mjs";
2
- import { at as reviewGateBlockedMessage, ht as envDefaults, i as skills, mt as defaults, n as summary, r as access, s as shipped, t as steering, u as gate } from "../chunks/reserved-C8Fm3s72.mjs";
3
- import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-oaYewBN_.mjs";
2
+ import { gt as envDefaults, ht as defaults, i as skills, n as summary, ot as reviewGateBlockedMessage, r as access, s as shipped, t as steering, u as gate } from "../chunks/reserved-BAv71I4i.mjs";
3
+ import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-kar1zhOb.mjs";
4
4
  //#region src/workflows/review/review.ts
5
5
  /**
6
6
  * Pure review of everything since `reviewBase`: a green gate, the quality
@@ -1,5 +1,5 @@
1
1
  import { agent, changesSince, glob, head, mark, read, refuse, removeScript, run, scope, vars } from "../flows.mjs";
2
- import { _t as flagMentions, ht as envDefaults, i as skills, mt as defaults, n as summary, o as flagShipped, p as FEEDBACK, r as access, t as steering, u as gate, xt as requireFlag, y as coder, yt as flipProblems } from "../chunks/reserved-C8Fm3s72.mjs";
2
+ import { St as requireFlag, bt as flipProblems, gt as envDefaults, ht as defaults, i as skills, n as summary, o as flagShipped, p as FEEDBACK, r as access, t as steering, u as gate, vt as flagMentions, y as coder } from "../chunks/reserved-BAv71I4i.mjs";
3
3
  //#region src/workflows/rollout/rollout.ts
4
4
  const flippingPrompt = (name, registry) => `Roll out the release flag "${name}": in ${registry}, set \`defaultOn: true\` on its entry. Change nothing else — no other entry, no other field, no other file, no comments.`;
5
5
  const suiteBlockedMessage = () => "The suite is red after the rollout. Fix it by hand — a rollout may only touch the flag's registry entry — then land.";