@pmelab/gtd 36.0.0 → 38.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 (62) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +30 -9
  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-BAv71I4i.mjs → reserved-sfMdy7v7.mjs} +83 -22
  7. package/dist/chunks/{tail-kar1zhOb.mjs → tail-Cgca5Gp-.mjs} +1 -1
  8. package/dist/chunks/{wire-D3Rc5r22.mjs → wire-CSsa3Wxd.mjs} +117 -2
  9. package/dist/gtd.bundle.mjs +498 -56
  10. package/dist/testkit.mjs +115 -3
  11. package/dist/wire.mjs +2 -2
  12. package/dist/workflows/cleanup.mjs +2 -2
  13. package/dist/workflows/feature.mjs +277 -58
  14. package/dist/workflows/fix.mjs +2 -2
  15. package/dist/workflows/review.mjs +2 -2
  16. package/dist/workflows/rollout.mjs +1 -1
  17. package/dist/workflows.mjs +355 -75
  18. package/package.json +1 -1
  19. package/src/cli/Cli.test.ts +10 -3
  20. package/src/cli/Cli.ts +76 -1
  21. package/src/cli/Program.test.ts +1 -0
  22. package/src/cli/Program.ts +28 -0
  23. package/src/cli/ToolCommands.ts +3 -0
  24. package/src/edge/Edge.ts +11 -0
  25. package/src/flows/runtime.ts +11 -0
  26. package/src/replay/Replay.ts +51 -2
  27. package/src/testkit/Layers.ts +1 -0
  28. package/src/testkit/steps.ts +35 -0
  29. package/src/wire/BeatDocument.ts +12 -2
  30. package/src/wire/BeatStatus.ts +3 -0
  31. package/src/wire/ContextLimit.fixture.ts +30 -0
  32. package/src/wire/ContextLimit.test.ts +96 -0
  33. package/src/wire/ContextLimit.ts +169 -0
  34. package/src/wire/index.ts +1 -0
  35. package/src/wire/types.ts +7 -0
  36. package/src/workflow/Workflow.ts +9 -1
  37. package/src/workflows/feature/context-limit.feature +37 -0
  38. package/src/workflows/feature/coverage.ts +22 -8
  39. package/src/workflows/feature/criteria.test.ts +17 -1
  40. package/src/workflows/feature/criteria.ts +26 -3
  41. package/src/workflows/feature/feature.ts +69 -16
  42. package/src/workflows/feature/index.ts +1 -0
  43. package/src/workflows/feature/refactor-absorbs.feature +12 -5
  44. package/src/workflows/feature/refactor-first.feature +11 -2
  45. package/src/workflows/feature/refactor-lens-models.feature +4 -0
  46. package/src/workflows/feature/refactor-rebuild.feature +269 -0
  47. package/src/workflows/feature/refactor-review.feature +45 -1
  48. package/src/workflows/feature/refactor.ts +9 -3
  49. package/src/workflows/feature/seam.test.ts +108 -3
  50. package/src/workflows/feature/seam.ts +169 -31
  51. package/src/workflows/feature/slicing-cap.feature +14 -0
  52. package/src/workflows/feature/slicing.feature +225 -0
  53. package/src/workflows/feature/slicing.test.ts +49 -0
  54. package/src/workflows/feature/slicing.ts +105 -0
  55. package/src/workflows/index.ts +1 -0
  56. package/src/workflows/shared/fences.ts +5 -0
  57. package/src/workflows/shared/options.test.ts +11 -2
  58. package/src/workflows/shared/options.ts +19 -4
  59. package/src/workflows/shared/steps.ts +2 -0
  60. package/src/workflows/shared/text.ts +129 -28
  61. package/src/workflows/shared/vars.ts +1 -0
  62. package/src/workflows/shared/workflows.steps.ts +13 -0
@@ -16934,6 +16934,7 @@ const defaults = {
16934
16934
  refactorFlagPattern: "\\bGTD_FLAG_\\w+",
16935
16935
  seamAllow: [...testFileGlobs, ".gtd/**"].join(", "),
16936
16936
  flagMode: "true",
16937
+ sliceMaxCriteria: "8",
16937
16938
  flag_risk_tier: "0",
16938
16939
  qualityReviews: "correctness, owasp-security, ponytail-review, test-audit, conventions, spec-challenge",
16939
16940
  refactorQualityReviews: "correctness, ponytail-review, conventions"
@@ -17393,6 +17394,24 @@ printf '%s\\n' ${[
17393
17394
  ].map(quote).join(" ")} > ${quote(".gtd/REQUIREMENTS.md")}
17394
17395
  `;
17395
17396
  };
17397
+ const slicingPrompt = (cap) => `${stateFileRules}
17398
+ - Read \`.gtd/REQUIREMENTS.md\`: its \`## Acceptance Criteria\` are settled
17399
+ and more than ${cap}, too many to build in one slice
17400
+ - Group them into slices in \`.gtd/SLICES.md\`, one \`## <title>\` heading per
17401
+ slice over a \`- AC-<n>\` list item per criterion it builds. Put criteria of
17402
+ the same flow or subsystem together; every criterion in exactly one slice,
17403
+ at most ${cap} per slice
17404
+ - Keep it coarse: as few slices as the cap allows, never one per criterion
17405
+ - Order the slices so each builds on the ones before: they run in that order,
17406
+ each from where the previous one landed
17407
+ - Change nothing but \`.gtd/SLICES.md\` — leave the criteria as they are
17408
+ - Leave everything uncommitted and finish your turn — do not commit
17409
+ `;
17410
+ /** The line telling a slice's turn which criteria are its own; empty for a single slice. */
17411
+ const sliceLine = (ids) => ids === void 0 ? "" : `- This slice builds ${ids.join(", ")} only: every rule here about the criteria
17412
+ means these. The others are for other slices, before or after it — leave
17413
+ them unbuilt and untested
17414
+ `;
17396
17415
  const designSystem = () => `${designPersona}
17397
17416
 
17398
17417
  ${agentConduct}`;
@@ -17436,9 +17455,11 @@ ${strays.map((path) => ` - \`${displayPath(path)}\``).join("\n")}
17436
17455
  - Introduce no new flag read and add no second read of an existing one:
17437
17456
  every match of \`${pattern}\` may move, never increase in number`;
17438
17457
  const withRules = (rules) => rules === void 0 ? "" : `${rules}\n`;
17439
- const refactorBuildingPrompt = ({ pkg, strays, pattern, reference, feedback = false }) => `${stateFileRules}
17458
+ const refactorBuildingPrompt = ({ pkg, strays, pattern, reference, feedback = false, note = false }) => `${stateFileRules}
17440
17459
  - The package to implement is \`${pkg}\`; never delete it
17441
- ${feedback ? `- The package carries a \`## Review feedback\` section: a human's review of the
17460
+ ${note ? `- The package's \`## Refactor note\` section names the seams to open and the
17461
+ shape each takes, written from the whole naive pass: build exactly those
17462
+ ` : ""}${feedback ? `- The package carries a \`## Review feedback\` section: a human's review of the
17442
17463
  last refactor. Act on it, within the rules below
17443
17464
  ` : ""}${refactorRules(strays, pattern)}
