@pmelab/gtd 56.0.0 → 58.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 (56) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +29 -35
  3. package/dist/chunks/{reserved-B8EC-WE8.mjs → reserved-BzAFXuz_.mjs} +56 -131
  4. package/dist/chunks/{review-HdrX0FJ7.mjs → review-CVqRS0mc.mjs} +2 -2
  5. package/dist/chunks/{tail-BSLah2mA.mjs → tail-JQzB21Q3.mjs} +1 -1
  6. package/dist/workflows/cleanup.mjs +2 -2
  7. package/dist/workflows/feature.mjs +99 -170
  8. package/dist/workflows/fix.mjs +2 -2
  9. package/dist/workflows/review.mjs +3 -3
  10. package/dist/workflows/rollout.mjs +1 -1
  11. package/dist/workflows.mjs +150 -297
  12. package/package.json +1 -1
  13. package/src/workflows/feature/acceptance-criteria.feature +32 -0
  14. package/src/workflows/feature/criteria-coverage.feature +0 -2
  15. package/src/workflows/feature/criteria.test.ts +1 -41
  16. package/src/workflows/feature/criteria.ts +12 -18
  17. package/src/workflows/feature/fast-path.feature +4 -1
  18. package/src/workflows/feature/feature.test.ts +3 -39
  19. package/src/workflows/feature/feature.ts +6 -8
  20. package/src/workflows/feature/lens-models.feature +0 -2
  21. package/src/workflows/feature/lens-review.feature +0 -2
  22. package/src/workflows/feature/naive-acceptance.feature +4 -0
  23. package/src/workflows/feature/naive-test.feature +263 -0
  24. package/src/workflows/feature/one-step-land.feature +4 -1
  25. package/src/workflows/feature/planning.ts +2 -0
  26. package/src/workflows/feature/refactor-absorbs.feature +4 -1
  27. package/src/workflows/feature/refactor-first.feature +4 -1
  28. package/src/workflows/feature/refactor-lens-models.feature +4 -0
  29. package/src/workflows/feature/refactor-rebuild.feature +4 -1
  30. package/src/workflows/feature/refactor-review.feature +20 -1
  31. package/src/workflows/feature/release-flag.feature +167 -0
  32. package/src/workflows/feature/release-flag.test.ts +49 -0
  33. package/src/workflows/feature/release-flag.ts +46 -0
  34. package/src/workflows/feature/review-gate.feature +1 -3
  35. package/src/workflows/feature/reviewer-walkthrough.feature +0 -2
  36. package/src/workflows/feature/risk-tier.feature +32 -0
  37. package/src/workflows/feature/seam-rejection.feature +8 -1
  38. package/src/workflows/feature/seam.feature +8 -1
  39. package/src/workflows/feature/seam.test.ts +21 -9
  40. package/src/workflows/feature/seam.ts +60 -14
  41. package/src/workflows/feature/standards.feature +0 -2
  42. package/src/workflows/shared/options.test.ts +3 -10
  43. package/src/workflows/shared/options.ts +5 -6
  44. package/src/workflows/shared/prose.ts +0 -6
  45. package/src/workflows/shared/skills.test.ts +0 -4
  46. package/src/workflows/shared/skills.ts +0 -5
  47. package/src/workflows/shared/steps.ts +1 -1
  48. package/src/workflows/shared/text.test.ts +58 -69
  49. package/src/workflows/shared/text.ts +79 -162
  50. package/src/workflows/shared/vars.ts +2 -4
  51. package/src/workflows/feature/flag-mode.feature +0 -88
  52. package/src/workflows/feature/hands-on-skip.feature +0 -316
  53. package/src/workflows/feature/prototype-rounds.feature +0 -156
  54. package/src/workflows/feature/prototype.feature +0 -428
  55. package/src/workflows/feature/prototype.test.ts +0 -43
  56. package/src/workflows/feature/prototype.ts +0 -198
