@pmelab/gtd 54.0.0 → 56.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 (72) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +109 -104
  3. package/dist/chunks/{reserved-CQ7amcKG.mjs → reserved-B8EC-WE8.mjs} +124 -230
  4. package/dist/chunks/{review-CXzUXJHL.mjs → review-HdrX0FJ7.mjs} +30 -125
  5. package/dist/chunks/{steering-CLhu8C7q.mjs → steering-CCNvngSH.mjs} +256 -65
  6. package/dist/chunks/{tail-JZ9PAOwp.mjs → tail-BSLah2mA.mjs} +155 -63
  7. package/dist/gtd.bundle.mjs +259 -22
  8. package/dist/steering.mjs +2 -2
  9. package/dist/testkit.mjs +6 -6
  10. package/dist/web/main.css +1 -1
  11. package/dist/web/main.js +551 -66
  12. package/dist/workflow-kit.mjs +1 -1
  13. package/dist/workflows/cleanup.mjs +2 -2
  14. package/dist/workflows/feature.mjs +47 -102
  15. package/dist/workflows/fix.mjs +3 -3
  16. package/dist/workflows/review.mjs +3 -3
  17. package/dist/workflows/rollout.mjs +1 -1
  18. package/dist/workflows.mjs +614 -574
  19. package/package.json +1 -1
  20. package/skills/authoring/SKILL.md +3 -3
  21. package/src/cli/Cli.ts +5 -5
  22. package/src/edge/Edge.test.ts +1 -1
  23. package/src/steering/FORMATS.md +5 -1
  24. package/src/steering/SteeringFormats.test.ts +2 -2
  25. package/src/steering/contract.test.ts +1 -0
  26. package/src/steering/findings.test.ts +47 -0
  27. package/src/steering/findings.ts +350 -0
  28. package/src/steering/fixtures/findings/gate.md +34 -0
  29. package/src/steering/fixtures/findings/gate.read.json +87 -0
  30. package/src/steering/index.test.ts +2 -2
  31. package/src/steering/index.ts +4 -3
  32. package/src/steering/review.test.ts +1 -75
  33. package/src/steering/review.ts +1 -74
  34. package/src/workflow/compile.test.ts +1 -0
  35. package/src/workflows/feature/criteria-coverage.feature +104 -0
  36. package/src/workflows/feature/feature.test.ts +1 -1
  37. package/src/workflows/feature/feature.ts +17 -20
  38. package/src/workflows/feature/green.ts +2 -5
  39. package/src/workflows/feature/lens-review.feature +323 -171
  40. package/src/workflows/feature/naive-acceptance.feature +31 -32
  41. package/src/workflows/feature/prototype.ts +5 -8
  42. package/src/workflows/feature/refactor-absorbs.feature +1 -7
  43. package/src/workflows/feature/refactor-first.feature +2 -10
  44. package/src/workflows/feature/refactor-rebuild.feature +20 -36
  45. package/src/workflows/feature/refactor-review.feature +33 -117
  46. package/src/workflows/feature/refactor.test.ts +0 -2
  47. package/src/workflows/feature/refactor.ts +0 -1
  48. package/src/workflows/feature/review-gate.feature +6 -22
  49. package/src/workflows/feature/reviewer-walkthrough.feature +16 -103
  50. package/src/workflows/feature/seam.test.ts +19 -101
  51. package/src/workflows/feature/seam.ts +13 -89
  52. package/src/workflows/feature/standards.feature +75 -34
  53. package/src/workflows/fix/fix-lens-origin.feature +20 -7
  54. package/src/workflows/fix/fix-quality.feature +104 -23
  55. package/src/workflows/review/review-door.feature +62 -59
  56. package/src/workflows/shared/access.test.ts +1 -0
  57. package/src/workflows/shared/learn.test.ts +0 -14
  58. package/src/workflows/shared/learn.ts +1 -6
  59. package/src/workflows/shared/lenses.ts +149 -43
  60. package/src/workflows/shared/review.test.ts +30 -27
  61. package/src/workflows/shared/review.ts +29 -196
  62. package/src/workflows/shared/skills.test.ts +3 -1
  63. package/src/workflows/shared/steps.test.ts +9 -6
  64. package/src/workflows/shared/steps.ts +12 -3
  65. package/src/workflows/shared/tail.test.ts +20 -2
  66. package/src/workflows/shared/tail.ts +38 -16
  67. package/src/workflows/shared/text.test.ts +122 -294
  68. package/src/workflows/shared/text.ts +181 -358
  69. package/src/workflows/shared/vars.ts +1 -1
  70. package/src/workflows/feature/review-answers.feature +0 -324
  71. package/src/workflows/feature/severity-budget.feature +0 -116
  72. package/src/workflows/feature/severity-sorted-review.feature +0 -198
