@pmelab/gtd 35.0.0 → 37.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 (69) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +45 -29
  3. package/claude/hooks/context.ts +66 -0
  4. package/claude/hooks/drive.ts +3 -0
  5. package/claude/hooks/register.tsx +69 -6
  6. package/dist/chunks/{reserved-C8Fm3s72.mjs → reserved-zAfoJtR7.mjs} +62 -24
  7. package/dist/chunks/{tail-oaYewBN_.mjs → tail-BRC4DGdV.mjs} +1 -1
  8. package/dist/chunks/{wire-D3Rc5r22.mjs → wire-CSsa3Wxd.mjs} +117 -2
  9. package/dist/flows.mjs +15 -10
  10. package/dist/gtd.bundle.mjs +511 -64
  11. package/dist/testkit.mjs +158 -26
  12. package/dist/wire.mjs +2 -2
  13. package/dist/workflows/cleanup.mjs +2 -2
  14. package/dist/workflows/feature.mjs +138 -38
  15. package/dist/workflows/fix.mjs +2 -2
  16. package/dist/workflows/review.mjs +2 -2
  17. package/dist/workflows/rollout.mjs +1 -1
  18. package/dist/workflows.mjs +208 -66
  19. package/package.json +1 -1
  20. package/skills/authoring/SKILL.md +3 -3
  21. package/src/cli/Cli.test.ts +10 -3
  22. package/src/cli/Cli.ts +76 -1
  23. package/src/cli/Program.test.ts +1 -0
  24. package/src/cli/Program.ts +28 -0
  25. package/src/cli/ToolCommands.ts +3 -0
  26. package/src/edge/Edge.ts +11 -0
  27. package/src/flows/runtime.ts +11 -0
  28. package/src/flows/scripts.ts +31 -9
  29. package/src/replay/Replay.ts +51 -2
  30. package/src/testkit/FlowScripts.test.ts +58 -0
  31. package/src/testkit/FlowScripts.ts +61 -24
  32. package/src/testkit/Layers.ts +1 -0
  33. package/src/wire/BeatDocument.ts +12 -2
  34. package/src/wire/BeatStatus.ts +3 -0
  35. package/src/wire/ContextLimit.fixture.ts +30 -0
  36. package/src/wire/ContextLimit.test.ts +96 -0
  37. package/src/wire/ContextLimit.ts +169 -0
  38. package/src/wire/index.ts +1 -0
  39. package/src/wire/types.ts +7 -0
  40. package/src/workflow/Workflow.ts +9 -1
  41. package/src/workflows/feature/acceptance-criteria.feature +5 -1
  42. package/src/workflows/feature/context-limit.feature +37 -0
  43. package/src/workflows/feature/coverage.feature +6 -3
  44. package/src/workflows/feature/coverage.ts +8 -2
  45. package/src/workflows/feature/fast-path.feature +11 -19
  46. package/src/workflows/feature/feature.test.ts +24 -4
  47. package/src/workflows/feature/feature.ts +14 -10
  48. package/src/workflows/feature/flag-mode.feature +17 -9
  49. package/src/workflows/feature/lens-models.feature +7 -1
  50. package/src/workflows/feature/naive-acceptance.feature +2 -2
  51. package/src/workflows/feature/one-step-land.feature +10 -12
  52. package/src/workflows/feature/quality-unresolved.feature +7 -1
  53. package/src/workflows/feature/refactor-absorbs.feature +12 -5
  54. package/src/workflows/feature/refactor-first.feature +11 -2
  55. package/src/workflows/feature/refactor-lens-models.feature +4 -0
  56. package/src/workflows/feature/refactor-rebuild.feature +269 -0
  57. package/src/workflows/feature/refactor-review.feature +45 -1
  58. package/src/workflows/feature/refactor.ts +9 -3
  59. package/src/workflows/feature/risk-tier.feature +12 -4
  60. package/src/workflows/feature/seam-rejection.feature +4 -9
  61. package/src/workflows/feature/seam.feature +29 -26
  62. package/src/workflows/feature/seam.test.ts +117 -6
  63. package/src/workflows/feature/seam.ts +158 -39
  64. package/src/workflows/review/review-door.feature +12 -4
  65. package/src/workflows/shared/options.test.ts +11 -2
  66. package/src/workflows/shared/options.ts +18 -4
  67. package/src/workflows/shared/steps.ts +1 -0
  68. package/src/workflows/shared/text.test.ts +8 -0
  69. package/src/workflows/shared/text.ts +91 -22
