@pmelab/gtd 55.0.0 → 57.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 (83) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +72 -80
  3. package/dist/chunks/{reserved-DHafcW7t.mjs → reserved-X2RkNHin.mjs} +110 -318
  4. package/dist/chunks/{review-BJGKwlYv.mjs → review-v7sh5GF4.mjs} +30 -125
  5. package/dist/chunks/{steering-CLhu8C7q.mjs → steering-CCNvngSH.mjs} +256 -65
  6. package/dist/chunks/{tail-B9Hw2AFB.mjs → tail-S04KEewi.mjs} +144 -56
  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 +63 -175
  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 +603 -727
  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 +1 -7
  36. package/src/workflows/feature/criteria.test.ts +1 -41
  37. package/src/workflows/feature/criteria.ts +2 -14
  38. package/src/workflows/feature/fast-path.feature +0 -1
  39. package/src/workflows/feature/feature.test.ts +1 -1
  40. package/src/workflows/feature/feature.ts +16 -25
  41. package/src/workflows/feature/flag-mode.feature +1 -1
  42. package/src/workflows/feature/lens-models.feature +0 -1
  43. package/src/workflows/feature/lens-review.feature +323 -172
  44. package/src/workflows/feature/naive-test.feature +258 -0
  45. package/src/workflows/feature/one-step-land.feature +0 -1
  46. package/src/workflows/feature/refactor-absorbs.feature +1 -8
  47. package/src/workflows/feature/refactor-first.feature +2 -11
  48. package/src/workflows/feature/refactor-rebuild.feature +4 -25
  49. package/src/workflows/feature/refactor-review.feature +33 -118
  50. package/src/workflows/feature/refactor.test.ts +0 -2
  51. package/src/workflows/feature/refactor.ts +0 -1
  52. package/src/workflows/feature/review-gate.feature +7 -24
  53. package/src/workflows/feature/reviewer-walkthrough.feature +16 -104
  54. package/src/workflows/feature/seam-rejection.feature +0 -1
  55. package/src/workflows/feature/seam.feature +0 -1
  56. package/src/workflows/feature/seam.test.ts +1 -2
  57. package/src/workflows/feature/seam.ts +56 -10
  58. package/src/workflows/feature/standards.feature +75 -35
  59. package/src/workflows/fix/fix-lens-origin.feature +20 -7
  60. package/src/workflows/fix/fix-quality.feature +102 -23
  61. package/src/workflows/review/review-door.feature +62 -59
  62. package/src/workflows/shared/learn.test.ts +0 -14
  63. package/src/workflows/shared/learn.ts +1 -6
  64. package/src/workflows/shared/lenses.ts +149 -43
  65. package/src/workflows/shared/options.ts +9 -1
  66. package/src/workflows/shared/prose.ts +0 -6
  67. package/src/workflows/shared/review.test.ts +27 -26
  68. package/src/workflows/shared/review.ts +29 -196
  69. package/src/workflows/shared/skills.test.ts +0 -4
  70. package/src/workflows/shared/skills.ts +0 -5
  71. package/src/workflows/shared/steps.ts +1 -1
  72. package/src/workflows/shared/tail.ts +18 -9
  73. package/src/workflows/shared/text.test.ts +125 -329
  74. package/src/workflows/shared/text.ts +173 -471
  75. package/src/workflows/shared/vars.ts +2 -3
  76. package/src/workflows/feature/hands-on-skip.feature +0 -316
  77. package/src/workflows/feature/prototype-rounds.feature +0 -156
  78. package/src/workflows/feature/prototype.feature +0 -428
  79. package/src/workflows/feature/prototype.test.ts +0 -43
  80. package/src/workflows/feature/prototype.ts +0 -198
  81. package/src/workflows/feature/review-answers.feature +0 -324
  82. package/src/workflows/feature/severity-budget.feature +0 -116
  83. 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-DHafcW7t.mjs";