17444
17465
  ${reference === void 0 ? "" : `- Reference only: \`git show ${reference} -- ${strays.map(quote).join(" ")}\` shows an
@@ -18037,9 +18058,9 @@ re-implement. It changed these files outside the expected set:
18037
18058
  ${listed(retry.strays)}
18038
18059
  Start over and choose a different approach; do not rebuild the same diff.
18039
18060
  `;
18040
- const naivePassPrompt = (retry) => `${stateFileRules}
18061
+ const naivePassPrompt = (retry, slice) => `${stateFileRules}
18041
18062
  - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
18042
- ${criteriaBinding(false)}
18063
+ ${sliceLine(slice)}${criteriaBinding(false)}
18043
18064
  - Implement them the quickest way that works — the naive version. Do not
18044
18065
  restructure, abstract or refactor around the change; it may land exactly as
18045
18066
  you leave it, and a reviewer makes it fit
@@ -18057,22 +18078,54 @@ ${criteriaBinding(false)}
18057
18078
  - Leave \`.gtd/\` untouched
18058
18079
  - Leave everything uncommitted and finish your turn — do not commit
18059
18080
  ${retry === void 0 ? "" : naiveRetry(retry)}`;
18060
- const implementBuildingPrompt = (reference, tier, amend, afterRefactor, flagMode = true) => `${stateFileRules}
18061
- - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
18062
- ${criteriaBinding(true)}
18063
- ${afterRefactor === true ? `- A reviewed refactor has just landed and may already carry part of the change,
18081
+ const carries = (rebuild) => (rebuild.acceptance?.ids.length ?? 0) > 0;
18082
+ const rebuildLines = ({ note, acceptance }) => `${note === void 0 ? "" : `- The refactor note (\`.gtd/REFACTOR-NOTE.md\`) named the seams the refactor
18083
+ opened; plug the change into them:
18084
+
18085
+ ${note.trimEnd().split("\n").map((line) => line === "" ? "" : ` > ${line}`).join("\n")}
18086
+
18087
+ `}${acceptance === void 0 || !carries({ acceptance }) ? "" : `- Carry over the naive pass's acceptance tests for ${acceptance.ids.join(", ")}, the
18088
+ one part of it you take as it is: \`git show ${acceptance.hash} -- ${acceptance.tests.map(quote).join(" ")}\`.
18089
+ Copy each test with its \`[AC-<n>]\` name into the tree as the refactor left
18090
+ it — check a file out of the tip whole only where the refactor did not touch
18091
+ it — changing only what reaches the release flag. A turn whose changed tests
18092
+ leave an ID out is refused, and the suite must be green with them
18093
+ `}`;
18094
+ const refactorNotePrompt = (reference, strays) => `${stateFileRules}
18095
+ - The seam judge ruled the naive pass refactor-first: it is never applied.
18096
+ A behaviour-neutral refactor lands first, then the change is rebuilt on
18097
+ the refactored base. Both turns read the note you write
18098
+ - Read the whole naive pass: \`git show ${reference}\`. It changed these
18099
+ existing files outside the allowlist:
18100
+ ${listed(strays)}
18101
+ - Write \`.gtd/REFACTOR-NOTE.md\`: which seams the refactor opens (the existing
18102
+ code to restructure, by path and symbol) and the shape each takes — the
18103
+ signatures and where the new behaviour will plug in. Name only the files
18104
+ listed above: the refactor may change no other path. Never the feature's own
18105
+ behaviour, and no flag read: the refactor adds neither
18106
+ - Change nothing else — a turn that does is refused
18107
+ - Leave everything uncommitted and finish your turn — do not commit
18108
+ `;
18109
+ const afterRefactorLine = (rebuild) => `- A reviewed refactor has just landed and may already carry part of the change,
18064
18110
  though never a release flag. Build only what is still missing, held to every
18065
- rule below; if nothing is, change nothing and finish your turn
18066
- ` : ""}${amend === true ? `- The change already exists in the tree from an earlier lap; amend it to meet
18111
+ rule below; if nothing is, ${carries(rebuild) ? "carry over the acceptance tests below and change nothing else" : "change nothing and finish your turn"}
18112
+ `;
18113
+ const flagLine = (flagMode, tier, amend, sliced) => !flagMode ? "- Add no release flag — this project ships without release flags" : tier === "low" ? "- Add no release flag — this change is low risk tier" : amend === true ? "- Keep the release flag the earlier lap added, if it added one; otherwise put\n the behaviour behind a new release flag" : sliced ? `- Put the new behaviour behind the release flag an earlier slice of this
18114
+ change added; only when none did, behind a new one, following the
18115
+ repository's flag conventions` : `- Put the new behaviour behind a new release flag, following the
18116
+ repository's flag conventions`;
18117
+ const implementBuildingPrompt = (reference, tier, amend, afterRefactor, flagMode = true, rebuild = {}, slice) => `${stateFileRules}
18118
+ - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
18119
+ ${sliceLine(slice)}${criteriaBinding(true)}
18120
+ ${afterRefactor === true ? afterRefactorLine(rebuild) : ""}${amend === true ? `- The change already exists in the tree from an earlier lap; amend it to meet
18067
18121
  the re-derived requirements instead of rewriting it
18068
18122
  ` : flagMode ? `- Implement them properly, fitting the codebase's seams and conventions
18069
18123
  ` : `- Implement them the quickest way that works — the naive version. Do not
18070
18124
  restructure, abstract or refactor around the change
18071
18125
  `}${reference === void 0 || amend === true ? "" : `- Reference only: \`git show ${reference}\` shows an earlier naive attempt.
18072
18126
  Read it for ideas; never apply, cherry-pick or check it out — write
18073
- the change fresh
18074
- `}${!flagMode ? "- Add no release flag — this project ships without release flags" : tier === "low" ? "- Add no release flag — this change is low risk tier" : amend === true ? "- Keep the release flag the earlier lap added, if it added one; otherwise put\n the behaviour behind a new release flag" : `- Put the new behaviour behind a new release flag, following the
18075
- repository's flag conventions`}
18127
+ the change fresh${carries(rebuild) ? ", its acceptance tests excepted (below)" : ""}
18128
+ `}${rebuildLines(rebuild)}${flagLine(flagMode, tier, amend, slice !== void 0)}
18076
18129
  - Write the tests first, then the code that makes them pass. Name each
18077
18130
  criterion's ID in the test that covers it (\`[AC-3] …\`): once the suite is
18078
18131
  green a judge checks every ID has a passing test, and sends uncovered ones back
@@ -18153,9 +18206,9 @@ const coverageMessage = () => `Judging acceptance-criteria coverage. Run \`gtd j
18153
18206
  \`yes\`/\`no\` verdict per criterion ID — or land without a verdict, which
18154
18207
  counts every criterion as uncovered.
18155
18208
  `;