@@ -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 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";
3
+ import { B as designSystem, Ct as testFileGlobs, D as planner, F as clearCriteriaMessage, I as clearCriteriaQuestion, J as implementBuildingPrompt, L as criterionLine, M as buildReviewCollectingPrompt, P as builderSystem, Q as naiveTryMessage, R as designGateAnswerMessage, S as collecting, St as envDefaults, V as designTriagePrompt, X as naiveApplyFailure, Y as implementFixPrompt, Z as naivePassPrompt, _ as REQUIREMENTS, _t as unwindFailedMessage, at as refactorFixPrompt, d as healthy, et as normaliseCriteriaScript, f as FEEDBACK, g as REFACTOR_NOTE, gt as startGateBlockedMessage, h as REFACTOR, ht as seamSteerMessage, i as skills, it as refactorDroppedRefusal, j as agentWithSkills, l as gate, mt as seamByHandMessage, n as summary, o as naiveTestOn, ot as refactorNotePrompt, p as NAIVE_TEST, pt as reviewerSystem, q as idleMessage, r as access, rt as refactorBuildingPrompt, s as shipped, st as refactorRules, t as steering, vt as unwindFailure, w as fixNits, x as coder, xt as defaults, y as answerReviewQuestions, z as designImpactPrompt } from "../chunks/reserved-BzAFXuz_.mjs";
4
+ import { a as refusePendingHandOff } from "../chunks/tail-JQzB21Q3.mjs";
5
+ import { n as isCode$1, r as reUnwind, t as buildTail } from "../chunks/review-CVqRS0mc.mjs";
6
6
  import { posix } from "node:path";
7
7
  //#region src/workflows/shared/fences.ts