3
- import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-B9Hw2AFB.mjs";
2
+ import { Ct as envDefaults, Ft as flagReferences, Lt as registryEntry, Rt as requireFlag, S as coder, St as defaults, i as skills, n as summary, o as flagShipped, r as access, t as steering } from "../chunks/reserved-X2RkNHin.mjs";
3
+ import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-S04KEewi.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
- 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";
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, restoreScript, 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-DHafcW7t.mjs";
4
- import { a as refusePendingHandOff, o as tail } from "../chunks/tail-B9Hw2AFB.mjs";
5
- import { i as reUnwind, n as humanReview, r as isCode$1, t as buildTail } from "../chunks/review-BJGKwlYv.mjs";
3
+ import { $ as naiveTryMessage, B as designImpactPrompt, C as collecting, Ct as envDefaults, F as builderSystem, H as designTriagePrompt, I as clearCriteriaMessage, J as idleMessage, L as clearCriteriaQuestion, M as agentWithSkills, N as buildReviewCollectingPrompt, O as planner, Q as naivePassPrompt, R as criterionLine, S as coder, St as defaults, T as fixNits, V as designSystem, X as implementFixPrompt, Y as implementBuildingPrompt, Z as naiveApplyFailure, _ as REFACTOR_NOTE, _t as startGateBlockedMessage, a as flagModeOn, at as refactorDroppedRefusal, b as answerReviewQuestions, c as shipped, ct as refactorRules, f as healthy, g as REFACTOR, gt as seamSteerMessage, ht as seamByHandMessage, i as skills, it as refactorBuildingPrompt, m as NAIVE_TEST, mt as reviewerSystem, n as summary, ot as refactorFixPrompt, p as FEEDBACK, r as access, s as naiveTestOn, st as refactorNotePrompt, t as steering, tt as normaliseCriteriaScript, u as gate, v as REQUIREMENTS, vt as unwindFailedMessage, wt as testFileGlobs, yt as unwindFailure, z as designGateAnswerMessage } from "../chunks/reserved-X2RkNHin.mjs";
4
+ import { a as refusePendingHandOff } from "../chunks/tail-S04KEewi.mjs";
5
+ import { n as isCode$1, r as reUnwind, t as buildTail } from "../chunks/review-v7sh5GF4.mjs";
6
6
  import { posix } from "node:path";
7
7
  //#region src/workflows/shared/fences.ts
8
8
  const markFenced = (text) => {
@@ -26,11 +26,10 @@ const ITEM = /^ ?(?:[-*+]|\d+[.)])\s+(?:\[[ xX]\]\s+)?(.*)$/;
26
26
  //#region src/workflows/feature/criteria.ts
27
27
  /** Below this the judge's `yes` is not trusted: a wrong skip builds against criteria nobody can check. */
28
28
  const CLEAR_MIN_P = .8;
29
- const ID = /^(AC-\d+)(\s*\(no hands-on\))?:\s*(.*)$/;
29
+ const ID = /^(AC-\d+):\s*(.*)$/;
30
30
  const criterion = (id, as = id[1]) => ({
31
31
  id: as,
32
- text: id[3],
33
- ...id[2] === void 0 ? {} : { noHandsOn: true }
32
+ text: id[2]
34
33
  });
35
34
  /** The list items under the first heading `title` matches (at `level` when given), up to the next heading; wrapped lines joined, fenced lines ignored. */