@@ -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 { agent, 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 normaliseCriteriaScript, A as buildReviewAwaitAfterDeferMessage, B as criterionLine, C as fixRisks, D as reviewing, F as clearCriteriaMessage, H as designGateAnswerMessage, I as clearCriteriaQuestion, J as idleMessage, L as coverageMessage, M as buildReviewFixNitsPrompt, N as buildReviewTriageMessage, O as agentWithSkills, P as builderSystem, Q as naivePassPrompt, R as coverageQuestion, T as reviewMissing, U as designSystem, V as deferNotesScript, W as designTriagePrompt, X as implementFixPrompt, Y as implementBuildingPrompt, Z as naiveApplyFailure, _ as REQUIREMENTS, _t as defaults, a as flagModeOn, at as refactorRules, b as coder, d as guarded, dt as seamByHandMessage, et as qualityUnresolvedMessage, f as healthy, ft as seamSteerMessage, g as REFACTOR_NOTE, gt as unwindFailure, h as REFACTOR, ht as unwindFailedMessage, i as skills, it as refactorNotePrompt, j as buildReviewCollectingPrompt, k as buildReviewAnswerQuestionsPrompt, lt as reviewNotesCapture, m as QUALITY, mt as uncoveredMessage, n as summary, nt as refactorDroppedRefusal, ot as reviewEditNotesCapture, p as FEEDBACK, pt as startGateBlockedMessage, r as access, rt as refactorFixPrompt, s as shipped, st as reviewEditsCapture, t as steering, tt as refactorBuildingPrompt, u as gate, ut as reviewerSystem, v as REVIEW, vt as envDefaults, w as planner, x as fixCheck, y as awaitReview, yt as testFileGlobs, z as coveringPrompt } from "../chunks/reserved-zAfoJtR7.mjs";
4
+ import { a as refusePendingHandOff, i as isOptionalObject, n as closeReview, o as tail } from "../chunks/tail-BRC4DGdV.mjs";
5
5
  import { posix } from "node:path";
6
6
  //#region src/workflows/shared/fences.ts