18156
- const coveringPrompt = (uncovered) => `${stateFileRules}
18209
+ const coveringPrompt = (uncovered, slice) => `${stateFileRules}
18157
18210
  - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
18158
- ${criteriaBinding(true)}
18211
+ ${sliceLine(slice)}${criteriaBinding(true)}
18159
18212
  - A judge found no passing test in this change that covers ${uncovered.join(", ")}.
18160
18213
  Write a behaviour-level test for each, naming its ID in the test or scenario
18161
18214
  name (\`[${uncovered[0]}] …\`), and fix the code if the test shows it falls short
@@ -18180,7 +18233,9 @@ const ESCALATION = ".gtd/ESCALATION.md";
18180
18233
  const REQUIREMENTS = ".gtd/REQUIREMENTS.md";
18181
18234
  const REVIEW = ".gtd/REVIEW.md";
18182
18235
  const REFACTOR = ".gtd/REFACTOR.md";
18236
+ const REFACTOR_NOTE = ".gtd/REFACTOR-NOTE.md";
18183
18237
  const QUALITY = ".gtd/QUALITY.md";
18238
+ const SLICES = ".gtd/SLICES.md";
18184
18239
  const planner = () => env.plannerModel ?? "";
18185
18240
  const coder = () => env.coderModel ?? "";
18186
18241
  const fixCheck = (rules) => agentWithSkills("fix", buildFixPrompt(rules), {
@@ -37692,12 +37747,17 @@ const ShippedOptions = Struct({
37692
37747
  reviewSeverityMinP: setting,
37693
37748
  refactorFlagPattern: setting,
37694
37749
  seamAllow: setting,
37750
+ sliceMaxCriteria: setting,
37695
37751
  qualityReviews: setting,
37696
37752
  refactorQualityReviews: setting,
37697
37753
  testCommand: setting,
37698
37754
  plannerModel: setting,
37699
37755
  coderModel: setting,
37700
- flagMode: optionalWith(Boolean$, { exact: true })
37756
+ flagMode: optionalWith(Boolean$, { exact: true }),
37757
+ contextLimit: optionalWith(Struct({
37758
+ soft: optionalWith(Number$, { exact: true }),
37759
+ hard: optionalWith(Number$, { exact: true })
37760
+ }), { exact: true })
37701
37761
  });
37702
37762
  const over = (base, options) => ({
37703
37763
  ...base,
@@ -37709,12 +37769,13 @@ const flagModeOn = () => {
37709
37769
  if (value !== "true" && value !== "false") refuse(`gtd: flagMode must be "true" or "false", got ${JSON.stringify(value)}`);
37710
37770
  return value === "true";
37711
37771
  };
37772
+ const limited = (flow, context) => context === void 0 ? flow : () => scope({ context }, flow);
37712
37773
  /** A shipped flow as a factory: its options override the settings it declares, each in its own kind. */
37713
37774
  const shipped = (flow, doors, base) => defineWorkflow(standardSchemaV1(ShippedOptions, {
37714
37775
  onExcessProperty: "error",
37715
37776
  errors: "all"
37716
- }), (options) => ({
37717
- flow,
37777
+ }), ({ contextLimit, ...options }) => ({
37778
+ flow: limited(flow, contextLimit),
37718
37779
  defaults: over(defaults, options),
37719
37780
  envDefaults: over(envDefaults, options),
37720
37781
  doors,
@@ -37727,14 +37788,14 @@ const FlagOptions = Struct({
37727
37788
  const flagShipped = (flow, name) => defineWorkflow(standardSchemaV1(FlagOptions, {
37728
37789
  onExcessProperty: "error",
37729
37790
  errors: "all"
37730
- }), (options) => {
37791
+ }), ({ contextLimit, ...options }) => {
37731
37792
  const flagDefaults = {
37732
37793
  ...defaults,
37733
37794
  flag: "",
37734
37795
  flagRegistry: defaultFlagRegistry
37735
37796
  };
37736
37797
  return {
37737
- flow,
37798
+ flow: limited(flow, contextLimit),
37738
37799
  defaults: over(flagDefaults, options),
37739
37800
  envDefaults: over(envDefaults, options),
37740
37801
  doors: { [name]: {
@@ -37867,13 +37928,15 @@ const markFenced = (text) => {
37867
37928
  };
37868
37929
  });
37869
37930
  };
37931
+ /** A markdown heading: its `#` run, then its text. */
37932
+ const HEADING = /^ {0,3}(#{1,6})\s+(.*?)\s*#*\s*$/;
37933
+ /** A list item, its checkbox dropped: its text. */
37934
+ const ITEM = /^ ?(?:[-*+]|\d+[.)])\s+(?:\[[ xX]\]\s+)?(.*)$/;
37870
37935
  //#endregion
37871
37936
  //#region src/workflows/feature/criteria.ts
37872
37937
  /** Below this the judge's `yes` is not trusted: a wrong skip builds against criteria nobody can check. */
37873
37938
  const CLEAR_MIN_P = .8;
37874
- const HEADING = /^ {0,3}(#{1,6})\s+(.*?)\s*#*\s*$/;
37875
- const ITEM = /^ ?(?:[-*+]|\d+[.)])\s+(?:\[[ xX]\]\s+)?(.*)$/;
37876
- const ID = /^(AC-\d+):\s*(.*)$/;
37939
+ const ID$1 = /^(AC-\d+):\s*(.*)$/;
37877
37940
  /** The list items under the first heading `title` matches (at `level` when given), up to the next heading; wrapped lines joined, fenced lines ignored. */
37878
37941
  const listUnder = (text, title, level) => {
37879
37942
  const lines = markFenced(text);
@@ -37901,7 +37964,7 @@ const listUnder = (text, title, level) => {
37901
37964
  };
37902
37965
  /** The `- AC-<n>: …` list of `## Acceptance Criteria`, or `undefined` when the section is missing. */
37903
37966
  const criteriaOf = (text) => listUnder(text, /^acceptance criteria$/i, 2)?.map((item) => {
37904
- const id = ID.exec(item);
37967
+ const id = ID$1.exec(item);
37905
37968
  return id === null ? {
37906
37969
  id: "",
37907
37970
  text: item
@@ -37910,16 +37973,20 @@ const criteriaOf = (text) => listUnder(text, /^acceptance criteria$/i, 2)?.map((
37910
37973
  text: id[2]
37911
37974
  };
37912
37975
  });
37976
+ /** The ID'd criteria of `text`, only `slice`'s when given. */
37977
+ const criterionIds = (text, slice) => (criteriaOf(text) ?? []).map((c) => c.id).filter((id) => id !== "" && (slice?.only === void 0 || slice.only.includes(id)) && !(slice?.except ?? []).includes(id));
37978
+ /** `slice`'s IDs in `.gtd/REQUIREMENTS.md` as it is now; `undefined` for an unsliced change. */
37979
+ const sliceIds = (slice) => slice === void 0 ? void 0 : criterionIds(read(".gtd/REQUIREMENTS.md") ?? "", slice);
37913
37980
  /** Items numbered `AC-1`…, unless every one already carries its own distinct ID. */
37914
37981
  const numbered = (items) => {
37915
- const ids = items.map((item) => ID.exec(item));
37982
+ const ids = items.map((item) => ID$1.exec(item));
37916
37983
  const own = ids.every((id) => id !== null) && new Set(ids.map((id) => id[1])).size === ids.length;
37917
37984
  return items.map((item, i) => own ? {
37918
37985
  id: ids[i][1],
37919
37986
  text: ids[i][2]
37920
37987
  } : {
37921
37988
  id: `AC-${i + 1}`,
37922
- text: item.replace(ID, "$2")
37989
+ text: item.replace(ID$1, "$2")
37923
37990
  });
37924
37991
  };
37925
37992
  const STATED = /^acceptance criteria:?$/i;
@@ -38473,7 +38540,8 @@ const plan = (shape, options) => {
38473
38540
  return {
38474
38541
  pattern,
38475
38542
  strays,
38476
- reference: options.reference?.trim() ? options.reference : void 0
38543
+ reference: options.reference?.trim() ? options.reference : void 0,
38544
+ note: options.note?.trim() ? options.note : void 0
38477
38545
  };
38478
38546
  };
38479
38547
  const guardOf = ({ pattern, strays }, entry) => () => {
@@ -38482,13 +38550,22 @@ const guardOf = ({ pattern, strays }, entry) => () => {
38482
38550
  };
38483
38551
  /** Derive, build, check and close one refactor lap; `feedback` is the collected review text of the lap before. */
38484
38552
  const refactorLap = async (p, guard, feedback) => {
38485
- const { pattern, strays, reference } = p;
38486
- await run("deriving", deriveScript([...packageLines(strays, pattern, reference), ...feedback === void 0 ? [] : [
38487
- "## Review feedback",
38488
- "",
38489
- ...demoteHeadings(feedback),
38490
- ""
38491
- ]]), {
38553
+ const { pattern, strays, reference, note } = p;
38554
+ await run("deriving", deriveScript([
38555
+ ...packageLines(strays, pattern, reference),
38556
+ ...note === void 0 ? [] : [
38557
+ "## Refactor note",
38558
+ "",
38559
+ ...demoteHeadings(note),
38560
+ ""
38561
+ ],
38562
+ ...feedback === void 0 ? [] : [
38563
+ "## Review feedback",
38564
+ "",
38565
+ ...demoteHeadings(feedback),
38566
+ ""
38567
+ ]
38568
+ ]), {
38492
38569
  label: "Deriving the refactor package",
38493
38570
  file: REFACTOR_PACKAGE
38494
38571
  });
@@ -38497,7 +38574,8 @@ const refactorLap = async (p, guard, feedback) => {
38497
38574
  strays,
38498
38575
  pattern,
38499
38576
  reference,
38500
- feedback: feedback !== void 0
38577
+ feedback: feedback !== void 0,
38578
+ note: note !== void 0
38501
38579
  }), {
38502
38580
  label: "Refactoring",
38503
38581
  model: coder(),
@@ -38583,22 +38661,27 @@ const COVERED_MIN_P = .7;
38583
38661
  /** Covering turns before the process rests at `uncovered`; a human edit there refills them. */
38584
38662
  const COVERING_RETRIES = 2;
38585
38663
  const frozen = () => requireCriteriaFrozen(REQUIREMENTS);
38586
- const green = () => healthy(() => agentWithSkills("fix.suite.fixing", implementFixPrompt(), {
38664
+ const green = (guard) => healthy(() => agentWithSkills("fix.suite.fixing", implementFixPrompt(), {
38587
38665
  label: "Fixing the check",
38588
38666
  file: FEEDBACK,
38589
38667
  model: coder(),
38590
38668
  system: builderSystem()
38591
38669
  }), {
38592
- guard: frozen,
38670
+ guard: () => {
38671
+ frozen();
38672
+ guard?.();
38673
+ },
38593
38674
  guardRests: false
38594
38675
  });
38595
38676
  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");
38596
38677
  /**
38597
38678
  * The criterion IDs of `.gtd/REQUIREMENTS.md` — read afresh, never carried
38598
- * over — no passing test since `base` covers, by the judge.
38679
+ * over, only `slice`'s when given — no passing test since `base` covers, by the judge.
38599
38680
  */
38600
- const uncoveredSince = async (base) => {
38601
- const criteria = (criteriaOf(read(".gtd/REQUIREMENTS.md") ?? "") ?? []).filter((c) => c.id !== "");
38681
+ const uncoveredSince = async (base, slice) => {
38682
+ const requirements = read(".gtd/REQUIREMENTS.md") ?? "";
38683
+ const own = criterionIds(requirements, slice);
38684
+ const criteria = (criteriaOf(requirements) ?? []).filter((c) => own.includes(c.id));
38602
38685
  if (criteria.length === 0) return {
38603
38686
  covered: [],
38604
38687
  uncovered: []
@@ -38625,14 +38708,14 @@ const record = (covered, uncovered = []) => {
38625
38708
  * sending uncovered IDs back to a covering turn; marks the IDs covered on the
38626
38709
  * landing that ends it. A human may overrule once the retries are spent.
38627
38710
  */
38628
- const requireCoverage = async (base = start()) => {
38711
+ const requireCoverage = async (base = start(), slice) => {
38629
38712
  let retries = 0;
38630
38713
  for (;;) {
38631
- const { covered, uncovered } = await uncoveredSince(base);
38714
+ const { covered, uncovered } = await uncoveredSince(base, slice);
38632
38715
  if (uncovered.length === 0) return record(covered);
38633
38716
  if (retries < COVERING_RETRIES) {
38634
38717
  retries++;
38635
- await agentWithSkills("covering", coveringPrompt(uncovered), {
38718
+ await agentWithSkills("covering", coveringPrompt(uncovered, sliceIds(slice)), {
38636
38719
  label: "Covering the acceptance criteria",
38637
38720
  model: coder(),
38638
38721
  system: builderSystem(),
@@ -38663,7 +38746,7 @@ const judgeSeam = (tip) => judgeDiffShape("judge", {
38663
38746
  allow: (vars.seamAllow ?? defaults.seamAllow).split(",").map((glob) => glob.trim()).filter((glob) => glob !== ""),
38664
38747
  exempt: tip.changes.filter((c) => c.status === "added").map((c) => c.path)
38665
38748
  });
38666
- const naivePass = (retry) => agentWithSkills("naive", naivePassPrompt(retry), {
38749
+ const naivePass = (retry, slice) => agentWithSkills("naive", naivePassPrompt(retry, sliceIds(slice)), {
38667
38750
  label: retry === void 0 ? "Writing the naive pass" : "Rewriting the naive pass",
38668
38751
  keep: true,
38669
38752
  model: coder(),
@@ -38703,16 +38786,20 @@ const addedLines = (change) => {
38703
38786
  const before = new Set((change.before ?? "").split("\n"));
38704
38787
  return change.after.split("\n").filter((line) => !before.has(line)).join("\n");
38705
38788
  };
38789
+ const acceptanceTests = (changes) => changes.filter((c) => c.after !== void 0 && isCode$1(c.path) && isTestFile(c.path));
38790
+ const idsOf = (file, slice) => criterionIds(read(file) ?? "", slice);
38791
+ const unnamed = (ids, tests) => {
38792
+ const lines = tests.map(addedLines);
38793
+ return ids.filter((id) => !lines.some((text) => named(id, text)));
38794
+ };
38706
38795
  /**
38707
38796
  * Why `tip`'s tests fall short, or `undefined`: a criterion ID of `file` no
38708
38797
  * test line of the pass names, or a test importing a file the pass added —
38709
38798
  * naive structure a reset and refactor replaces, which the tests must survive.
38710
38799
  */
38711
- const acceptanceFault = (file, tip) => {
38712
- const ids = (criteriaOf(read(file) ?? "") ?? []).map((c) => c.id).filter((id) => id !== "");
38713
- const tests = tip.changes.filter((c) => c.after !== void 0 && isCode$1(c.path) && isTestFile(c.path));
38714
- const lines = tests.map(addedLines);
38715
- const untested = ids.filter((id) => !lines.some((text) => named(id, text)));
38800
+ const acceptanceFault = (file, tip, slice) => {
38801
+ const tests = acceptanceTests(tip.changes);
38802
+ const untested = unnamed(idsOf(file, slice), tests);
38716
38803
  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]}] …"`;
38717
38804
  const added = tip.changes.filter((c) => c.status === "added" && isCode$1(c.path) && !isTestFile(c.path)).map((c) => c.path);
38718
38805
  const reaching = tests.filter((test) => holdsCallSites(test.path)).flatMap((test) => {
@@ -38721,6 +38808,41 @@ const acceptanceFault = (file, tip) => {
38721
38808
  });
38722
38809
  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")}`;
38723
38810
  };
38811
+ /** What a rebuild carries over from the naive pass `tip`: the IDs its tests name, and those test files. */
38812
+ const naiveAcceptance = (tip, slice) => {
38813
+ const ids = idsOf(REQUIREMENTS, slice);
38814
+ const perTest = acceptanceTests(tip.changes).map((c) => {
38815
+ const lines = addedLines(c);
38816
+ return {
38817
+ path: c.path,
38818
+ ids: ids.filter((id) => named(id, lines))
38819
+ };
38820
+ });
38821
+ return {
38822
+ hash: tip.hash,
38823
+ tests: perTest.filter((c) => c.ids.length > 0).map((c) => c.path),
38824
+ ids: ids.filter((id) => perTest.some((c) => c.ids.includes(id)))
38825
+ };
38826
+ };
38827
+ /**
38828
+ * Refuse a rebuild whose test changes since `since` leave out an ID the naive
38829
+ * pass's tests named. A skipped test still counts: the gate reads text, never runs it.
38830
+ */
38831
+ const requireAcceptance = (acceptance, since) => {
38832
+ const missing = unnamed(acceptance.ids, acceptanceTests(changesSince(since)));
38833
+ if (missing.length === 0) return;
38834
+ 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`);
38835
+ };
38836
+ /**
38837
+ * Refuse a fix turn that, on its own, drops an ID the naive pass's tests named
38838
+ * from a test file. Turn-local, so a person's edit at an escalation rest never
38839
+ * wedges later fix turns.
38840
+ */
38841
+ const requireAcceptanceKept = (acceptance) => {
38842
+ 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 ?? "")));
38843
+ if (dropped.length === 0) return;
38844
+ 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`);
38845
+ };
38724
38846
  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`;
38725
38847
  /**
38726
38848
  * Run the suite with `tip`'s diff applied, then take it out again; resolves
@@ -38764,18 +38886,30 @@ function implement(first, tier, amend) {
38764
38886
  amend
38765
38887
  };
38766
38888
  return scope("implement", async () => {
38767
- if (options.naive === void 0 || !await applied(options.naive)) await agentWithSkills("building", implementBuildingPrompt(options.reference, options.tier, options.amend, options.afterRefactor, flagModeOn()), {
38889
+ const since = head$1();
38890
+ const acceptance = options.rebuild?.acceptance;
38891
+ const built = options.naive === void 0 || !await applied(options.naive);
38892
+ if (built) await agentWithSkills("building", implementBuildingPrompt(options.reference, options.tier, options.amend, options.afterRefactor, flagModeOn(), options.rebuild, sliceIds(options.slice)), {
38768
38893
  label: "Implementing",
38769
38894
  model: coder(),
38770
38895
  system: builderSystem(),
38771
38896
  ...options.afterRefactor === true ? { allowEmpty: true } : {}
38772
38897
  });
38773
38898
  requireCriteriaFrozen(REQUIREMENTS);
38774
- await green();
38775
- await requireCoverage();
38776
- await run("closing", removeScript([REQUIREMENTS]), { label: "Closing out the implementation" });
38899
+ if (built && acceptance !== void 0) requireAcceptance(acceptance, since);
38900
+ await green(built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0);
38901
+ await requireCoverage(options.base, options.slice);
38902
+ await closing(options.more === true);
38777
38903
  });
38778
38904
  }
38905
+ /** The last slice's closing, for a last slice a steer left with no criterion to build. */
38906
+ const closeOut = () => scope("implement", () => closing(false));
38907
+ /** A slice with `more` after it keeps the criteria and the slicing for the next one. */
38908
+ const closing = (more) => run("closing", removeScript(more ? [REFACTOR_NOTE] : [
38909
+ REQUIREMENTS,
38910
+ REFACTOR_NOTE,
38911
+ SLICES
38912
+ ]), { label: "Closing out the implementation" });
38779
38913
  /** Resolves `true` when the human steered (edited), `false` on a clean overrule. */
38780
38914
  const steer = async (retry) => {
38781
38915
  await human("steer", {
@@ -38806,8 +38940,8 @@ const budget = () => {
38806
38940
  };
38807
38941
  };
38808
38942
  /** Sends a pass whose tests fall short or run red back (`undefined`: it goes on, an overruled one as it is). */
38809
- const vetted = async (tip, vetting) => {
38810
- const fault = vetting.waived ? void 0 : acceptanceFault(REQUIREMENTS, tip);
38943
+ const vetted = async (tip, vetting, slice) => {
38944
+ const fault = vetting.waived ? void 0 : acceptanceFault(REQUIREMENTS, tip, slice);
38811
38945
  if (fault !== void 0) {
38812
38946
  const verdict = await vetting.again({
38813
38947
  fault,
@@ -38823,33 +38957,56 @@ const vetted = async (tip, vetting) => {
38823
38957
  /** The judge's ruling on a vetted pass: the decision, or the next attempt's verdict. */
38824
38958
  const ruled = async (tip, again) => {
38825
38959
  const shape = await judgeSeam(tip);
38826
- if (shape.choice === "land") return { reference: tip.hash };
38960
+ if (shape.choice === "land") return {
38961
+ reference: tip.hash,
38962
+ tip
38963
+ };
38827
38964
  if (shape.choice === "refactor-first") return {
38828
38965
  reference: tip.hash,
38966
+ tip,
38829
38967
  shape
38830
38968
  };
38831
38969
  const next = await again({
38832
38970
  strays: shape.strays,
38833
38971
  p: shape.judgment?.answers.shape?.p ?? 0
38834
38972
  });
38835
- return next.kind === "overruled" ? { reference: tip.hash } : next;
38973
+ return next.kind === "overruled" ? {
38974
+ reference: tip.hash,
38975
+ tip
38976
+ } : next;
38836
38977
  };
38837
- const decide = () => scope("seam", async () => {
38978
+ const decide = (slice) => scope("seam", async () => {
38838
38979
  const vetting = {
38839
38980
  again: budget(),
38840
38981
  waived: false
38841
38982
  };
38842
38983
  let retry;
38843
38984
  for (;;) {
38844
- await naivePass(retry);
38985
+ await naivePass(retry, slice);
38845
38986
  const tip = kept("naive");
38846
- const outcome = await vetted(tip, vetting) ?? (flagModeOn() ? await ruled(tip, vetting.again) : { reference: tip.hash });
38987
+ const outcome = await vetted(tip, vetting, slice) ?? (flagModeOn() ? await ruled(tip, vetting.again) : {
38988
+ reference: tip.hash,
38989
+ tip
38990
+ });
38847
38991
  if (!("kind" in outcome)) return outcome;
38848
38992
  retry = outcome.kind === "retry" ? outcome.retry : void 0;
38849
38993
  }
38850
38994
  });
38851
- const seamBuild = async (base) => {
38852
- const { reference, shape } = await decide();
38995
+ /** Write the refactor note from the whole naive pass; a turn that changes anything else is refused. */
38996
+ const refactorNote = async (reference, strays) => {
38997
+ await agent("note", refactorNotePrompt(reference, strays), {
38998
+ label: "Writing the refactor note",
38999
+ file: REFACTOR_NOTE,
39000
+ model: coder(),
39001
+ system: builderSystem()
39002
+ });
39003
+ const others = changes().map((c) => c.path).filter((path) => path !== REFACTOR_NOTE);
39004
+ const note = read(".gtd/REFACTOR-NOTE.md") ?? "";
39005
+ 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")}` : ""}`);
39006
+ return note;
39007
+ };
39008
+ const seamBuild = async (base, slice$1 = {}) => {
39009
+ const { reference, tip, shape } = await decide(slice$1.criteria);
38853
39010
  if (shape === void 0) return {
38854
39011
  reference,
38855
39012
  base,
@@ -38863,42 +39020,164 @@ const seamBuild = async (base) => {
38863
39020
  }));
38864
39021
  return {
38865
39022
  reference,
38866
- base
39023
+ base,
39024
+ rebuild: { acceptance: naiveAcceptance(tip, slice$1.criteria) }
38867
39025
  };
38868
39026
  }
38869
- if (refactorStrays(shape).length === 0) return {
39027
+ const strays = refactorStrays(shape);
39028
+ if (strays.length === 0) return {
38870
39029
  reference,
38871
- base
39030
+ base,
39031
+ rebuild: { acceptance: naiveAcceptance(tip, slice$1.criteria) }
38872
39032
  };
38873
- const refactored = await slice("refactor", () => reviewedRefactor(shape, { reference }));
39033
+ const note = await scope("seam", () => refactorNote(reference, strays));
39034
+ const refactored = await slice(slice$1.refactor ?? "refactor", () => reviewedRefactor(shape, {
39035
+ reference,
39036
+ note
39037
+ }));
38874
39038
  return {
38875
39039
  reference,
38876
39040
  base: head$1(),
38877
- refactored
39041
+ refactored,
39042
+ rebuild: {
39043
+ note,
39044
+ acceptance: naiveAcceptance(tip, slice$1.criteria)
39045
+ }
38878
39046
  };
38879
39047
  };
38880
39048
  //#endregion
39049
+ //#region src/workflows/feature/slicing.ts
39050
+ const ID = /^(AC-\d+)(?![\w-])/;
39051
+ /** The `sliceMaxCriteria` setting. Anything but a positive integer fails rather than guess a cap. */
39052
+ const sliceCap = () => {
39053
+ const value = vars.sliceMaxCriteria ?? defaults.sliceMaxCriteria;
39054
+ if (!/^[1-9]\d*$/.test(value)) refuse(`gtd: sliceMaxCriteria must be a positive integer, got ${JSON.stringify(value)}`);
39055
+ return Number(value);
39056
+ };
39057
+ /** Each `## ` heading of `text` with the list items under it; an item not led by an ID keeps an empty one. */
39058
+ const slicesOf = (text) => {
39059
+ const slices = [];
39060
+ let open = false;
39061
+ for (const { line, fenced } of markFenced(text)) {
39062
+ if (fenced) continue;
39063
+ const heading = HEADING.exec(line);
39064
+ if (heading !== null) {
39065
+ open = heading[1] === "##";
39066
+ if (open) slices.push({
39067
+ title: heading[2],
39068
+ ids: []
39069
+ });
39070
+ continue;
39071
+ }
39072
+ const item = ITEM.exec(line);
39073
+ if (item !== null && open) slices.at(-1).ids.push(ID.exec(item[1])?.[1] ?? "");
39074
+ }
39075
+ return slices;
39076
+ };
39077
+ const twice = (list) => [...new Set(list.filter((id, i) => list.indexOf(id) !== i))];
39078
+ /** Why `slices` fail to put each of `ids` in exactly one slice of at most `cap`, or `undefined`. */
39079
+ const sliceFault = (slices, ids, cap) => {
39080
+ const listed = slices.flatMap((s) => s.ids);
39081
+ if (slices.length === 0) return `no "## " slice`;
39082
+ const empty = slices.filter((s) => s.ids.length === 0).map((s) => s.title);
39083
+ if (empty.length > 0) return `a slice with no criterion: ${empty.join(", ")}`;
39084
+ if (listed.includes("")) return "a list item not led by a criterion ID";
39085
+ const over = slices.filter((s) => s.ids.length > cap).map((s) => s.title);
39086
+ if (over.length > 0) return `more than ${cap} criteria in: ${over.join(", ")}`;
39087
+ const repeated = twice(listed);
39088
+ if (repeated.length > 0) return `an ID in more than one slice: ${repeated.join(", ")}`;
39089
+ const unknown = listed.filter((id) => !ids.includes(id));
39090
+ if (unknown.length > 0) return `an ID ${REQUIREMENTS} does not list: ${unknown.join(", ")}`;
39091
+ const missing = ids.filter((id) => !listed.includes(id));
39092
+ if (missing.length > 0) return `no slice for: ${missing.join(", ")}`;
39093
+ };
39094
+ /** Refuse a turn whose `.gtd/SLICES.md` does not put each of `ids` in exactly one slice of at most `cap`. */
39095
+ const requireSlices = (ids, cap) => {
39096
+ const slices = slicesOf(read(".gtd/SLICES.md") ?? "");
39097
+ const why = sliceFault(slices, ids, cap);
39098
+ if (why !== void 0) refuse(`gtd land: slices: ${SLICES} has ${why} — list each slice as a "## <title>" heading over "- AC-<n>" items, every criterion of ${REQUIREMENTS} in exactly one slice, at most ${cap} per slice`);
39099
+ return slices;
39100
+ };
39101
+ /**
39102
+ * The slices to build, in order. Criteria within `sliceMaxCriteria` are one
39103
+ * slice and no turn; above it `design.slicing` groups them.
39104
+ */
39105
+ const slicing = async () => {
39106
+ const all = criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
39107
+ const cap = sliceCap();
39108
+ if (all.length <= cap) return [{
39109
+ title: "",
39110
+ ids: all
39111
+ }];
39112
+ return scope("design", async () => {
39113
+ await agentWithSkills("slicing", slicingPrompt(cap), {
39114
+ label: "Slicing the change",
39115
+ file: SLICES,
39116
+ model: planner(),
39117
+ system: designSystem()
39118
+ });
39119
+ return requireSlices(all, cap);
39120
+ });
39121
+ };
39122
+ /** Slice `i`'s criteria; `undefined` when `slices` is the one slice of an unsliced change. */
39123
+ const sliceCriteria = (slices, i) => slices.length === 1 ? void 0 : i < slices.length - 1 ? { only: slices[i].ids } : { except: slices.slice(0, -1).flatMap((s) => s.ids) };
39124
+ //#endregion
38881
39125
  //#region src/workflows/feature/feature.ts
38882
39126
  /** Revert the sketch that started the process out of the working tree; its intent survives in history. */
38883
39127
  const unwind = () => {
38884
39128
  const commit = head$1();
38885
39129
  return run("unwind", revertScript(commit, FEEDBACK, unwindFailure(commit)), { label: "Unwinding your input" });
38886
39130
  };
38887
- /** A naive pass (judged only with `flagMode` on), then implement and the quality lap; design must have run since `firstBase`. */
39131
+ /**
39132
+ * The slices `design.slicing` grouped the criteria into, each built in turn
39133
+ * from where the previous one landed; design must have run since `firstBase`.
39134
+ */
38888
39135
  const buildFeature = async (firstBase) => {
38889
39136
  const tier = riskTierOn() ? recordRisk() : void 0;
38890
- const seam = await seamBuild(firstBase);
38891
- await slice("feature", async () => {
39137
+ const slices = await slicing();
39138
+ const one = slices.length === 1;
39139
+ for (const i of slices.keys()) {
39140
+ const more = i < slices.length - 1;
39141
+ const criteria = sliceCriteria(slices, i);
39142
+ if (sliceIds(criteria)?.length === 0) {
39143
+ if (!more) await closeOut();
39144
+ continue;
39145
+ }
39146
+ const n = one ? "" : `-${i + 1}`;
39147
+ await buildSlice(i === 0 ? firstBase : head$1(), {
39148
+ tier,
39149
+ criteria,
39150
+ refactor: `refactor${n}`,
39151
+ feature: `feature${n}`,
39152
+ more
39153
+ });
39154
+ }
39155
+ };
39156
+ /** A naive pass (judged only with `flagMode` on), then implement and the quality lap. */
39157
+ const buildSlice = async (sliceBase, build) => {
39158
+ const seam = await seamBuild(sliceBase, {
39159
+ criteria: build.criteria,
39160
+ refactor: build.refactor
39161
+ });
39162
+ await slice(build.feature, async () => {
38892
39163
  await implement({
38893
39164
  reference: seam.reference,
38894
- tier,
39165
+ tier: build.tier,
38895
39166
  afterRefactor: seam.refactored,
38896
- naive: seam.land === true ? seam.reference : void 0
39167
+ naive: seam.land === true ? seam.reference : void 0,
39168
+ rebuild: seam.rebuild,
39169
+ slice: build.criteria,
39170
+ base: build.criteria === void 0 ? void 0 : sliceBase,
39171
+ more: build.more
38897
39172
  });
38898
- const base = seam.refactored === true && !changesSince(seam.base).some((c) => isCode$1(c.path)) ? firstBase : seam.base;
39173
+ const carried = seam.rebuild?.acceptance?.tests ?? [];
39174
+ const base = seam.refactored === true && !changesSince(seam.base).some((c) => isCode$1(c.path) && !carried.includes(c.path)) ? sliceBase : seam.base;
39175
+ const guard = build.more ? () => requireCriteriaFrozen(REQUIREMENTS) : void 0;
38899
39176
  await tail(settleQuality, {
38900
39177
  green: true,
38901
- lensBase: base
39178
+ lensBase: base,
39179
+ guard,
39180
+ guardRests: false
38902
39181
  });
38903
39182
  });
38904
39183
  };
@@ -38944,6 +39223,7 @@ const ordinaryStart = async () => {
38944
39223
  });
38945
39224
  refusePendingHandOff();
38946
39225
  flagModeOn();
39226
+ sliceCap();
38947
39227
  const stated = statedCriteria(changes(), head$1());
38948
39228
  await unwind();
38949
39229
  if (changes(".gtd/FEEDBACK.md").some((c) => c.status !== "deleted")) await human("unwind-failed", {
@@ -39138,4 +39418,4 @@ var workflows_default = feature;
39138
39418
  */
39139
39419
  const doors = {};
39140
39420
  //#endregion
39141
- export { COMMENT, ESCALATION, FEEDBACK, FIX_CAP, FlagOptions, QUALITY, REFACTOR, REFACTOR_PACKAGE, REQUIREMENTS, REVIEW, REVIEW_ORIG, ShippedOptions, acceptanceFault, access, afterTail, agentWithSkills, answerProductQuestions, answerReviewQuestions, awaitReview, base, baseline, buildFeature, buildTail, builtInLenses, cleanup, cleanupFlow, closeReview, coder, collecting, criteriaOf, workflows_default as default, defaults, deferNotes, describeEscalation, design, doors, envDefaults, escalate, escalation, escalationExhausted, feature, fix, fixCheck, fixFlow, fixNits, fixQuality, fixQualityFindings, fixReviewNotes, fixRisks, gate, guarded, handOffReview, handOffTail, healthy, holdsCallSites, implement, isTestFile, judgeSeam, lensEntriesOf, lensOf, lensesOf, naivePass, ordinaryStart, planAndBuild, planner, qualityLap, reUnwind, refactorCollecting, refactorFirst, refactorStrays, requireCriteria, requireCriteriaFrozen, requireNoNewFlags, requireNoTestFiles, requireTestCallSitesOnly, requireWithin, retryQuestion, review, reviewFlow, reviewMissing, reviewQuality, reviewedRefactor, reviewing, rollout, rolloutFlow, seamBuild, skills, skipDesign, statedCriteria, steering, summary, tail, threads, triage, unwind };
39421
+ export { COMMENT, ESCALATION, FEEDBACK, FIX_CAP, FlagOptions, QUALITY, REFACTOR, REFACTOR_NOTE, REFACTOR_PACKAGE, REQUIREMENTS, REVIEW, REVIEW_ORIG, SLICES, ShippedOptions, acceptanceFault, access, afterTail, agentWithSkills, answerProductQuestions, answerReviewQuestions, awaitReview, base, baseline, buildFeature, buildTail, builtInLenses, cleanup, cleanupFlow, closeOut, closeReview, coder, collecting, criteriaOf, criterionIds, workflows_default as default, defaults, deferNotes, describeEscalation, design, doors, envDefaults, escalate, escalation, escalationExhausted, feature, fix, fixCheck, fixFlow, fixNits, fixQuality, fixQualityFindings, fixReviewNotes, fixRisks, gate, guarded, handOffReview, handOffTail, healthy, holdsCallSites, implement, isTestFile, judgeSeam, lensEntriesOf, lensOf, lensesOf, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, planner, qualityLap, reUnwind, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireNoNewFlags, requireNoTestFiles, requireSlices, requireTestCallSitesOnly, requireWithin, retryQuestion, review, reviewFlow, reviewMissing, reviewQuality, reviewedRefactor, reviewing, rollout, rolloutFlow, seamBuild, skills, skipDesign, sliceCap, sliceCriteria, sliceIds, slicesOf, slicing, statedCriteria, steering, summary, tail, threads, triage, unwind };