36
35
  const listUnder = (text, title, level) => {
@@ -67,11 +66,6 @@ const criteriaOf = (text) => listUnder(text, /^acceptance criteria$/i, 2)?.map((
67
66
  });
68
67
  /** The ID'd criteria of `text`. */
69
68
  const criterionIds = (text) => (criteriaOf(text) ?? []).map((c) => c.id).filter((id) => id !== "");
70
- /** The ID'd criteria of `text` a person can try by hand: none tagged `(no hands-on)`. */
71
- const handsOnIds = (text) => {
72
- const tagged = (criteriaOf(text) ?? []).filter((c) => c.noHandsOn).map((c) => c.id);
73
- return criterionIds(text).filter((id) => !tagged.includes(id));
74
- };
75
69
  /** Items numbered `AC-1`…, unless every one already carries its own distinct ID. */
76
70
  const numbered = (items) => {
77
71
  const ids = items.map((item) => ID.exec(item));
@@ -314,147 +308,6 @@ const green = () => healthy(() => agentWithSkills("fix.suite.fixing", implementF
314
308
  guardRests: false
315
309
  });
316
310
  //#endregion
317
- //#region src/workflows/feature/prototype.ts
318
- /** Only a `no` at this or above skips a criterion: no verdict, or an unsure one, sends it to the hands-on test. */
319
- const UNOBSERVABLE_MIN_P = .5;
320
- /** The `prototypeRounds` setting. Anything but a non-negative integer fails rather than guess a cap. */
321
- const prototypeRounds = () => {
322
- const value = vars.prototypeRounds ?? defaults.prototypeRounds;
323
- if (!/^(0|[1-9]\d*)$/.test(value)) refuse(`gtd: prototypeRounds must be a non-negative integer, got ${JSON.stringify(value)}`);
324
- return Number(value);
325
- };
326
- const CHUNK = /^(AC-\d+|Problem\b.*?)\s*:\s*(pass|fail|blocked)$/i;
327
- const UNGUIDED = /^no testing instructions$/i;
328
- /** The `## ` chunks of a prototype report; a heading in neither shape is prose, not an item. */
329
- const reportOf = (text) => {
330
- let unguided = false;
331
- const items = [];
332
- const malformed = [];
333
- for (const { line, fenced } of markFenced(text)) {
334
- const heading = fenced ? null : HEADING.exec(line);
335
- if (heading?.[1] !== "##") continue;
336
- if (UNGUIDED.test(heading[2])) unguided = true;
337
- const chunk = CHUNK.exec(heading[2]);
338
- if (chunk === null && /^(ac-\d|problem)/i.test(heading[2])) malformed.push(heading[2]);
339
- if (chunk !== null) items.push({
340
- subject: /^ac-/i.test(chunk[1]) ? chunk[1].toUpperCase() : chunk[1],
341
- verdict: chunk[2].toLowerCase()
342
- });
343
- }
344
- return {
345
- unguided,
346
- items,
347
- malformed
348
- };
349
- };
350
- /**
351
- * The report the testing turn left, refused unless it is the only change and
352
- * names each of `ids` once, with its open items: any other criterion is not
353
- * this test's to fix.
354
- */
355
- const requireReport = (ids, retest) => {
356
- const others = changes().map((c) => c.path).filter((path) => path !== PROTOTYPE);
357
- if (others.length > 0) refuse(`gtd land: prototype-report: test in a disposable place and change nothing but ${PROTOTYPE} — revert these:\n${others.map((path) => ` - ${path}`).join("\n")}`);
358
- const text = read(PROTOTYPE);
359
- if (text === void 0) return refuse(`gtd land: prototype-report: write ${PROTOTYPE}`);
360
- const report = reportOf(text);
361
- if (report.malformed.length > 0) refuse(`gtd land: prototype-report: head each chunk \`## <ID>: pass|fail|blocked\` or \`## Problem <title>: pass|fail|blocked\` — these miss it:\n${report.malformed.map((h) => ` - ${h}`).join("\n")}`);
362
- if (report.unguided && report.items.length === 0 && !retest) return {
363
- unguided: true,
364
- open: []
365
- };
366
- const elsewhere = criterionIds(read(".gtd/REQUIREMENTS.md") ?? "").filter((id) => !ids.includes(id));
367
- const count = (id) => report.items.filter((i) => i.subject === id).length;
368
- const wrong = ids.filter((id) => count(id) !== 1);
369
- if (wrong.length > 0) refuse(`gtd land: prototype-report: ${PROTOTYPE} needs exactly one chunk headed \`## <ID>: pass|fail|blocked\` for ${wrong.join(", ")}`);
370
- return {
371
- unguided: false,
372
- open: report.items.filter((i) => i.verdict !== "pass" && !elsewhere.includes(i.subject))
373
- };
374
- };
375
- /**
376
- * The criteria a person can observe by using the change, by the judge: those
377
- * tagged `(no hands-on)` are never asked about.
378
- */
379
- const observable = async () => {
380
- const requirements = read(".gtd/REQUIREMENTS.md") ?? "";
381
- const own = handsOnIds(requirements);
382
- const criteria = (criteriaOf(requirements) ?? []).filter((c) => own.includes(c.id));
383
- if (criteria.length === 0) return [];
384
- const { answers } = await judge("observable", {
385
- questions: criteria.map(observableQuestion),
386
- evidence: { requirements },
387
- message: observableMessage(),
388
- label: "Judging which criteria to test by hand"
389
- });
390
- return own.filter((id) => !answered(answers[id], "no", UNOBSERVABLE_MIN_P));
391
- };
392
- const frozen = () => requireCriteriaFrozen(REQUIREMENTS);
393
- const testing = async (ids, retest) => {
394
- await agentWithSkills("testing", prototypeTestingPrompt(criterionIds(read(".gtd/REQUIREMENTS.md") ?? "").filter((id) => !ids.includes(id)), retest), {
395
- label: retest ? "Re-testing the change by hand" : "Testing the change by hand",
396
- file: PROTOTYPE,
397
- model: coder(),
398
- system: testerSystem(),
399
- allowEmpty: true
400
- });
401
- return requireReport(ids, retest);
402
- };
403
- const fixing = async () => {
404
- await scope("fix", () => agentWithSkills("fixing", prototypeFixingPrompt(), {
405
- label: "Fixing what the hands-on test found",
406
- file: PROTOTYPE,
407
- model: coder(),
408
- system: builderSystem(),
409
- allowEmpty: true
410
- }));
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`);
412
- frozen();
413
- };
414
- /** A person's rest; resolves `true` when they changed something to test again, `false` on a clean overrule. */
415
- const askPerson = async (name, message, label) => {
416
- await human(name, {
417
- message,
418
- label,
419
- file: PROTOTYPE,
420
- acceptClean: true
421
- });
422
- if (!changes().some((c) => c.path !== ".gtd/PROTOTYPE.md")) return false;
423
- frozen();
424
- return true;
425
- };
426
- /**
427
- * Use the change by hand: a testing turn reports on every criterion a person
428
- * can observe, a fix turn takes each failure, and the change is tested again —
429
- * until the report is clean, or `prototypeRounds` fixes are spent and a person
430
- * decides. A change with no such criterion is not tested by hand.
431
- */
432
- const prototypeTest = async () => {
433
- const cap = prototypeRounds();
434
- if (cap === 0) return;
435
- await scope("prototype", async () => {
436
- const ids = await observable();
437
- if (ids.length === 0) return;
438
- let fixes = 0;
439
- let retest = false;
440
- for (;;) {
441
- const { unguided, open } = await testing(ids, retest);
442
- retest = !unguided;
443
- if (unguided) {
444
- if (!await askPerson("unguided", prototypeUnguidedMessage(), "No way to test by hand")) return;
445
- } else if (open.length === 0) return;
446
- else if (fixes < cap && open.some((i) => i.verdict === "fail")) {
447
- fixes++;
448
- await fixing();
449
- } else {
450
- if (!await askPerson("open", prototypeOpenMessage(open, fixes), "Hands-on test still failing")) return;
451
- fixes = 0;
452
- }
453
- await green();
454
- }
455
- });
456
- };
457
- //#endregion
458
311
  //#region src/workflows/feature/refactor.ts
459
312
  const REFACTOR_PACKAGE = REFACTOR;
460
313
  const isCode = (path) => path !== ".gtd" && !path.startsWith(".gtd/");
@@ -696,7 +549,6 @@ const reviewedRefactor = async (shape, options = {}) => {
696
549
  fixAccess: strayAccess(p.strays),
697
550
  lensBase: entry,
698
551
  rules: refactorRules(p.strays, p.pattern),
699
- walkthrough: true,
700
552
  lap: "refactor"
701
553
  });
702
554
  if (outcome.verdict === "signoff") return true;
@@ -826,13 +678,8 @@ const implementing = (options) => scope("implement", async () => {
826
678
  if (options.naive === void 0 || !await applied(options.naive)) await build(options);
827
679
  else requireCriteriaFrozen(REQUIREMENTS);
828
680
  await green();
829
- await prototypeTest();
830
681
  const ids = criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
831
- await run("closing", removeScript([
832
- REQUIREMENTS,
833
- REFACTOR_NOTE,
834
- PROTOTYPE
835
- ]), { label: "Closing out the implementation" });
682
+ await run("closing", removeScript([REQUIREMENTS, REFACTOR_NOTE]), { label: "Closing out the implementation" });
836
683
  return ids;
837
684
  });
838
685
  /** Resolves `true` when the human steered (edited), `false` on a clean overrule. */
@@ -900,6 +747,39 @@ const ruled = async (tip, again) => {
900
747
  tip
901
748
  } : next;
902
749
  };
750
+ /** Back to `before`: every code path, and the notes file. `.gtd/` edits (the requirements) stay. */
751
+ const untryScript = (before) => {
752
+ const code = changesSince(before).filter((c) => isCode$1(c.path));
753
+ return restoreScript(head(), {
754
+ restore: code.filter((c) => c.status !== "added").map((c) => c.path),
755
+ remove: [...code.filter((c) => c.status === "added").map((c) => c.path), NAIVE_TEST]
756
+ }, before);
757
+ };
758
+ /**
759
+ * A person tries the green pass `tip` in the working tree. Resolves
760
+ * `undefined` on a clean landing (signed off), else what they sent back;
761
+ * either way the pass, their code edits and the notes file leave the tree.
762
+ */
763
+ const tried = async (tip) => {
764
+ const before = head();
765
+ await run("applying", applyScript(tip.hash, FEEDBACK, naiveApplyFailure(tip.hash), [".gtd"]), { label: "Applying the naive pass to try it" });
766
+ await human("try", {
767
+ message: naiveTryMessage(flagModeOn()),
768
+ label: "Try the naive pass",
769
+ file: NAIVE_TEST,
770
+ acceptClean: true
771
+ });
772
+ const landed = changes();
773
+ const edits = landed.some((c) => isCode$1(c.path)) ? head() : void 0;
774
+ const notes = read(".gtd/NAIVE-TEST.md")?.trim() || void 0;
775
+ if (landed.some((c) => c.path === ".gtd/REQUIREMENTS.md")) requireCriteria(REQUIREMENTS);
776
+ await run("restoring", untryScript(before), { label: "Taking the naive pass out again" });
777
+ return landed.length === 0 ? void 0 : {
778
+ tested: tip.hash,
779
+ notes,
780
+ edits
781
+ };
782
+ };
903
783
  const decide = () => scope("seam", async () => {
904
784
  const vetting = {
905
785
  again: budget(),
@@ -909,7 +789,16 @@ const decide = () => scope("seam", async () => {
909
789
  for (;;) {
910
790
  await naivePass(retry);
911
791
  const tip = kept("naive");
912
- const outcome = await vetted(tip, vetting) ?? (flagModeOn() ? await ruled(tip, vetting.again) : {
792
+ const verdict = await vetted(tip, vetting);
793
+ if (verdict === void 0 && naiveTestOn()) {
794
+ const sentBack = await tried(tip);
795
+ if (sentBack !== void 0) {
796
+ vetting.again = budget();
797
+ retry = sentBack;
798
+ continue;
799
+ }
800
+ }
801
+ const outcome = verdict ?? (flagModeOn() ? await ruled(tip, vetting.again) : {
913
802
  reference: tip.hash,
914
803
  tip
915
804
  });
@@ -996,7 +885,11 @@ const buildFeature = async (firstBase) => {
996
885
  });
997
886
  const carried = seam.rebuild?.acceptance?.tests ?? [];
998
887
  const base = seam.refactored === true && !changesSince(seam.base).some((c) => isCode$1(c.path) && !carried.includes(c.path)) ? firstBase : seam.base;
999
- const outcome = await reviewedTail(base, base, requirements);
888
+ const outcome = await buildTail(base, {
889
+ green: true,
890
+ lensBase: base,
891
+ requirements
892
+ });
1000
893
  await featureFeedback(outcome, base, {
1001
894
  closed,
1002
895
  used
@@ -1007,16 +900,6 @@ const gather = (handedOut) => {
1007
900
  for (const id of listedIds()) handedOut.add(id);
1008
901
  return [...handedOut];
1009
902
  };
1010
- /** The quality lap, then a reviewer's walkthrough since `base` for the human. */
1011
- const reviewedTail = (base, lensBase, requirements, lap = "first") => tail((escalations, report) => humanReview(base, escalations, {
1012
- report,
1013
- walkthrough: true,
1014
- lap
1015
- }), {
1016
- green: true,
1017
- lensBase,
1018
- requirements
1019
- });
1020
903
  const featureFeedback = async (first, lensBase, ids) => {
1021
904
  let outcome = first;
1022
905
  let closed = ids.closed;
@@ -1037,7 +920,12 @@ const featureFeedback = async (first, lensBase, ids) => {
1037
920
  tier,
1038
921
  amend: true
1039
922
  });
1040
- outcome = await reviewedTail(outcome.base, lensBase, requirements, "feedback");
923
+ outcome = await buildTail(outcome.base, {
924
+ green: true,
925
+ lensBase,
926
+ requirements,
927
+ lap: "feedback"
928
+ });
1041
929
  }
1042
930
  };
1043
931
  /** Design — skipped when the sketch's own criteria are judged clear — then `buildFeature`. */
@@ -1072,7 +960,7 @@ const ordinaryStart = async () => {
1072
960
  });
1073
961
  refusePendingHandOff();
1074
962
  flagModeOn();
1075
- prototypeRounds();
963
+ naiveTestOn();
1076
964
  const stated = statedCriteria(changes(), head());
1077
965
  await unwind();
1078
966
  if (changes(".gtd/FEEDBACK.md").some((c) => c.status !== "deleted")) await human("unwind-failed", {
@@ -1088,4 +976,4 @@ const feature = shipped(ordinaryStart);
1088
976
  //#region src/workflows/feature/index.ts
1089
977
  var feature_default = feature;
1090
978
  //#endregion
1091
- 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 };
979
+ export { COMMENT, REFACTOR_PACKAGE, acceptanceFault, access, afterTail, answerProductQuestions, answerReviewQuestions, buildFeature, buildTail, collecting, criteriaOf, criterionIds, feature_default as default, defaults, design, envDefaults, feature, fixNits, 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-DHafcW7t.mjs";
4
- import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-B9Hw2AFB.mjs";
2
+ import { b as reviewNotes, x as reviewRisks } from "../chunks/steering-CCNvngSH.mjs";
3
+ import { Ct as envDefaults, G as finisherSystem, K as fixReviewNotesPrompt, M as agentWithSkills, S as coder, St as defaults, c as shipped, i as skills, l as baseline, n as summary, r as access, t as steering, y as REVIEW } from "../chunks/reserved-X2RkNHin.mjs";
4
+ import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-S04KEewi.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-DHafcW7t.mjs";
3
- import { a as refusePendingHandOff } from "../chunks/tail-B9Hw2AFB.mjs";
4
- import { i as reUnwind, t as buildTail } from "../chunks/review-BJGKwlYv.mjs";
2
+ import { Ct as envDefaults, G as finisherSystem, M as agentWithSkills, S as coder, St as defaults, c as shipped, dt as reviewFeedbackFixPrompt, ft as reviewGateBlockedMessage, i as skills, n as summary, r as access, t as steering, u as gate, v as REQUIREMENTS } from "../chunks/reserved-X2RkNHin.mjs";
3
+ import { a as refusePendingHandOff } from "../chunks/tail-S04KEewi.mjs";
4
+ import { r as reUnwind, t as buildTail } from "../chunks/review-v7sh5GF4.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-DHafcW7t.mjs";
2
+ import { Ct as envDefaults, It as flipProblems, Pt as flagMentions, Rt as requireFlag, S as coder, St as defaults, i as skills, n as summary, o as flagShipped, p as FEEDBACK, r as access, t as steering, u as gate } from "../chunks/reserved-X2RkNHin.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.";