@@ -1,5 +1,5 @@
1
1
  import { changes, changesSince, hasThreadFor, head, mark, read, refuse, requireReplies, requireThreadsClosed, scope } from "./flows.mjs";
2
- import { S as unansweredNotes, _ as reviewNotes, c as stripCodeThreads, g as reviewDecisions, k as parseThreads, v as reviewRisks } from "./chunks/steering-CLhu8C7q.mjs";
2
+ import { A as parseThreads, C as unansweredNotes, b as reviewNotes, c as stripCodeThreads, x as reviewRisks, y as reviewDecisions } from "./chunks/steering-CCNvngSH.mjs";
3
3
  //#region src/workflow-kit/health.ts
4
4
  /** Hand a red suite that repeated attempts could not fix to a person, until `limit` rounds are spent. */
5
5
  const escalation = async (count, steps) => {
@@ -1,6 +1,6 @@
1
1
  import { agent, changes, glob, read, refuse, scope, start, vars } from "../flows.mjs";
2
- import { Kt as registryEntry, Mt as envDefaults, Wt as flagReferences, i as skills, jt as defaults, n as summary, o as flagShipped, qt as requireFlag, r as access, t as steering, x as coder } from "../chunks/reserved-CQ7amcKG.mjs";
3
- import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-JZ9PAOwp.mjs";
2
+ import { Bt as flagReferences, Dt as defaults, Ht as registryEntry, Ot as envDefaults, Ut as requireFlag, i as skills, n as summary, o as flagShipped, r as access, t as steering, x as coder } from "../chunks/reserved-B8EC-WE8.mjs";
3
+ import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-BSLah2mA.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,8 +1,8 @@
1
1
  import { agent, answered, applyScript, changes, changesSince, check, codeChanges, env, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, matches, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireThreadsClosed, revertScript, run, scope, start, vars, wrote } from "../flows.mjs";
2
2
  import { lensEntriesOf } from "../workflow-kit.mjs";
3
- import { $ as observableMessage, B as designGateAnswerMessage, Ct as testerSystem, D as planner, Dt as unwindFailure, Et as unwindFailedMessage, H as designSystem, I as clearCriteriaMessage, J as implementBuildingPrompt, L as clearCriteriaQuestion, M as buildReviewCollectingPrompt, Mt as envDefaults, Nt as testFileGlobs, P as builderSystem, Q as normaliseCriteriaScript, S as collecting, St as startGateBlockedMessage, U as designTriagePrompt, V as designImpactPrompt, X as naiveApplyFailure, Y as implementFixPrompt, Z as naivePassPrompt, _ as REQUIREMENTS, a as flagModeOn, bt as seamByHandMessage, ct as refactorBuildingPrompt, d as healthy, dt as refactorNotePrompt, et as observableQuestion, f as FEEDBACK, ft as refactorRules, g as REFACTOR_NOTE, h as REFACTOR, i as skills, it as prototypeUnguidedMessage, j as agentWithSkills, jt as defaults, l as gate, lt as refactorDroppedRefusal, n as summary, nt as prototypeOpenMessage, p as PROTOTYPE, q as idleMessage, r as access, rt as prototypeTestingPrompt, s as shipped, t as steering, tt as prototypeFixingPrompt, ut as refactorFixPrompt, w as fixNits, x as coder, xt as seamSteerMessage, y as answerReviewQuestions, yt as reviewerSystem, z as criterionLine } from "../chunks/reserved-CQ7amcKG.mjs";
4
- import { a as refusePendingHandOff, o as tail } from "../chunks/tail-JZ9PAOwp.mjs";
5
- import { i as reUnwind, n as humanReview, r as isCode$1, t as buildTail } from "../chunks/review-CXzUXJHL.mjs";
3
+ import { $ as normaliseCriteriaScript, B as designSystem, Ct as unwindFailedMessage, D as planner, Dt as defaults, F as clearCriteriaMessage, I as clearCriteriaQuestion, J as implementBuildingPrompt, L as criterionLine, M as buildReviewCollectingPrompt, Ot as envDefaults, P as builderSystem, R as designGateAnswerMessage, S as collecting, St as testerSystem, V as designTriagePrompt, X as naiveApplyFailure, Y as implementFixPrompt, Z as naivePassPrompt, _ as REQUIREMENTS, a as flagModeOn, at as prototypeUnguidedMessage, bt as seamSteerMessage, ct as refactorBuildingPrompt, d as healthy, dt as refactorNotePrompt, et as observableMessage, f as FEEDBACK, ft as refactorRules, g as REFACTOR_NOTE, h as REFACTOR, i as skills, it as prototypeTestingPrompt, j as agentWithSkills, kt as testFileGlobs, l as gate, lt as refactorDroppedRefusal, n as summary, nt as prototypeFixingPrompt, p as PROTOTYPE, q as idleMessage, r as access, rt as prototypeOpenMessage, s as shipped, t as steering, tt as observableQuestion, ut as refactorFixPrompt, vt as reviewerSystem, w as fixNits, wt as unwindFailure, x as coder, xt as startGateBlockedMessage, y as answerReviewQuestions, yt as seamByHandMessage, z as designImpactPrompt } from "../chunks/reserved-B8EC-WE8.mjs";
4
+ import { a as refusePendingHandOff } from "../chunks/tail-BSLah2mA.mjs";
5
+ import { n as isCode$1, r as reUnwind, t as buildTail } from "../chunks/review-HdrX0FJ7.mjs";
6
6
  import { posix } from "node:path";
7
7
  //#region src/workflows/shared/fences.ts
8
8
  const markFenced = (text) => {
@@ -304,16 +304,13 @@ const design = (base, guard) => scope("design", async () => {
304
304
  });
305
305
  //#endregion
306
306
  //#region src/workflows/feature/green.ts
307
- const green = (guard) => healthy(() => agentWithSkills("fix.suite.fixing", implementFixPrompt(), {
307
+ const green = () => healthy(() => agentWithSkills("fix.suite.fixing", implementFixPrompt(), {
308
308
  label: "Fixing the check",
309
309
  file: FEEDBACK,
310
310
  model: coder(),
311
311
  system: builderSystem()
312
312
  }), {
313
- guard: () => {
314
- requireCriteriaFrozen(REQUIREMENTS);
315
- guard?.();
316
- },
313
+ guard: () => requireCriteriaFrozen(REQUIREMENTS),
317
314
  guardRests: false
318
315
  });
319
316
  //#endregion
@@ -403,7 +400,7 @@ const testing = async (ids, retest) => {
403
400
  });
404
401
  return requireReport(ids, retest);
405
402
  };
406
- const fixing = async (guard) => {
403
+ const fixing = async () => {
407
404
  await scope("fix", () => agentWithSkills("fixing", prototypeFixingPrompt(), {
408
405
  label: "Fixing what the hands-on test found",
409
406
  file: PROTOTYPE,
@@ -413,7 +410,6 @@ const fixing = async (guard) => {
413
410
  }));
414
411
  if (changes().some((c) => c.path === ".gtd/PROTOTYPE.md")) refuse(`gtd land: prototype-fix: leave ${PROTOTYPE} as it is — the next round re-tests from it`);
415
412
  frozen();
416
- guard?.();
417
413
  };
418
414
  /** A person's rest; resolves `true` when they changed something to test again, `false` on a clean overrule. */
419
415
  const askPerson = async (name, message, label) => {
@@ -431,10 +427,9 @@ const askPerson = async (name, message, label) => {
431
427
  * Use the change by hand: a testing turn reports on every criterion a person
432
428
  * can observe, a fix turn takes each failure, and the change is tested again —
433
429
  * until the report is clean, or `prototypeRounds` fixes are spent and a person
434
- * decides. A change with no such criterion is not tested by hand. `guard`
435
- * holds every fix turn, as it holds the implementation's.
430
+ * decides. A change with no such criterion is not tested by hand.
436
431
  */
437
- const prototypeTest = async (guard) => {
432
+ const prototypeTest = async () => {
438
433
  const cap = prototypeRounds();
439
434
  if (cap === 0) return;
440
435
  await scope("prototype", async () => {
@@ -450,12 +445,12 @@ const prototypeTest = async (guard) => {
450
445
  } else if (open.length === 0) return;
451
446
  else if (fixes < cap && open.some((i) => i.verdict === "fail")) {
452
447
  fixes++;
453
- await fixing(guard);
448
+ await fixing();
454
449
  } else {
455
450
  if (!await askPerson("open", prototypeOpenMessage(open, fixes), "Hands-on test still failing")) return;
456
451
  fixes = 0;
457
452
  }
458
- await green(guard);
453
+ await green();
459
454
  }
460
455
  });
461
456
  };
@@ -701,7 +696,6 @@ const reviewedRefactor = async (shape, options = {}) => {
701
696
  fixAccess: strayAccess(p.strays),
702
697
  lensBase: entry,
703
698
  rules: refactorRules(p.strays, p.pattern),
704
- walkthrough: true,
705
699
  lap: "refactor"
706
700
  });
707
701
  if (outcome.verdict === "signoff") return true;
@@ -755,69 +749,25 @@ const importsOf = (test) => [...(test.after ?? "").replace(COMMENT, "").matchAll
755
749
  }));
756
750
  });
757
751
  const reaches = (imports, added) => namesOf(added).some((name) => imports.some(({ path, tail }) => name === path || tail && name.endsWith(`/${path}`)));
758
- const named = (id, text) => new RegExp(`(?<![\\w-])${id}(?!\\d)`).test(text);
759
- /** The lines `change` added: an ID an earlier change left in the file does not count. */
760
- const addedLines = (change) => {
761
- const before = new Set((change.before ?? "").split("\n"));
762
- return change.after.split("\n").filter((line) => !before.has(line)).join("\n");
763
- };
764
752
  const acceptanceTests = (changes) => changes.filter((c) => c.after !== void 0 && isCode$1(c.path) && isTestFile(c.path));
765
- const idsOf = (file) => criterionIds(read(file) ?? "");
766
- const unnamed = (ids, tests) => {
767
- const lines = tests.map(addedLines);
768
- return ids.filter((id) => !lines.some((text) => named(id, text)));
769
- };
770
753
  /**
771
- * Why `tip`'s tests fall short, or `undefined`: a criterion ID of `file` no
772
- * test line of the pass names, or a test importing a file the pass added —
773
- * naive structure a reset and refactor replaces, which the tests must survive.
754
+ * Why `tip`'s tests fall short, or `undefined`: a test importing a file the
755
+ * pass added — naive structure a reset and refactor replaces, which the tests
756
+ * must survive.
774
757
  */
775
- const acceptanceFault = (file, tip) => {
776
- const tests = acceptanceTests(tip.changes);
777
- const untested = unnamed(idsOf(file), tests);
778
- 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]}] …"`;
758
+ const acceptanceFault = (tip) => {
779
759
  const added = tip.changes.filter((c) => c.status === "added" && isCode$1(c.path) && !isTestFile(c.path)).map((c) => c.path);
780
- const reaching = tests.filter((test) => holdsCallSites(test.path)).flatMap((test) => {
760
+ const reaching = acceptanceTests(tip.changes).filter((test) => holdsCallSites(test.path)).flatMap((test) => {
781
761
  const imports = importsOf(test);
782
762
  return added.filter((path) => reaches(imports, path)).map((path) => ` - ${test.path} → ${path}`);
783
763
  });
784
764
  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")}`;
785
765
  };
786
- /** What a rebuild carries over from the naive pass `tip`: the IDs its tests name, and those test files. */
787
- const naiveAcceptance = (tip) => {
788
- const ids = idsOf(REQUIREMENTS);
789
- const perTest = acceptanceTests(tip.changes).map((c) => {
790
- const lines = addedLines(c);
791
- return {
792
- path: c.path,
793
- ids: ids.filter((id) => named(id, lines))
794
- };
795
- });
796
- return {
797
- hash: tip.hash,
798
- tests: perTest.filter((c) => c.ids.length > 0).map((c) => c.path),
799
- ids: ids.filter((id) => perTest.some((c) => c.ids.includes(id)))
800
- };
801
- };
802
- /**
803
- * Refuse a rebuild whose test changes since `since` leave out an ID the naive
804
- * pass's tests named. A skipped test still counts: the gate reads text, never runs it.
805
- */
806
- const requireAcceptance = (acceptance, since) => {
807
- const missing = unnamed(acceptance.ids, acceptanceTests(changesSince(since)));
808
- if (missing.length === 0) return;
809
- 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`);
810
- };
811
- /**
812
- * Refuse a fix turn that, on its own, drops an ID the naive pass's tests named
813
- * from a test file. Turn-local, so a person's edit at an escalation rest never
814
- * wedges later fix turns.
815
- */
816
- const requireAcceptanceKept = (acceptance) => {
817
- 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 ?? "")));
818
- if (dropped.length === 0) return;
819
- 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`);
820
- };
766
+ /** What a rebuild carries over from the naive pass `tip`: every test file it added or changed. */
767
+ const naiveAcceptance = (tip) => ({
768
+ hash: tip.hash,
769
+ tests: acceptanceTests(tip.changes).map((c) => c.path)
770
+ });
821
771
  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`;
822
772
  /**
823
773
  * Run the suite with `tip`'s diff applied, then take it out again; resolves
@@ -854,11 +804,6 @@ const applied = async (tip) => {
854
804
  await run("applying", applyScript(tip, FEEDBACK, naiveApplyFailure(tip), [".gtd"]), { label: "Applying the naive pass" });
855
805
  return !wrote(".gtd/FEEDBACK.md") && codeChanges().length > 0;
856
806
  };
857
- /** `carried` limited to the criteria `.gtd/REQUIREMENTS.md` lists now; a file listing none narrows nothing. */
858
- const settled = (carried) => {
859
- const listed = idsOf(REQUIREMENTS);
860
- return listed.length === 0 ? carried : carried.filter((id) => listed.includes(id));
861
- };
862
807
  const build = async (options) => {
863
808
  await agentWithSkills("building", implementBuildingPrompt(options.reference, options.tier, options.amend, options.afterRefactor, flagModeOn(), options.rebuild), {
864
809
  label: "Implementing",
@@ -877,18 +822,10 @@ async function implement(first, tier, amend) {
877
822
  }
878
823
  /** `implement`, resolving with the criterion IDs the requirements listed when it closed. */
879
824
  const implementing = (options) => scope("implement", async () => {
880
- const since = head();
881
- const acceptance = options.rebuild?.acceptance;
882
- const built = options.naive === void 0 || !await applied(options.naive);
883
- if (built) await build(options);
825
+ if (options.naive === void 0 || !await applied(options.naive)) await build(options);
884
826
  else requireCriteriaFrozen(REQUIREMENTS);
885
- if (built && acceptance !== void 0) requireAcceptance({
886
- ...acceptance,
887
- ids: settled(acceptance.ids)
888
- }, since);
889
- const keepsTests = built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0;
890
- await green(keepsTests);
891
- await prototypeTest(keepsTests);
827
+ await green();
828
+ await prototypeTest();
892
829
  const ids = criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
893
830
  await run("closing", removeScript([
894
831
  REQUIREMENTS,
@@ -928,7 +865,7 @@ const budget = () => {
928
865
  };
929
866
  /** Sends a pass whose tests fall short or run red back (`undefined`: it goes on, an overruled one as it is). */
930
867
  const vetted = async (tip, vetting) => {
931
- const fault = vetting.waived ? void 0 : acceptanceFault(REQUIREMENTS, tip);
868
+ const fault = vetting.waived ? void 0 : acceptanceFault(tip);
932
869
  if (fault !== void 0) {
933
870
  const verdict = await vetting.again({
934
871
  fault,
@@ -1048,6 +985,7 @@ const buildFeature = async (firstBase) => {
1048
985
  const handedOut = new Set(listedIds());
1049
986
  const seam = await seamBuild(firstBase);
1050
987
  const used = gather(handedOut);
988
+ const requirements = read(REQUIREMENTS);
1051
989
  const closed = await implementing({
1052
990
  reference: seam.reference,
1053
991
  tier,
@@ -1057,7 +995,11 @@ const buildFeature = async (firstBase) => {
1057
995
  });
1058
996
  const carried = seam.rebuild?.acceptance?.tests ?? [];
1059
997
  const base = seam.refactored === true && !changesSince(seam.base).some((c) => isCode$1(c.path) && !carried.includes(c.path)) ? firstBase : seam.base;
1060
- const outcome = await reviewedTail(base, base);
998
+ const outcome = await buildTail(base, {
999
+ green: true,
1000
+ lensBase: base,
1001
+ requirements
1002
+ });
1061
1003
  await featureFeedback(outcome, base, {
1062
1004
  closed,
1063
1005
  used
@@ -1068,15 +1010,6 @@ const gather = (handedOut) => {
1068
1010
  for (const id of listedIds()) handedOut.add(id);
1069
1011
  return [...handedOut];
1070
1012
  };
1071
- /** The quality lap, then a reviewer's walkthrough since `base` for the human. */
1072
- const reviewedTail = (base, lensBase, lap = "first") => tail((escalations, report) => humanReview(base, escalations, {
1073
- report,
1074
- walkthrough: true,
1075
- lap
1076
- }), {
1077
- green: true,
1078
- lensBase
1079
- });
1080
1013
  const featureFeedback = async (first, lensBase, ids) => {
1081
1014
  let outcome = first;
1082
1015
  let closed = ids.closed;
@@ -1091,11 +1024,18 @@ const featureFeedback = async (first, lensBase, ids) => {
1091
1024
  requireStableIds(REQUIREMENTS, lap);
1092
1025
  gather(handedOut);
1093
1026
  });
1027
+ const tier = riskTierOn() ? recordRisk() : void 0;
1028
+ const requirements = read(REQUIREMENTS);
1094
1029
  closed = await implementing({
1095
- tier: riskTierOn() ? recordRisk() : void 0,
1030
+ tier,
1096
1031
  amend: true
1097
1032
  });
1098
- outcome = await reviewedTail(outcome.base, lensBase, "feedback");
1033
+ outcome = await buildTail(outcome.base, {
1034
+ green: true,
1035
+ lensBase,
1036
+ requirements,
1037
+ lap: "feedback"
1038
+ });
1099
1039
  }
1100
1040
  };
1101
1041
  /** Design — skipped when the sketch's own criteria are judged clear — then `buildFeature`. */
@@ -1109,11 +1049,16 @@ const afterTail = async (first) => {
1109
1049
  while (outcome.verdict === "feedback") {
1110
1050
  await reUnwind(outcome);
1111
1051
  await design(outcome.base);
1052
+ const tier = riskTierOn() ? recordRisk() : void 0;
1053
+ const requirements = read(REQUIREMENTS);
1112
1054
  await implement({
1113
- tier: riskTierOn() ? recordRisk() : void 0,
1055
+ tier,
1114
1056
  amend: true
1115
1057
  });
1116
- outcome = await buildTail(outcome.base, { green: true });
1058
+ outcome = await buildTail(outcome.base, {
1059
+ green: true,
1060
+ requirements
1061
+ });
1117
1062
  }
1118
1063
  };
1119
1064
  /** An ordinary start: wait at `idle`, unwind the sketch, check the baseline, then plan and build. */
@@ -1141,4 +1086,4 @@ const feature = shipped(ordinaryStart);
1141
1086
  //#region src/workflows/feature/index.ts
1142
1087
  var feature_default = feature;
1143
1088
  //#endregion
1144
- export { COMMENT, REFACTOR_PACKAGE, acceptanceFault, access, afterTail, answerProductQuestions, answerReviewQuestions, buildFeature, buildTail, collecting, criteriaOf, criterionIds, feature_default as default, defaults, design, envDefaults, feature, fixNits, handsOnIds, hasImpact, holdsCallSites, implement, implementing, isTestFile, judgeSeam, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, reUnwind, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireImpact, requireImpactOnly, requireNoNewFlags, requireNoTestFiles, requireStableIds, requireTestCallSitesOnly, requireWithin, reviewedRefactor, seamBuild, skills, skipDesign, statedCriteria, steering, summary, triage, unstableIds, unwind };
1089
+ export { COMMENT, REFACTOR_PACKAGE, acceptanceFault, access, afterTail, answerProductQuestions, answerReviewQuestions, buildFeature, buildTail, collecting, criteriaOf, criterionIds, feature_default as default, defaults, design, envDefaults, feature, fixNits, handsOnIds, hasImpact, holdsCallSites, implement, implementing, isTestFile, judgeSeam, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, reUnwind, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireCriteria, requireCriteriaFrozen, requireImpact, requireImpactOnly, requireNoNewFlags, requireNoTestFiles, requireStableIds, requireTestCallSitesOnly, requireWithin, reviewedRefactor, seamBuild, skills, skipDesign, statedCriteria, steering, summary, triage, unstableIds, unwind };
@@ -1,7 +1,7 @@
1
1
  import { read, refuse, removeScript, run, scope, start } from "../flows.mjs";
2
- import { _ as reviewNotes, v as reviewRisks } from "../chunks/steering-CLhu8C7q.mjs";
3
- import { G as fixReviewNotesPrompt, Mt as envDefaults, W as finisherSystem, c as baseline, i as skills, j as agentWithSkills, jt as defaults, n as summary, r as access, s as shipped, t as steering, v as REVIEW, x as coder } from "../chunks/reserved-CQ7amcKG.mjs";
4
- import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-JZ9PAOwp.mjs";
2
+ import { b as reviewNotes, x as reviewRisks } from "../chunks/steering-CCNvngSH.mjs";
3
+ import { Dt as defaults, G as fixReviewNotesPrompt, Ot as envDefaults, W as finisherSystem, c as baseline, i as skills, j as agentWithSkills, n as summary, r as access, s as shipped, t as steering, v as REVIEW, x as coder } from "../chunks/reserved-B8EC-WE8.mjs";
4
+ import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-BSLah2mA.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,7 +1,7 @@
1
1
  import { read, refuse, removeScript, run, start } from "../flows.mjs";
2
- import { Mt as envDefaults, W as finisherSystem, _ as REQUIREMENTS, gt as reviewGateBlockedMessage, ht as reviewFeedbackFixPrompt, i as skills, j as agentWithSkills, jt as defaults, l as gate, n as summary, r as access, s as shipped, t as steering, x as coder } from "../chunks/reserved-CQ7amcKG.mjs";
3
- import { a as refusePendingHandOff } from "../chunks/tail-JZ9PAOwp.mjs";
4
- import { i as reUnwind, t as buildTail } from "../chunks/review-CXzUXJHL.mjs";
2
+ import { Dt as defaults, Ot as envDefaults, W as finisherSystem, _ as REQUIREMENTS, gt as reviewGateBlockedMessage, ht as reviewFeedbackFixPrompt, i as skills, j as agentWithSkills, l as gate, n as summary, r as access, s as shipped, t as steering, x as coder } from "../chunks/reserved-B8EC-WE8.mjs";
3
+ import { a as refusePendingHandOff } from "../chunks/tail-BSLah2mA.mjs";
4
+ import { r as reUnwind, t as buildTail } from "../chunks/review-HdrX0FJ7.mjs";
5
5
  //#region src/workflows/review/review.ts
6
6
  const fixFeedback = async () => {
7
7
  await agentWithSkills("feedback.fixing", reviewFeedbackFixPrompt(), {
@@ -1,5 +1,5 @@
1
1
  import { agent, changesSince, glob, head, mark, read, refuse, removeScript, run, scope, vars } from "../flows.mjs";
2
- import { Gt as flipProblems, Mt as envDefaults, Ut as flagMentions, f as FEEDBACK, i as skills, jt as defaults, l as gate, n as summary, o as flagShipped, qt as requireFlag, r as access, t as steering, x as coder } from "../chunks/reserved-CQ7amcKG.mjs";
2
+ import { Dt as defaults, Ot as envDefaults, Ut as requireFlag, Vt as flipProblems, f as FEEDBACK, i as skills, l as gate, n as summary, o as flagShipped, r as access, t as steering, x as coder, zt as flagMentions } from "../chunks/reserved-B8EC-WE8.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.";