8
8
  const markFenced = (text) => {
@@ -23,14 +23,50 @@ const HEADING = /^ {0,3}(#{1,6})\s+(.*?)\s*#*\s*$/;
23
23
  /** A list item, its checkbox dropped: its text. */
24
24
  const ITEM = /^ ?(?:[-*+]|\d+[.)])\s+(?:\[[ xX]\]\s+)?(.*)$/;
25
25
  //#endregion
26
+ //#region src/workflows/feature/release-flag.ts
27
+ const NAME = /^existing:\s*`?([A-Za-z_][\w.-]*)`?$/i;
28
+ const isFlagHeading = (h) => h !== null && h[1].length === 2 && /^release flag$/i.test(h[2].trim());
29
+ /** The first line under `## Release Flag`, `undefined` without the section. */
30
+ const requestLine = (text) => {
31
+ const lines = markFenced(text);
32
+ const at = lines.findIndex((l) => !l.fenced && isFlagHeading(HEADING.exec(l.line)));
33
+ if (at === -1) return void 0;
34
+ const body = lines.slice(at + 1);
35
+ const end = body.findIndex((l) => !l.fenced && HEADING.test(l.line));
36
+ return (end === -1 ? body : body.slice(0, end)).find((l) => l.line.trim() !== "")?.line.trim();
37
+ };
38
+ /** What `text` asks for, or why it cannot be read. No section asks for no flag. */
39
+ const releaseFlagOf = (text) => {
40
+ const line = requestLine(text);
41
+ if (line === void 0 || /^none$/i.test(line)) return { kind: "none" };
42
+ if (/^new$/i.test(line)) return { kind: "new" };
43
+ const name = NAME.exec(line)?.[1];
44
+ return name === void 0 ? { unreadable: line } : {
45
+ kind: "existing",
46
+ name
47
+ };
48
+ };
49
+ /** Refuse `file` when its `## Release Flag` section is neither `none`, `new` nor `existing: <NAME>`. */
50
+ const requireReleaseFlag = (file) => {
51
+ const request = releaseFlagOf(read(file) ?? "");
52
+ if (!("unreadable" in request)) return;
53
+ refuse(`gtd land: release-flag: ${file}'s "## Release Flag" reads ${JSON.stringify(request.unreadable)} — its first line must be \`new\`, \`existing: <NAME>\` or \`none\`, or leave the section out for no flag`);
54
+ };
55
+ /** What `.gtd/REQUIREMENTS.md` asks for now; a person's unreadable edit is refused. */
56
+ const requestedFlag = () => {
57
+ requireReleaseFlag(REQUIREMENTS);
58
+ return releaseFlagOf(read(".gtd/REQUIREMENTS.md") ?? "");
59
+ };
60
+ /** Whether the change goes behind a release flag: only then does the seam judge rule. */
61
+ const flagged = () => requestedFlag().kind !== "none";
62
+ //#endregion
26
63
  //#region src/workflows/feature/criteria.ts
27
64
  /** Below this the judge's `yes` is not trusted: a wrong skip builds against criteria nobody can check. */
28
65
  const CLEAR_MIN_P = .8;
29
- const ID = /^(AC-\d+)(\s*\(no hands-on\))?:\s*(.*)$/;
66
+ const ID = /^(AC-\d+):\s*(.*)$/;
30
67
  const criterion = (id, as = id[1]) => ({
31
68
  id: as,
32
- text: id[3],
33
- ...id[2] === void 0 ? {} : { noHandsOn: true }
69
+ text: id[2]
34
70
  });
35
71
  /** 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
72
  const listUnder = (text, title, level) => {
@@ -67,11 +103,6 @@ const criteriaOf = (text) => listUnder(text, /^acceptance criteria$/i, 2)?.map((
67
103
  });
68
104
  /** The ID'd criteria of `text`. */
69
105
  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
106
  /** Items numbered `AC-1`…, unless every one already carries its own distinct ID. */
76
107
  const numbered = (items) => {
77
108
  const ids = items.map((item) => ID.exec(item));
@@ -120,14 +151,15 @@ const requireCriteria = (file) => {
120
151
  refuse(`gtd land: acceptance-criteria: ${file} has ${criteria.length === 0 ? `no "## Acceptance Criteria" list` : unnamed.length > 0 ? `${unnamed.length} criterion without an ID:\n${unnamed.map((c) => ` - ${c.text}`).join("\n")}` : `duplicate IDs: ${[...new Set(twice)].join(", ")}`} — list every criterion under "## Acceptance Criteria" as "- AC-<n>: <checkable criterion>", each ID unique and never reused`);
121
152
  };
122
153
  const isImpactHeading = (h) => h !== null && h[1].length === 2 && /^impact$/i.test(h[2].trim());
154
+ const isImpactTurnHeading = (h) => h !== null && h[1].length === 2 && /^(impact|release flag)$/i.test(h[2].trim());
123
155
  /** Whether `text` has an unfenced, level-2 `## Impact` heading. */
124
156
  const hasImpact = (text) => markFenced(text).some((l) => !l.fenced && isImpactHeading(HEADING.exec(l.line)));
125
- /** `text` without its `## Impact` section, whitespace collapsed. */
157
+ /** `text` without its `## Impact` and `## Release Flag` sections, whitespace collapsed. */
126
158
  const withoutImpact = (text) => {
127
159
  let inImpact = false;
128
160
  return markFenced(text).filter((l) => {
129
161
  const h = l.fenced ? null : HEADING.exec(l.line);
130
- if (h !== null && h[1].length <= 2) inImpact = isImpactHeading(h);
162
+ if (h !== null && h[1].length <= 2) inImpact = isImpactTurnHeading(h);
131
163
  return !inImpact;
132
164
  }).map((l) => l.line).join(" ").replace(/\s+/g, " ").trim();
133
165
  };
@@ -144,12 +176,12 @@ const requireImpact = (file) => {
144
176
  if (hasImpact(text)) return;
145
177
  refuse(`gtd land: impact: ${file} has no "## Impact" section — explore the codebase and add "## Impact" naming the units, public surfaces and surprises this change touches`);
146
178
  };
147
- /** Refuse a turn that changed `file` beyond its `## Impact` section. */
179
+ /** Refuse a turn that changed `file` beyond its `## Impact` and `## Release Flag` sections. */
148
180
  const requireImpactOnly = (file) => {
149
181
  const change = changes().get(file);
150
182
  if (change === void 0) return;
151
183
  if (withoutImpact(change.before ?? "") === withoutImpact(change.after ?? "")) return;
152
- refuse(`gtd land: impact-only: this turn may only add "## Impact" to ${file} — restore everything else`);
184
+ refuse(`gtd land: impact-only: this turn may only add "## Impact" (and "## Release Flag") to ${file} — restore everything else`);
153
185
  };
154
186
  const idNumber = (id) => Number(id.slice(3));
155
187
  /** Why `listed` renumbers or reuses an earlier ID; `undefined` when it does neither. */
@@ -196,6 +228,7 @@ const impact = async () => {
196
228
  requireCriteriaFrozen(REQUIREMENTS);
197
229
  requireImpact(REQUIREMENTS);
198
230
  requireImpactOnly(REQUIREMENTS);
231
+ requireReleaseFlag(REQUIREMENTS);
199
232
  };
200
233
  /**
201
234
  * The skip path: when the sketch stated clear, checkable criteria, write them
@@ -299,6 +332,7 @@ const design = (base, guard) => scope("design", async () => {
299
332
  requireReplies(REQUIREMENTS);
300
333
  requireCriteria(REQUIREMENTS);
301
334
  requireImpact(REQUIREMENTS);
335
+ requireReleaseFlag(REQUIREMENTS);
302
336
  guard?.();
303
337
  } while (await noteGate(REQUIREMENTS, answerProductQuestions));
304
338
  });
@@ -314,147 +348,6 @@ const green = () => healthy(() => agentWithSkills("fix.suite.fixing", implementF
314
348
  guardRests: false
315
349
  });
316
350
  //#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
351
  //#region src/workflows/feature/refactor.ts
459
352
  const REFACTOR_PACKAGE = REFACTOR;
460
353
  const isCode = (path) => path !== ".gtd" && !path.startsWith(".gtd/");
@@ -805,7 +698,7 @@ const applied = async (tip) => {
805
698
  return !wrote(".gtd/FEEDBACK.md") && codeChanges().length > 0;
806
699
  };
807
700
  const build = async (options) => {
808
- await agentWithSkills("building", implementBuildingPrompt(options.reference, options.tier, options.amend, options.afterRefactor, flagModeOn(), options.rebuild), {
701
+ await agentWithSkills("building", implementBuildingPrompt(options.reference, options.amend, options.afterRefactor, requestedFlag(), options.rebuild), {
809
702
  label: "Implementing",
810
703
  model: coder(),
811
704
  system: builderSystem(),
@@ -825,19 +718,14 @@ const implementing = (options) => scope("implement", async () => {
825
718
  if (options.naive === void 0 || !await applied(options.naive)) await build(options);
826
719
  else requireCriteriaFrozen(REQUIREMENTS);
827
720
  await green();
828
- await prototypeTest();
829
721
  const ids = criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
830
- await run("closing", removeScript([
831
- REQUIREMENTS,
832
- REFACTOR_NOTE,
833
- PROTOTYPE
834
- ]), { label: "Closing out the implementation" });
722
+ await run("closing", removeScript([REQUIREMENTS, REFACTOR_NOTE]), { label: "Closing out the implementation" });
835
723
  return ids;
836
724
  });
837
725
  /** Resolves `true` when the human steered (edited), `false` on a clean overrule. */
838
726
  const steer = async (retry) => {
839
727
  await human("steer", {
840
- message: seamSteerMessage(retry, flagModeOn()),
728
+ message: seamSteerMessage(retry, flagged()),
841
729
  label: "Steer or overrule the naive pass",
842
730
  file: REQUIREMENTS,
843
731
  acceptClean: true
@@ -899,6 +787,39 @@ const ruled = async (tip, again) => {
899
787
  tip
900
788
  } : next;
901
789
  };
790
+ /** Back to `before`: every code path, and the notes file. `.gtd/` edits (the requirements) stay. */
791
+ const untryScript = (before) => {
792
+ const code = changesSince(before).filter((c) => isCode$1(c.path));
793
+ return restoreScript(head(), {
794
+ restore: code.filter((c) => c.status !== "added").map((c) => c.path),
795
+ remove: [...code.filter((c) => c.status === "added").map((c) => c.path), NAIVE_TEST]
796
+ }, before);
797
+ };
798
+ /**
799
+ * A person tries the green pass `tip` in the working tree. Resolves
800
+ * `undefined` on a clean landing (signed off), else what they sent back;
801
+ * either way the pass, their code edits and the notes file leave the tree.
802
+ */
803
+ const tried = async (tip) => {
804
+ const before = head();
805
+ await run("applying", applyScript(tip.hash, FEEDBACK, naiveApplyFailure(tip.hash), [".gtd"]), { label: "Applying the naive pass to try it" });
806
+ await human("try", {
807
+ message: naiveTryMessage(flagged()),
808
+ label: "Try the naive pass",
809
+ file: NAIVE_TEST,
810
+ acceptClean: true
811
+ });
812
+ const landed = changes();
813
+ const edits = landed.some((c) => isCode$1(c.path)) ? head() : void 0;
814
+ const notes = read(".gtd/NAIVE-TEST.md")?.trim() || void 0;
815
+ if (landed.some((c) => c.path === ".gtd/REQUIREMENTS.md")) requireCriteria(REQUIREMENTS);
816
+ await run("restoring", untryScript(before), { label: "Taking the naive pass out again" });
817
+ return landed.length === 0 ? void 0 : {
818
+ tested: tip.hash,
819
+ notes,
820
+ edits
821
+ };
822
+ };
902
823
  const decide = () => scope("seam", async () => {
903
824
  const vetting = {
904
825
  again: budget(),
@@ -908,7 +829,16 @@ const decide = () => scope("seam", async () => {
908
829
  for (;;) {
909
830
  await naivePass(retry);
910
831
  const tip = kept("naive");
911
- const outcome = await vetted(tip, vetting) ?? (flagModeOn() ? await ruled(tip, vetting.again) : {
832
+ const verdict = await vetted(tip, vetting);
833
+ if (verdict === void 0 && naiveTestOn()) {
834
+ const sentBack = await tried(tip);
835
+ if (sentBack !== void 0) {
836
+ vetting.again = budget();
837
+ retry = sentBack;
838
+ continue;
839
+ }
840
+ }
841
+ const outcome = verdict ?? (flagged() ? await ruled(tip, vetting.again) : {
912
842
  reference: tip.hash,
913
843
  tip
914
844
  });
@@ -977,7 +907,7 @@ const unwind = () => {
977
907
  return run("unwind", revertScript(commit, FEEDBACK, unwindFailure(commit)), { label: "Unwinding your input" });
978
908
  };
979
909
  /**
980
- * A naive pass (judged only with `flagMode` on), then implement and the
910
+ * A naive pass (judged only when a release flag is asked for), then implement and the
981
911
  * quality lap, then the review; design must have run since `firstBase`.
982
912
  */
983
913
  const buildFeature = async (firstBase) => {
@@ -1069,8 +999,7 @@ const ordinaryStart = async () => {
1069
999
  file: ".gtd/TODO.md"
1070
1000
  });
1071
1001
  refusePendingHandOff();
1072
- flagModeOn();
1073
- prototypeRounds();
1002
+ naiveTestOn();
1074
1003
  const stated = statedCriteria(changes(), head());
1075
1004
  await unwind();
1076
1005
  if (changes(".gtd/FEEDBACK.md").some((c) => c.status !== "deleted")) await human("unwind-failed", {
@@ -1086,4 +1015,4 @@ const feature = shipped(ordinaryStart);
1086
1015
  //#region src/workflows/feature/index.ts
1087
1016
  var feature_default = feature;
1088
1017
  //#endregion
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 };
1018
+ 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
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";
3
+ import { G as fixReviewNotesPrompt, St 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, xt as defaults } from "../chunks/reserved-BzAFXuz_.mjs";
4
+ import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-JQzB21Q3.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 { 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";
2
+ import { St as envDefaults, W as finisherSystem, _ as REQUIREMENTS, dt as reviewGateBlockedMessage, i as skills, j as agentWithSkills, l as gate, n as summary, r as access, s as shipped, t as steering, ut as reviewFeedbackFixPrompt, x as coder, xt as defaults } from "../chunks/reserved-BzAFXuz_.mjs";
3
+ import { a as refusePendingHandOff } from "../chunks/tail-JQzB21Q3.mjs";
4
+ import { r as reUnwind, t as buildTail } from "../chunks/review-CVqRS0mc.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 { 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";
2
+ import { Ft as flipProblems, Lt as requireFlag, Nt as flagMentions, St as envDefaults, a as flagShipped, f as FEEDBACK, i as skills, l as gate, n as summary, r as access, t as steering, x as coder, xt as defaults } from "../chunks/reserved-BzAFXuz_.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.";