7
7
  const markFenced = (text) => {
@@ -623,7 +623,8 @@ const plan = (shape, options) => {
623
623
  return {
624
624
  pattern,
625
625
  strays,
626
- reference: options.reference?.trim() ? options.reference : void 0
626
+ reference: options.reference?.trim() ? options.reference : void 0,
627
+ note: options.note?.trim() ? options.note : void 0
627
628
  };
628
629
  };
629
630
  const guardOf = ({ pattern, strays }, entry) => () => {
@@ -632,13 +633,22 @@ const guardOf = ({ pattern, strays }, entry) => () => {
632
633
  };
633
634
  /** Derive, build, check and close one refactor lap; `feedback` is the collected review text of the lap before. */
634
635
  const refactorLap = async (p, guard, feedback) => {
635
- const { pattern, strays, reference } = p;
636
- await run("deriving", deriveScript([...packageLines(strays, pattern, reference), ...feedback === void 0 ? [] : [
637
- "## Review feedback",
638
- "",
639
- ...demoteHeadings(feedback),
640
- ""
641
- ]]), {
636
+ const { pattern, strays, reference, note } = p;
637
+ await run("deriving", deriveScript([
638
+ ...packageLines(strays, pattern, reference),
639
+ ...note === void 0 ? [] : [
640
+ "## Refactor note",
641
+ "",
642
+ ...demoteHeadings(note),
643
+ ""
644
+ ],
645
+ ...feedback === void 0 ? [] : [
646
+ "## Review feedback",
647
+ "",
648
+ ...demoteHeadings(feedback),
649
+ ""
650
+ ]
651
+ ]), {
642
652
  label: "Deriving the refactor package",
643
653
  file: REFACTOR_PACKAGE
644
654
  });
@@ -647,7 +657,8 @@ const refactorLap = async (p, guard, feedback) => {
647
657
  strays,
648
658
  pattern,
649
659
  reference,
650
- feedback: feedback !== void 0
660
+ feedback: feedback !== void 0,
661
+ note: note !== void 0
651
662
  }), {
652
663
  label: "Refactoring",
653
664
  model: coder(),
@@ -733,13 +744,16 @@ const COVERED_MIN_P = .7;
733
744
  /** Covering turns before the process rests at `uncovered`; a human edit there refills them. */
734
745
  const COVERING_RETRIES = 2;
735
746
  const frozen = () => requireCriteriaFrozen(REQUIREMENTS);
736
- const green = () => healthy(() => agentWithSkills("fix.suite.fixing", implementFixPrompt(), {
747
+ const green = (guard) => healthy(() => agentWithSkills("fix.suite.fixing", implementFixPrompt(), {
737
748
  label: "Fixing the check",
738
749
  file: FEEDBACK,
739
750
  model: coder(),
740
751
  system: builderSystem()
741
752
  }), {
742
- guard: frozen,
753
+ guard: () => {
754
+ frozen();
755
+ guard?.();
756
+ },
743
757
  guardRests: false
744
758
  });
745
759
  const testsSince = (base) => changesSince(base).filter((c) => c.after !== void 0 && isCode$1(c.path) && isTestFile(c.path)).sort((a, b) => Number(/\bAC-\d/.test(a.after)) - Number(/\bAC-\d/.test(b.after))).map((c) => `=== ${c.path} (${c.status})\n${c.after}`).join("\n");
@@ -853,16 +867,20 @@ const addedLines = (change) => {
853
867
  const before = new Set((change.before ?? "").split("\n"));
854
868
  return change.after.split("\n").filter((line) => !before.has(line)).join("\n");
855
869
  };
870
+ const acceptanceTests = (changes) => changes.filter((c) => c.after !== void 0 && isCode$1(c.path) && isTestFile(c.path));
871
+ const idsOf = (file) => (criteriaOf(read(file) ?? "") ?? []).map((c) => c.id).filter((id) => id !== "");
872
+ const unnamed = (ids, tests) => {
873
+ const lines = tests.map(addedLines);
874
+ return ids.filter((id) => !lines.some((text) => named(id, text)));
875
+ };
856
876
  /**
857
877
  * Why `tip`'s tests fall short, or `undefined`: a criterion ID of `file` no
858
878
  * test line of the pass names, or a test importing a file the pass added —
859
879
  * naive structure a reset and refactor replaces, which the tests must survive.
860
880
  */
861
881
  const acceptanceFault = (file, tip) => {
862
- const ids = (criteriaOf(read(file) ?? "") ?? []).map((c) => c.id).filter((id) => id !== "");
863
- const tests = tip.changes.filter((c) => c.after !== void 0 && isCode$1(c.path) && isTestFile(c.path));
864
- const lines = tests.map(addedLines);
865
- const untested = ids.filter((id) => !lines.some((text) => named(id, text)));
882
+ const tests = acceptanceTests(tip.changes);
883
+ const untested = unnamed(idsOf(file), tests);
866
884
  if (untested.length > 0) return `acceptance-tests: no test of the naive pass names ${untested.join(", ")} — write a behaviour-level test for each criterion with its ID in the test or scenario name, e.g. "[${untested[0]}] …"`;
867
885
  const added = tip.changes.filter((c) => c.status === "added" && isCode$1(c.path) && !isTestFile(c.path)).map((c) => c.path);
868
886
  const reaching = tests.filter((test) => holdsCallSites(test.path)).flatMap((test) => {
@@ -871,6 +889,41 @@ const acceptanceFault = (file, tip) => {
871
889
  });
872
890
  if (reaching.length > 0) return `acceptance-internals: these tests import files the naive pass added — test through code that existed before it (a public entry point, an existing module's exports):\n${reaching.join("\n")}`;
873
891
  };
892
+ /** What a rebuild carries over from the naive pass `tip`: the IDs its tests name, and those test files. */
893
+ const naiveAcceptance = (tip) => {
894
+ const ids = idsOf(REQUIREMENTS);
895
+ const perTest = acceptanceTests(tip.changes).map((c) => {
896
+ const lines = addedLines(c);
897
+ return {
898
+ path: c.path,
899
+ ids: ids.filter((id) => named(id, lines))
900
+ };
901
+ });
902
+ return {
903
+ hash: tip.hash,
904
+ tests: perTest.filter((c) => c.ids.length > 0).map((c) => c.path),
905
+ ids: ids.filter((id) => perTest.some((c) => c.ids.includes(id)))
906
+ };
907
+ };
908
+ /**
909
+ * Refuse a rebuild whose test changes since `since` leave out an ID the naive
910
+ * pass's tests named. A skipped test still counts: the gate reads text, never runs it.
911
+ */
912
+ const requireAcceptance = (acceptance, since) => {
913
+ const missing = unnamed(acceptance.ids, acceptanceTests(changesSince(since)));
914
+ if (missing.length === 0) return;
915
+ refuse(`gtd land: rebuild-acceptance: the rebuild's tests leave out ${missing.join(", ")} — carry over the naive pass's acceptance tests (\`git show ${acceptance.hash} -- ${acceptance.tests.map(quote).join(" ")}\`), each with its ID in the test or scenario name`);
916
+ };
917
+ /**
918
+ * Refuse a fix turn that, on its own, drops an ID the naive pass's tests named
919
+ * from a test file. Turn-local, so a person's edit at an escalation rest never
920
+ * wedges later fix turns.
921
+ */
922
+ const requireAcceptanceKept = (acceptance) => {
923
+ const dropped = acceptance.ids.filter((id) => changes().filter((c) => isCode$1(c.path) && isTestFile(c.path)).some((c) => named(id, c.before ?? "") && !named(id, c.after ?? "")));
924
+ if (dropped.length === 0) return;
925
+ refuse(`gtd land: rebuild-acceptance: this turn drops ${dropped.join(", ")} from the tests — the naive pass's acceptance tests stay; fix the code, not them`);
926
+ };
874
927
  const restore = (path) => `if git cat-file -e ${quote(`HEAD:${path}`)} 2>/dev/null; then git checkout -q HEAD -- ${quote(path)}; else rm -f -- ${quote(path)}; fi`;
875
928
  /**
876
929
  * Run the suite with `tip`'s diff applied, then take it out again; resolves
@@ -883,7 +936,7 @@ const naiveGreen = (tip) => {
883
936
  return check("check", [
884
937
  ...paths,
885
938
  "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`,
939
+ `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
940
  " echo 'gtd: the kept naive pass no longer applies to HEAD; no test ran'",
888
941
  " exit 1",
889
942
  "fi",
@@ -902,6 +955,11 @@ const naiveGreen = (tip) => {
902
955
  label: "Running the naive pass's tests"
903
956
  });
904
957
  };
958
+ /** Resolves `false` when the tip no longer applies (the failure is in `.gtd/FEEDBACK.md`) or has nothing outside `.gtd/` to apply. */
959
+ const applied = async (tip) => {
960
+ await run("applying", applyScript(tip, FEEDBACK, naiveApplyFailure(tip), [".gtd"]), { label: "Applying the naive pass" });
961
+ return !wrote(".gtd/FEEDBACK.md") && codeChanges().length > 0;
962
+ };
905
963
  function implement(first, tier, amend) {
906
964
  const options = typeof first === "object" && first !== null ? first : {
907
965
  reference: first,
@@ -909,22 +967,26 @@ function implement(first, tier, amend) {
909
967
  amend
910
968
  };
911
969
  return scope("implement", async () => {
912
- await agentWithSkills("building", implementBuildingPrompt(options.reference, options.tier, options.amend, options.afterRefactor, flagModeOn()), {
970
+ const since = head();
971
+ const acceptance = options.rebuild?.acceptance;
972
+ const built = options.naive === void 0 || !await applied(options.naive);
973
+ if (built) await agentWithSkills("building", implementBuildingPrompt(options.reference, options.tier, options.amend, options.afterRefactor, flagModeOn(), options.rebuild), {
913
974
  label: "Implementing",
914
975
  model: coder(),
915
976
  system: builderSystem(),
916
977
  ...options.afterRefactor === true ? { allowEmpty: true } : {}
917
978
  });
918
979
  requireCriteriaFrozen(REQUIREMENTS);
919
- await green();
980
+ if (built && acceptance !== void 0) requireAcceptance(acceptance, since);
981
+ await green(built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0);
920
982
  await requireCoverage();
921
- await run("closing", removeScript([REQUIREMENTS]), { label: "Closing out the implementation" });
983
+ await run("closing", removeScript([REQUIREMENTS, REFACTOR_NOTE]), { label: "Closing out the implementation" });
922
984
  });
923
985
  }
924
986
  /** Resolves `true` when the human steered (edited), `false` on a clean overrule. */
925
987
  const steer = async (retry) => {
926
988
  await human("steer", {
927
- message: seamSteerMessage(retry),
989
+ message: seamSteerMessage(retry, flagModeOn()),
928
990
  label: "Steer or overrule the naive pass",
929
991
  file: REQUIREMENTS,
930
992
  acceptClean: true
@@ -968,16 +1030,23 @@ const vetted = async (tip, vetting) => {
968
1030
  /** The judge's ruling on a vetted pass: the decision, or the next attempt's verdict. */
969
1031
  const ruled = async (tip, again) => {
970
1032
  const shape = await judgeSeam(tip);
971
- if (shape.choice === "land") return { reference: tip.hash };
1033
+ if (shape.choice === "land") return {
1034
+ reference: tip.hash,
1035
+ tip
1036
+ };
972
1037
  if (shape.choice === "refactor-first") return {
973
1038
  reference: tip.hash,
1039
+ tip,
974
1040
  shape
975
1041
  };
976
1042
  const next = await again({
977
1043
  strays: shape.strays,
978
1044
  p: shape.judgment?.answers.shape?.p ?? 0
979
1045
  });
980
- return next.kind === "overruled" ? { reference: tip.hash } : next;
1046
+ return next.kind === "overruled" ? {
1047
+ reference: tip.hash,
1048
+ tip
1049
+ } : next;
981
1050
  };
982
1051
  const decide = () => scope("seam", async () => {
983
1052
  const vetting = {
@@ -988,16 +1057,33 @@ const decide = () => scope("seam", async () => {
988
1057
  for (;;) {
989
1058
  await naivePass(retry);
990
1059
  const tip = kept("naive");
991
- const outcome = await vetted(tip, vetting) ?? await ruled(tip, vetting.again);
1060
+ const outcome = await vetted(tip, vetting) ?? (flagModeOn() ? await ruled(tip, vetting.again) : {
1061
+ reference: tip.hash,
1062
+ tip
1063
+ });
992
1064
  if (!("kind" in outcome)) return outcome;
993
1065
  retry = outcome.kind === "retry" ? outcome.retry : void 0;
994
1066
  }
995
1067
  });
1068
+ /** Write the refactor note from the whole naive pass; a turn that changes anything else is refused. */
1069
+ const refactorNote = async (reference, strays) => {
1070
+ await agent("note", refactorNotePrompt(reference, strays), {
1071
+ label: "Writing the refactor note",
1072
+ file: REFACTOR_NOTE,
1073
+ model: coder(),
1074
+ system: builderSystem()
1075
+ });
1076
+ const others = changes().map((c) => c.path).filter((path) => path !== REFACTOR_NOTE);
1077
+ const note = read(".gtd/REFACTOR-NOTE.md") ?? "";
1078
+ if (others.length > 0 || note.trim() === "") refuse(`gtd land: refactor-note: write ${REFACTOR_NOTE} and change nothing else${others.length > 0 ? ` — revert these:\n${others.map((path) => ` - ${path}`).join("\n")}` : ""}`);
1079
+ return note;
1080
+ };
996
1081
  const seamBuild = async (base) => {
997
- const { reference, shape } = await decide();
1082
+ const { reference, tip, shape } = await decide();
998
1083
  if (shape === void 0) return {
999
1084
  reference,
1000
- base
1085
+ base,
1086
+ land: true
1001
1087
  };
1002
1088
  if (shape.unreadable.length > 0) {
1003
1089
  await scope("seam", () => human("by-hand", {
@@ -1007,18 +1093,29 @@ const seamBuild = async (base) => {
1007
1093
  }));
1008
1094
  return {
1009
1095
  reference,
1010
- base
1096
+ base,
1097
+ rebuild: { acceptance: naiveAcceptance(tip) }
1011
1098
  };
1012
1099
  }
1013
- if (refactorStrays(shape).length === 0) return {
1100
+ const strays = refactorStrays(shape);
1101
+ if (strays.length === 0) return {
1014
1102
  reference,
1015
- base
1103
+ base,
1104
+ rebuild: { acceptance: naiveAcceptance(tip) }
1016
1105
  };
1017
- const refactored = await slice("refactor", () => reviewedRefactor(shape, { reference }));
1106
+ const note = await scope("seam", () => refactorNote(reference, strays));
1107
+ const refactored = await slice("refactor", () => reviewedRefactor(shape, {
1108
+ reference,
1109
+ note
1110
+ }));
1018
1111
  return {
1019
1112
  reference,
1020
1113
  base: head(),
1021
- refactored
1114
+ refactored,
1115
+ rebuild: {
1116
+ note,
1117
+ acceptance: naiveAcceptance(tip)
1118
+ }
1022
1119
  };
1023
1120
  };
1024
1121
  //#endregion
@@ -1028,17 +1125,20 @@ const unwind = () => {
1028
1125
  const commit = head();
1029
1126
  return run("unwind", revertScript(commit, FEEDBACK, unwindFailure(commit)), { label: "Unwinding your input" });
1030
1127
  };
1031
- /** A judged naive pass (none with `flagMode` off), then implement and the quality lap; design must have run since `firstBase`. */
1128
+ /** A naive pass (judged only with `flagMode` on), then implement and the quality lap; design must have run since `firstBase`. */
1032
1129
  const buildFeature = async (firstBase) => {
1033
1130
  const tier = riskTierOn() ? recordRisk() : void 0;
1034
- const seam = flagModeOn() ? await seamBuild(firstBase) : { base: firstBase };
1131
+ const seam = await seamBuild(firstBase);
1035
1132
  await slice("feature", async () => {
1036
1133
  await implement({
1037
1134
  reference: seam.reference,
1038
1135
  tier,
1039
- afterRefactor: seam.refactored
1136
+ afterRefactor: seam.refactored,
1137
+ naive: seam.land === true ? seam.reference : void 0,
1138
+ rebuild: seam.rebuild
1040
1139
  });
1041
- const base = seam.refactored === true && !changesSince(seam.base).some((c) => isCode$1(c.path)) ? firstBase : seam.base;
1140
+ const carried = seam.rebuild?.acceptance?.tests ?? [];
1141
+ const base = seam.refactored === true && !changesSince(seam.base).some((c) => isCode$1(c.path) && !carried.includes(c.path)) ? firstBase : seam.base;
1042
1142
  await tail(settleQuality, {
1043
1143
  green: true,
1044
1144
  lensBase: base
@@ -1102,4 +1202,4 @@ const feature = shipped(ordinaryStart);
1102
1202
  //#region src/workflows/feature/index.ts
1103
1203
  var feature_default = feature;
1104
1204
  //#endregion
1105
- export { COMMENT, REFACTOR_PACKAGE, acceptanceFault, access, afterTail, answerProductQuestions, answerReviewQuestions, buildFeature, buildTail, collecting, criteriaOf, feature_default as default, defaults, deferNotes, design, envDefaults, feature, fixNits, holdsCallSites, implement, isTestFile, judgeSeam, naivePass, ordinaryStart, planAndBuild, reUnwind, refactorCollecting, refactorFirst, refactorStrays, requireCriteria, requireCriteriaFrozen, requireNoNewFlags, requireNoTestFiles, requireTestCallSitesOnly, requireWithin, reviewedRefactor, seamBuild, skills, skipDesign, statedCriteria, steering, summary, triage, unwind };
1205
+ export { COMMENT, REFACTOR_PACKAGE, acceptanceFault, access, afterTail, answerProductQuestions, answerReviewQuestions, buildFeature, buildTail, collecting, criteriaOf, feature_default as default, defaults, deferNotes, design, envDefaults, feature, fixNits, holdsCallSites, implement, isTestFile, judgeSeam, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, reUnwind, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireNoNewFlags, requireNoTestFiles, requireTestCallSitesOnly, requireWithin, reviewedRefactor, seamBuild, skills, skipDesign, statedCriteria, steering, summary, triage, unwind };
@@ -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 { G as finisherSystem, K as fixReviewNotesPrompt, O as agentWithSkills, _t as defaults, b as coder, c as baseline, i as skills, n as summary, r as access, s as shipped, t as steering, v as REVIEW, vt as envDefaults } from "../chunks/reserved-zAfoJtR7.mjs";
4
+ import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-BRC4DGdV.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 { _t as defaults, ct as reviewGateBlockedMessage, i as skills, n as summary, r as access, s as shipped, t as steering, u as gate, vt as envDefaults } from "../chunks/reserved-zAfoJtR7.mjs";
3
+ import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-BRC4DGdV.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 flipProblems, _t as defaults, b as coder, bt as flagMentions, i as skills, n as summary, o as flagShipped, p as FEEDBACK, r as access, t as steering, u as gate, vt as envDefaults, wt as requireFlag } from "../chunks/reserved-zAfoJtR7.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.";