@pmelab/gtd 49.0.0 → 51.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 (60) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +16 -3
  3. package/dist/chunks/{reserved-CYZ1bSBy.mjs → reserved-CUkk9XgL.mjs} +151 -5
  4. package/dist/chunks/{review-BewyJflS.mjs → review-BaCXlIT2.mjs} +84 -20
  5. package/dist/chunks/{steering-BXwjmocB.mjs → steering-DtUVUz9l.mjs} +44 -14
  6. package/dist/chunks/{tail-C1Oi2YEy.mjs → tail-CX6Pdsql.mjs} +1 -1
  7. package/dist/gtd.bundle.mjs +22 -5
  8. package/dist/steering.mjs +2 -2
  9. package/dist/testkit.mjs +1 -1
  10. package/dist/web/main.js +27 -3
  11. package/dist/workflow-kit.mjs +1 -1
  12. package/dist/workflows/cleanup.mjs +2 -2
  13. package/dist/workflows/feature.mjs +144 -11
  14. package/dist/workflows/fix.mjs +3 -3
  15. package/dist/workflows/review.mjs +3 -3
  16. package/dist/workflows/rollout.mjs +1 -1
  17. package/dist/workflows.mjs +415 -42
  18. package/package.json +1 -1
  19. package/src/steering/index.ts +1 -0
  20. package/src/steering/review.test.ts +60 -2
  21. package/src/steering/review.ts +66 -9
  22. package/src/workflows/feature/coverage.feature +1 -0
  23. package/src/workflows/feature/fast-path.feature +1 -0
  24. package/src/workflows/feature/feature.ts +4 -1
  25. package/src/workflows/feature/flag-mode.feature +1 -1
  26. package/src/workflows/feature/lens-models.feature +1 -0
  27. package/src/workflows/feature/lens-review.feature +17 -4
  28. package/src/workflows/feature/one-step-land.feature +1 -0
  29. package/src/workflows/feature/prototype-rounds.feature +152 -0
  30. package/src/workflows/feature/prototype.feature +419 -0
  31. package/src/workflows/feature/prototype.test.ts +43 -0
  32. package/src/workflows/feature/prototype.ts +181 -0
  33. package/src/workflows/feature/refactor-absorbs.feature +9 -1
  34. package/src/workflows/feature/refactor-first.feature +13 -3
  35. package/src/workflows/feature/refactor-rebuild.feature +29 -5
  36. package/src/workflows/feature/refactor-review.feature +111 -13
  37. package/src/workflows/feature/refactor.test.ts +14 -4
  38. package/src/workflows/feature/refactor.ts +2 -0
  39. package/src/workflows/feature/review-answers.feature +9 -1
  40. package/src/workflows/feature/review-gate.feature +21 -3
  41. package/src/workflows/feature/reviewer-walkthrough.feature +15 -3
  42. package/src/workflows/feature/seam-rejection.feature +1 -0
  43. package/src/workflows/feature/seam.feature +1 -0
  44. package/src/workflows/feature/seam.test.ts +3 -2
  45. package/src/workflows/feature/seam.ts +19 -5
  46. package/src/workflows/feature/severity-budget.feature +112 -0
  47. package/src/workflows/feature/severity-sorted-review.feature +198 -0
  48. package/src/workflows/feature/slice-findings.feature +15 -2
  49. package/src/workflows/feature/slicing-cap.feature +9 -0
  50. package/src/workflows/feature/slicing.feature +1 -0
  51. package/src/workflows/feature/standards.feature +5 -0
  52. package/src/workflows/shared/options.ts +1 -0
  53. package/src/workflows/shared/prose.ts +6 -0
  54. package/src/workflows/shared/review.ts +124 -18
  55. package/src/workflows/shared/skills.test.ts +4 -0
  56. package/src/workflows/shared/skills.ts +5 -0
  57. package/src/workflows/shared/steps.ts +1 -0
  58. package/src/workflows/shared/text.test.ts +90 -0
  59. package/src/workflows/shared/text.ts +181 -4
  60. package/src/workflows/shared/vars.ts +3 -0
@@ -15648,6 +15648,9 @@ const qaDescriptor = {
15648
15648
  const ANSWER_LEAD = "Tick exactly one:";
15649
15649
  const ANSWER_FIX = "fix as suggested";
15650
15650
  const ANSWER_WONT_FIX = "won't fix";
15651
+ /** A chunk's leading `_critical: security_` paragraph: the tier gtd ranked it with, and why. */
15652
+ const SEVERITY_LINE_RE = /^_((?:critical|should|could)(?::[^_\n]*)?)_$/;
15653
+ const severityLine = (label) => `_${label}_`;
15651
15654
  /**
15652
15655
  * The canonical sample. Every detail here is load-bearing for the formatter
15653
15656
  * round-trip: both footnote bodies exceed 80 characters so a reflow actually
@@ -15938,12 +15941,26 @@ const answerListOf = (content, body) => {
15938
15941
  })
15939
15942
  };
15940
15943
  };
15944
+ /** A leading severity line is the chunk's tier, never its prose. */
15945
+ const splitSeverity = (content, run) => {
15946
+ const first = run[0];
15947
+ const label = first?.type === "paragraph" ? SEVERITY_LINE_RE.exec(sourceText(content, first).trim())?.[1] : void 0;
15948
+ if (label === void 0) return { leadingRun: run };
15949
+ return {
15950
+ severity: {
15951
+ label,
15952
+ line: toLspPosition(first.position.start).line
15953
+ },
15954
+ leadingRun: run.slice(1)
15955
+ };
15956
+ };
15941
15957
  /** Splits one chunk's body nodes into its file pointers (hunk pointers are task items, collected recursively at ANY nesting depth via `taskItems` — a nested hunk is the same kind of hunk as a top-level one) and description prose. The description is the chunk's own leading run and never a node that contains a hunk pointer: it stops at the first node whose `taskItems` include a real pointer (at any depth — a blockquote wrapping a pointer list stops it just as a top-level list would). Every node kind in what's left before that point contributes — a `footnoteDefinition` is the one exception, excluded by `blockNodesOfRun` itself (it's a note, never document content). */
15942
15958
  const parseChunkBody = (content, lines, title, body) => {
15943
15959
  const answer = answerListOf(content, body);
15944
15960
  const isAnswer = (n) => answer !== void 0 && (n === answer.list || n === body[answer.lead]);
15945
15961
  const firstPointerIndex = body.findIndex((n) => !isAnswer(n) && taskItems(n).some((item) => hasPointerToken(content, item)));
15946
- const leadingRun = (firstPointerIndex === -1 ? body : body.slice(0, firstPointerIndex)).filter((n) => !isAnswer(n));
15962
+ const run = (firstPointerIndex === -1 ? body : body.slice(0, firstPointerIndex)).filter((n) => !isAnswer(n));
15963
+ const { severity, leadingRun } = splitSeverity(content, run);
15947
15964
  const descriptionNodes = blockNodesOfRun(content, leadingRun);
15948
15965
  const descriptionInline = collapseImages(blockRunInline(content, leadingRun));
15949
15966
  const description = leadingRun.filter((n) => n.type !== "footnoteDefinition").map((n) => sourceText(content, n)).join(" ").trim();
@@ -15962,6 +15979,7 @@ const parseChunkBody = (content, lines, title, body) => {
15962
15979
  files,
15963
15980
  errors,
15964
15981
  ...answer === void 0 ? {} : { answers: answer.options },
15982
+ ...severity === void 0 ? {} : { severity },
15965
15983
  boxed: body.some((n) => !isAnswer(n) && taskItems(n).length > 0)
15966
15984
  };
15967
15985
  };
@@ -15998,7 +16016,7 @@ const parseChangesets = (tree, content, lines) => {
15998
16016
  const errors = [];
15999
16017
  for (const { heading, headingLine, body } of splitChunks(tree)) {
16000
16018
  const title = headingText$1(content, heading);
16001
- const { description, descriptionNodes, descriptionInline, files, answers, boxed, errors: bodyErrors } = parseChunkBody(content, lines, title, body);
16019
+ const { description, descriptionNodes, descriptionInline, files, answers, severity, boxed, errors: bodyErrors } = parseChunkBody(content, lines, title, body);
16002
16020
  errors.push(...bodyErrors);
16003
16021
  if (files.length === 0 && boxed) errors.push({
16004
16022
  message: `Chunk "${title}" has no file pointers`,
@@ -16012,7 +16030,8 @@ const parseChangesets = (tree, content, lines) => {
16012
16030
  descriptionInline,
16013
16031
  files,
16014
16032
  headingLine,
16015
- ...answers === void 0 ? {} : { answers }
16033
+ ...answers === void 0 ? {} : { answers },
16034
+ ...severity === void 0 ? {} : { severity }
16016
16035
  });
16017
16036
  }
16018
16037
  if (changesets.length === 0) errors.push({ message: "REVIEW.md has no '##' chunks" });
@@ -16313,6 +16332,7 @@ const reviewView = (content) => {
16313
16332
  detail: chunk.description,
16314
16333
  detailInline: chunk.descriptionInline,
16315
16334
  body: chunk.descriptionNodes,
16335
+ ...chunk.severity === void 0 ? {} : { status: chunk.severity.label },
16316
16336
  anchor: {
16317
16337
  kind: "chunk",
16318
16338
  index: chunkIndex
@@ -16460,15 +16480,19 @@ const reviewDescriptor = {
16460
16480
  openThreads: openThreadsOf
16461
16481
  };
16462
16482
  const pointerLabel = (f) => `${f.path}${f.line === void 0 ? "" : `#${f.line}${f.rangeEnd === void 0 ? "" : `-${f.rangeEnd}`}`}`;
16463
- const chunkNote = (chunk, prior) => chunk.description === (prior?.description ?? "") || prior === void 0 && chunk.answers !== void 0 ? [] : [{
16464
- line: chunk.headingLine,
16465
- note: {
16466
- kind: "chunk",
16467
- anchor: chunk.title,
16468
- before: prior?.description ?? "",
16469
- text: chunk.description
16470
- }
16471
- }];
16483
+ const chunkProse = (chunk, retiered) => [chunk?.description ?? "", retiered && chunk?.severity !== void 0 ? severityLine(chunk.severity.label) : ""].filter((p) => p !== "").join("\n\n");
16484
+ const chunkNote = (chunk, prior) => {
16485
+ const retiered = chunk.severity !== void 0 && chunk.severity.label !== prior?.severity?.label;
16486
+ return chunkProse(chunk, retiered) === chunkProse(prior, retiered) || prior === void 0 && chunk.answers !== void 0 ? [] : [{
16487
+ line: chunk.headingLine,
16488
+ note: {
16489
+ kind: "chunk",
16490
+ anchor: chunk.title,
16491
+ before: chunkProse(prior, retiered),
16492
+ text: chunkProse(chunk, retiered)
16493
+ }
16494
+ }];
16495
+ };
16472
16496
  const pointerNotes = (chunk, prior) => chunk.files.flatMap((file) => {
16473
16497
  const label = pointerLabel(file);
16474
16498
  const was = prior?.files.find((f) => pointerLabel(f) === label);
@@ -16582,7 +16606,13 @@ const reviewDecisions = (before, after) => {
16582
16606
  const reviewChunks = (content) => parseReviewDoc(content).changesets.map((c) => ({
16583
16607
  title: c.title,
16584
16608
  line: c.headingLine,
16585
- pointers: c.files.length
16609
+ pointers: c.files.length,
16610
+ ...c.severity === void 0 ? {} : { severityLine: c.severity.line },
16611
+ files: c.files.map((f) => ({
16612
+ path: f.path,
16613
+ ...f.line === void 0 ? {} : { line: f.line },
16614
+ ...f.rangeEnd === void 0 ? {} : { rangeEnd: f.rangeEnd }
16615
+ }))
16586
16616
  }));
16587
16617
  //#endregion
16588
16618
  //#region src/steering/CodeThreads.ts
@@ -17211,6 +17241,7 @@ const defaults = {
17211
17241
  qualityReviews: "correctness, owasp-security, ponytail-review, test-audit, conventions, spec-challenge",
17212
17242
  refactorQualityReviews: "correctness, ponytail-review, conventions",
17213
17243
  continuations: "3",
17244
+ prototypeRounds: "3",
17214
17245
  continuationStall: "2"
17215
17246
  };
17216
17247
  /** Environment settings. */
@@ -17288,6 +17319,11 @@ task in front of you end to end, tests first, and come back to fix
17288
17319
  things when redirected — a failing check, or reviewer feedback. Stay
17289
17320
  strictly inside that task; never touch code outside it. Treat feedback as the
17290
17321
  diagnosis to act on; discard it, with reason, only when simply wrong.`;
17322
+ const testerPersona = `You are the hands-on tester in gtd's build pipeline: use what was just
17323
+ built the way a person would — run the real program, open the real
17324
+ page, call the real endpoint — and report exactly what you did and
17325
+ what you saw. Never fix anything yourself, and never pass a criterion
17326
+ you did not watch hold.`;
17291
17327
  const finisherPersona = `You are the closing identity in gtd's build pipeline — the last coder
17292
17328
  before it closes. Fix a late-breaking failing check after sign-off,
17293
17329
  focused and minimal. Every turn lands its own commit — nothing here
@@ -17609,6 +17645,56 @@ const withWalkthrough = (review, report) => {
17609
17645
  const body = lines.join("\n").trimEnd();
17610
17646
  return summary.length === 0 ? `${body}\n` : `${body}\n\n${summary.join("\n\n")}\n`;
17611
17647
  };
17648
+ const chunkBlocks = (review) => {
17649
+ const lines = review.split("\n");
17650
+ const chunks = reviewChunks(review);
17651
+ return {
17652
+ head: lines.slice(0, chunks[0]?.line ?? lines.length),
17653
+ blocks: chunks.map((c, i) => ({
17654
+ title: c.title,
17655
+ lines: lines.slice(c.line, chunks[i + 1]?.line ?? lines.length)
17656
+ }))
17657
+ };
17658
+ };
17659
+ const isBlank = (line) => line !== void 0 && line.trim() === "";
17660
+ /** \`review\` with every chunk's severity line removed, so a re-review is ranked afresh. */
17661
+ const unranked = (review) => {
17662
+ const drop = new Set(reviewChunks(review).flatMap((c) => c.severityLine ?? []));
17663
+ if (drop.size === 0) return review;
17664
+ const lines = review.split("\n");
17665
+ return lines.filter((_, i) => !drop.has(i) && !(drop.has(i - 1) && isBlank(lines[i]))).join("\n");
17666
+ };
17667
+ const chunkTexts = (review) => new Map(chunkBlocks(review).blocks.map((b) => [b.title, b.lines.join("\n").trim()]));
17668
+ const trimBlankLines = (lines) => {
17669
+ let start = 0;
17670
+ let end = lines.length;
17671
+ while (start < end && isBlank(lines[start])) start++;
17672
+ while (end > start && isBlank(lines[end - 1])) end--;
17673
+ return lines.slice(start, end);
17674
+ };
17675
+ /**
17676
+ * \`review\` with the \`ranked\` chunks first, in their order, each led by its
17677
+ * severity line; every other chunk follows in place.
17678
+ */
17679
+ const rankedReview = (review, ranked) => {
17680
+ const { head, blocks } = chunkBlocks(review);
17681
+ const place = new Map(ranked.map((r, k) => [r.title, k]));
17682
+ const labels = new Map(ranked.map((r) => [r.title, r.label]));
17683
+ const order = (i) => place.get(blocks[i].title) ?? ranked.length;
17684
+ const chunk = (i) => {
17685
+ const { title, lines } = blocks[i];
17686
+ const label = labels.get(title);
17687
+ const heading = /^ {0,3}-+\s*$/.test(lines[1] ?? "") ? 2 : 1;
17688
+ const body = trimBlankLines(lines.slice(heading)).join("\n");
17689
+ return [
17690
+ lines.slice(0, heading).join("\n"),
17691
+ ...label === void 0 ? [] : [severityLine(label)],
17692
+ ...body === "" ? [] : [body]
17693
+ ].join("\n\n");
17694
+ };
17695
+ const sorted = blocks.map((_, i) => i).sort((x, y) => order(x) - order(y) || x - y);
17696
+ return `${[head.join("\n").trim(), ...sorted.map(chunk)].filter((p) => p !== "").join("\n\n")}\n`;
17697
+ };
17612
17698
  /**
17613
17699
  * \`.gtd/REVIEW.md\` as gtd writes it: one chunk per note, each ending in its
17614
17700
  * answer boxes, then the fixed and noise lists. Chunk titles are made unique —
@@ -17663,8 +17749,11 @@ const acceptanceCriteriaBlock = `
17663
17749
  Whichever lap this is, \`.gtd/REQUIREMENTS.md\` carries a
17664
17750
  \`## Acceptance Criteria\` section: one \`- AC-<n>: <criterion>\` line
17665
17751
  per criterion, numbered from \`AC-1\`. They bind every later step —
17666
- the build is done when each one holds — so each must be checkable: a
17667
- test or a command shows it holds or not. A later lap keeps every ID:
17752
+ the build is done when each one holds — so each must be behaviour a
17753
+ test can pin: one that fails when it does not hold. A green suite, a
17754
+ passing typecheck or lint, a generator or build that runs — the health
17755
+ check enforces those on every lap, so none is a criterion; fold what it
17756
+ must produce into a behaviour a test can observe. A later lap keeps every ID:
17668
17757
  reword a criterion in place, append a new one with the next unused
17669
17758
  number, delete a dropped one — never renumber, never reuse an ID. When
17670
17759
  the file holds no list (a review loop-back rewrites it), recover the
@@ -17772,7 +17861,7 @@ const clearCriteriaQuestion = {
17772
17861
  id: "clear",
17773
17862
  primitive: "noul",
17774
17863
  instructions: "state.criteria lists the acceptance criteria the human's sketch (state.sketch) stated, as gtd numbered them. Decide whether they can stand as the binding definition of done, with no design conversation.",
17775
- criteria: "yes: every criterion is clear and checkable — a test or a command can show it holds or not — and together they say what to build. no: any one criterion is vague, subjective or untestable, or the sketch leaves an open product question. Unsure means no."
17864
+ criteria: "yes: every criterion is clear behaviour a test can pin — a test would fail if it did not hold — and together they say what to build. no: any one criterion is vague, subjective or untestable, only a command can check it (a suite, typecheck, lint, build or generator run that passes), or the sketch leaves an open product question. Unsure means no."
17776
17865
  };
17777
17866
  const clearCriteriaMessage = () => `The sketch stated its own acceptance criteria. Run \`gtd judge answer\` and
17778
17867
  pipe \`yes\` when every one is clear and checkable, so design is skipped and
@@ -18163,6 +18252,13 @@ const wrapItem = (item) => {
18163
18252
  } else line = line === "" ? word : `${line} ${word}`;
18164
18253
  return [...out, line];
18165
18254
  };
18255
+ const reviewRankingMessage = () => `Ranking each chunk of the review before the human reads it. Run
18256
+ \`gtd judge answer\` and pipe, per chunk \`C<n>\`: \`C<n>.security\`
18257
+ (yes/no), \`C<n>.reversibility\` (one-way/two-way),
18258
+ \`C<n>.blastRadius\` (local/module/contract) and \`C<n>.newConcept\`
18259
+ (yes/no) — or land without a verdict, which lists every chunk as
18260
+ critical.
18261
+ `;
18166
18262
  const buildReviewTriageMessage = () => `Judging each note the human added to \`.gtd/REVIEW.md\` (a line note, a
18167
18263
  footnote, or prose under a chunk) with one verdict: \`edit\` (a change
18168
18264
  request), \`question\`, \`nit\` (a small fix needing no re-plan) or
@@ -18684,6 +18780,80 @@ Two ways forward (then run \`gtd land\`):
18684
18780
  - **Overrule** — land a clean tree to finish without them. The slice records
18685
18781
  the IDs it covers and, as \`uncovered\`, the ones it does not
18686
18782
  `;
18783
+ const testerSystem = () => `${testerPersona}
18784
+
18785
+ ${agentConduct}`;
18786
+ const PROTOTYPE_FORMAT = `- Write \`.gtd/PROTOTYPE.md\`, replacing any earlier report: one \`## \` chunk
18787
+ per criterion, headed \`## AC-<n>: pass\`, \`## AC-<n>: fail\` or
18788
+ \`## AC-<n>: blocked\`, then one chunk per problem you found outside any
18789
+ criterion, headed \`## Problem <short title>: fail\` (\`: pass\` once a
18790
+ re-test shows it fixed). Under each: the exact steps you ran, what you
18791
+ observed (output, a screenshot path, the error), and for a failure what
18792
+ you expected instead
18793
+ - \`blocked\` is for a criterion you could not exercise, or one whose
18794
+ expected behaviour itself looks wrong — say which and why: a person
18795
+ decides it, never a fixer`;
18796
+ const prototypeTestingPrompt = (slice, retest) => `${stateFileRules}
18797
+ - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
18798
+ ${sliceLine(slice)}- The change is built and its suite is green. Now test it by hand: use
18799
+ every acceptance criterion the way a person would and watch what
18800
+ happens. Running the test suite is not this — it already ran
18801
+ - Find how this project is exercised for real, in this order: a project
18802
+ skill that launches or drives it (e.g. \`.claude/skills/*\`); a manual
18803
+ testing or running section in \`AGENTS.md\`, \`CLAUDE.md\` or \`README.md\`;
18804
+ failing those, the project's shape — a browser or Playwright for a web
18805
+ project, the built CLI in a scratch directory for a CLI, a request client
18806
+ for an API
18807
+ - Found none and recognised no shape: write only a chunk headed
18808
+ \`## No testing instructions\` saying what you looked for, and finish —
18809
+ a person is asked; never guess
18810
+ - Test in a disposable place only — a scratch directory outside this
18811
+ repository, a throwaway repository, a dev server on a free port — never
18812
+ against the user's real data or this working tree. Point every tool's
18813
+ output (screenshots, traces, test results) outside the repository too
18814
+ ${retest ? `- This is a re-test: \`.gtd/PROTOTYPE.md\` holds the last round's report and
18815
+ a fix turn or a person has changed the code since. Re-test every failed or blocked criterion and
18816
+ every problem it lists in full, then re-run the passed ones quickly
18817
+ ` : ""}${PROTOTYPE_FORMAT}
18818
+ - Change nothing but \`.gtd/PROTOTYPE.md\` — a turn that does is refused
18819
+ - Leave everything uncommitted and finish your turn — do not commit
18820
+ `;
18821
+ const prototypeFixingPrompt = (slice) => `${stateFileRules}
18822
+ - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
18823
+ ${sliceLine(slice)}${criteriaBinding(true)}
18824
+ - Read \`.gtd/PROTOTYPE.md\`: a hands-on test of the change. Fix every
18825
+ \`fail\` chunk of this change's criteria${slice === void 0 ? "" : " (this slice's only)"} or a problem found
18826
+ beside them, at its cause, with a test that shows it; leave \`blocked\`
18827
+ ones to the person
18828
+ - A \`fail\` the code does not explain (the tester misread what it saw) has
18829
+ nothing to fix: leave it and change nothing for it — the next round re-tests
18830
+ - Leave \`.gtd/PROTOTYPE.md\` as it is: the next round re-tests from it
18831
+ - Run \`${env.testCommand}\` and finish only once it is green
18832
+ - Leave everything uncommitted and finish your turn — do not commit
18833
+ `;
18834
+ const openItems = (items) => items.map((item) => `- ${item.subject}: ${item.verdict}`).join("\n");
18835
+ const prototypeOpenMessage = (items, rounds) => `The hands-on test of this slice still has open items${rounds > 0 ? ` after ${rounds} fix round${rounds === 1 ? "" : "s"}` : ", and none a fixer can take"}:
18836
+ ${openItems(items)}
18837
+
18838
+ \`.gtd/PROTOTYPE.md\` holds the last report: the steps run and what was seen.
18839
+
18840
+ Two ways forward (then run \`gtd land\`):
18841
+ - **Fix** — change the code, or the testing instructions, yourself (the
18842
+ criteria are settled: an edit to them is refused); the suite runs and the
18843
+ slice is tested again, with a fresh round budget
18844
+ - **Overrule** — land a clean tree to go on to the quality lap with these
18845
+ items open
18846
+ `;
18847
+ const prototypeUnguidedMessage = () => `The hands-on test found no way to exercise this project for real — no
18848
+ project skill, no manual testing section in \`AGENTS.md\`, \`CLAUDE.md\` or
18849
+ \`README.md\`, and no project shape it recognised. \`.gtd/PROTOTYPE.md\` says
18850
+ what it looked for.
18851
+
18852
+ Two ways forward (then run \`gtd land\`):
18853
+ - **Instruct** — add testing instructions (a project skill, or a manual
18854
+ testing section); the slice is tested again
18855
+ - **Skip** — land a clean tree to go on to the quality lap untested
18856
+ `;
18687
18857
  //#endregion
18688
18858
  //#region src/workflows/shared/steps.ts
18689
18859
  const FEEDBACK = ".gtd/FEEDBACK.md";
@@ -18695,6 +18865,7 @@ const REFACTOR_NOTE = ".gtd/REFACTOR-NOTE.md";
18695
18865
  const QUALITY = ".gtd/QUALITY.md";
18696
18866
  const SLICES = ".gtd/SLICES.md";
18697
18867
  const HANDOFF = ".gtd/HANDOFF.md";
18868
+ const PROTOTYPE = ".gtd/PROTOTYPE.md";
18698
18869
  const planner = () => env.plannerModel ?? "";
18699
18870
  const coder = () => env.coderModel ?? "";
18700
18871
  const fixCheck = (rules) => agentWithSkills("fix", buildFixPrompt(rules), {
@@ -38412,6 +38583,7 @@ const ShippedOptions = Struct({
38412
38583
  qualityReviews: setting$1,
38413
38584
  refactorQualityReviews: setting$1,
38414
38585
  continuations: setting$1,
38586
+ prototypeRounds: setting$1,
38415
38587
  continuationStall: setting$1,
38416
38588
  testCommand: setting$1,
38417
38589
  plannerModel: setting$1,
@@ -39052,7 +39224,8 @@ const severityOf = (note, judgment, minP) => {
39052
39224
  if (critical.length > 0) return {
39053
39225
  tier: "critical",
39054
39226
  reason: `critical: ${critical.join(", ")}`,
39055
- rank: rank()
39227
+ rank: rank(),
39228
+ ...a("security") === "yes" ? { security: true } : {}
39056
39229
  };
39057
39230
  const should = [...a("blastRadius") === "contract" ? ["contract"] : [], ...a("newConcept") === "yes" ? ["new concept"] : []];
39058
39231
  if (should.length > 0) return {
@@ -39066,29 +39239,30 @@ const severityOf = (note, judgment, minP) => {
39066
39239
  rank: 2
39067
39240
  };
39068
39241
  };
39069
- const severityQuestions = (note) => [
39242
+ /** `asks` names the change in full, `change` refers back to it. */
39243
+ const severityQuestions = (id, asks, change) => [
39070
39244
  {
39071
- id: `${note.id}.security`,
39245
+ id: `${id}.security`,
39072
39246
  primitive: "noul",
39073
- instructions: `Does the change the human asks for in note "${note.id}" (on "${note.anchor}") touch security — authentication, authorisation, secrets, untrusted input or data exposure?`,
39247
+ instructions: `Does ${asks} touch security — authentication, authorisation, secrets, untrusted input or data exposure?`,
39074
39248
  criteria: "yes: it changes or could weaken a security property. no: it does not."
39075
39249
  },
39076
39250
  {
39077
- id: `${note.id}.reversibility`,
39251
+ id: `${id}.reversibility`,
39078
39252
  primitive: "choice",
39079
- instructions: `Once the change in note "${note.id}" ships, can it be undone without breaking anyone who already depends on it?`,
39253
+ instructions: `Once ${change} ships, can it be undone without breaking anyone who already depends on it?`,
39080
39254
  criteria: "one-way: a published API, file format, persisted data, protocol or other contract that cannot be taken back. two-way: can be reverted freely."
39081
39255
  },
39082
39256
  {
39083
- id: `${note.id}.blastRadius`,
39257
+ id: `${id}.blastRadius`,
39084
39258
  primitive: "choice",
39085
- instructions: `How far does the change in note "${note.id}" reach?`,
39259
+ instructions: `How far does ${change} reach?`,
39086
39260
  criteria: "local: one function or file. module: a whole module or several files. contract: a public interface other code or users depend on."
39087
39261
  },
39088
39262
  {
39089
- id: `${note.id}.newConcept`,
39263
+ id: `${id}.newConcept`,
39090
39264
  primitive: "noul",
39091
- instructions: `Does the change in note "${note.id}" introduce a concept new to this codebase — a new abstraction, entity or term? Judge from the evidence, never a glossary; reusing or extending an existing concept is not new.`,
39265
+ instructions: `Does ${change} introduce a concept new to this codebase — a new abstraction, entity or term? Judge from the evidence, never a glossary; reusing or extending an existing concept is not new.`,
39092
39266
  criteria: "yes: a genuinely new concept. no: reuses or extends an existing one."
39093
39267
  }
39094
39268
  ];
@@ -39105,7 +39279,7 @@ const triage$1 = (severities) => async (notes) => {
39105
39279
  answers: {},
39106
39280
  truncated: []
39107
39281
  } : await judge("review.triage", {
39108
- questions: asked.flatMap((n) => [verdictQuestion(n), ...severityQuestions(n)]),
39282
+ questions: asked.flatMap((n) => [verdictQuestion(n), ...severityQuestions(n.id, `the change the human asks for in note "${n.id}" (on "${n.anchor}")`, `the change in note "${n.id}"`)]),
39109
39283
  evidence: Object.fromEntries(asked.map((n) => [n.id, `Anchor: ${n.anchor}\nReviewer's text: ${n.before}\nHuman's text: ${n.text}`])),
39110
39284
  message: buildReviewTriageMessage(),
39111
39285
  label: "Judging each note"
@@ -39121,6 +39295,67 @@ const triage$1 = (severities) => async (notes) => {
39121
39295
  return [n.id, "edit"];
39122
39296
  }));
39123
39297
  };
39298
+ /** Review order: security, one-way doors, the judge unsure, then should, then could. */
39299
+ const chunkKey = (s) => s.tier === "could" ? 4 : s.tier === "should" ? 3 : s.security === true ? 0 : s.rank === 0 ? 1 : 2;
39300
+ /** A chunk's text, then the code each of its pointers covers: a bare path the whole file. */
39301
+ const chunkEvidence = (text, files) => [text, ...files.map(({ path, line, rangeEnd }) => {
39302
+ const where = `${path}${line === void 0 ? "" : `#${line}${rangeEnd === void 0 ? "" : `-${rangeEnd}`}`}`;
39303
+ const lines = read(path.replace(/^\.\//, ""))?.split("\n");
39304
+ if (lines === void 0) return `${where}: no longer exists`;
39305
+ return `${where}:\n${(line === void 0 ? lines : lines.slice(line - 1, rangeEnd ?? line)).join("\n")}`;
39306
+ })].join("\n\n");
39307
+ const bytes = (text) => new TextEncoder().encode(text).length;
39308
+ /** Fills each batch while the budget's even share still holds its widest evidence, so nothing is cut that fits alone. */
39309
+ const batched = (items, budget) => {
39310
+ const batches = [];
39311
+ let batch = [];
39312
+ let widest = 0;
39313
+ for (const item of items) {
39314
+ const size = bytes(item.evidence);
39315
+ if (batch.length > 0 && Math.floor(budget / (batch.length + 1)) < Math.max(widest, size)) {
39316
+ batches.push(batch);
39317
+ batch = [];
39318
+ widest = 0;
39319
+ }
39320
+ batch.push(item);
39321
+ widest = Math.max(widest, size);
39322
+ }
39323
+ return batch.length === 0 ? batches : [...batches, batch];
39324
+ };
39325
+ /**
39326
+ * Rates every chunk the reviewer wrote — a walkthrough chunk or a carried
39327
+ * finding — and sorts \`full\` by it, each chunk led by its tier and reason.
39328
+ * gtd's summary chunks are not rated and stay last.
39329
+ */
39330
+ const ranked = async (review, full) => {
39331
+ const texts = chunkTexts(full);
39332
+ const chunks = reviewChunks(review).map((c, i) => ({
39333
+ id: `C${i + 1}`,
39334
+ title: c.title,
39335
+ evidence: chunkEvidence(texts.get(c.title) ?? "", c.files)
39336
+ }));
39337
+ if (chunks.length === 0) return full;
39338
+ const minP = numeric(vars.reviewSeverityMinP, Infinity);
39339
+ const batches = batched(chunks, numeric(vars.judgeBudgetBytes, 32768));
39340
+ const severities = /* @__PURE__ */ new Map();
39341
+ for (const [i, batch] of batches.entries()) {
39342
+ const judgment = await judge("review.ranking", {
39343
+ questions: batch.flatMap((c) => severityQuestions(c.id, `the change review chunk "${c.id}" ("${c.title}") walks through`, `the change in review chunk "${c.id}"`)),
39344
+ evidence: Object.fromEntries(batch.map((c) => [c.id, c.evidence])),
39345
+ message: reviewRankingMessage(),
39346
+ label: batches.length === 1 ? "Ranking the review" : `Ranking the review (${i + 1}/${batches.length})`
39347
+ });
39348
+ for (const c of batch) severities.set(c.id, severityOf(c, judgment, minP));
39349
+ }
39350
+ return rankedReview(full, chunks.map((c, i) => ({
39351
+ c,
39352
+ i,
39353
+ s: severities.get(c.id)
39354
+ })).sort((x, y) => chunkKey(x.s) - chunkKey(y.s) || x.i - y.i).map(({ c, s }) => ({
39355
+ title: c.title,
39356
+ label: s.reason
39357
+ })));
39358
+ };
39124
39359
  const isCode$1 = (path) => path !== ".gtd" && !path.startsWith(".gtd/");
39125
39360
  const composedOf = (base, report, nitFix, answered) => ({
39126
39361
  ...report,
@@ -39172,12 +39407,13 @@ const requirePointers = (review, notes, codeChanged) => {
39172
39407
  const walkthrough = async (base, report, lap, carry) => {
39173
39408
  const notes = titledNotes(report);
39174
39409
  await reviewing(base, carry, lap, walkthroughBlock(notes, report.nitFix));
39175
- const review = read(REVIEW);
39176
- if (review === void 0) return;
39177
- requirePointers(review, notes, changesSince(base).some((c) => isCode$1(c.path)));
39178
- const full = withWalkthrough(review, report);
39179
- if (full !== review) await run("review.carrying", writeScript(REVIEW, full), {
39180
- label: "Adding the quality lap's findings",
39410
+ const written = read(REVIEW);
39411
+ if (written === void 0) return;
39412
+ const review = unranked(written);
39413
+ requirePointers(written, notes, changesSince(base).some((c) => isCode$1(c.path)));
39414
+ const full = await ranked(review, withWalkthrough(review, report));
39415
+ if (full !== written) await run("review.carrying", writeScript(REVIEW, full), {
39416
+ label: "Ordering the review and adding the quality lap's findings",
39181
39417
  file: REVIEW,
39182
39418
  base
39183
39419
  });
@@ -39597,7 +39833,9 @@ const reviewedRefactor = async (shape, options = {}) => {
39597
39833
  feedback,
39598
39834
  fixAccess: strayAccess(p.strays),
39599
39835
  lensBase: entry,
39600
- rules: refactorRules(p.strays, p.pattern)
39836
+ rules: refactorRules(p.strays, p.pattern),
39837
+ walkthrough: true,
39838
+ lap: "refactor"
39601
39839
  });
39602
39840
  if (outcome.verdict === "signoff") return true;
39603
39841
  await reUnwind(outcome);
@@ -39612,7 +39850,7 @@ const reviewedRefactor = async (shape, options = {}) => {
39612
39850
  const COVERED_MIN_P = .7;
39613
39851
  /** Covering turns before the process rests at `uncovered`; a human edit there refills them. */
39614
39852
  const COVERING_RETRIES = 2;
39615
- const frozen = () => requireCriteriaFrozen(REQUIREMENTS);
39853
+ const frozen$1 = () => requireCriteriaFrozen(REQUIREMENTS);
39616
39854
  const green = (guard) => healthy(() => agentWithSkills("fix.suite.fixing", implementFixPrompt(), {
39617
39855
  label: "Fixing the check",
39618
39856
  file: FEEDBACK,
@@ -39620,7 +39858,7 @@ const green = (guard) => healthy(() => agentWithSkills("fix.suite.fixing", imple
39620
39858
  system: builderSystem()
39621
39859
  }), {
39622
39860
  guard: () => {
39623
- frozen();
39861
+ frozen$1();
39624
39862
  guard?.();
39625
39863
  },
39626
39864
  guardRests: false
@@ -39673,7 +39911,7 @@ const requireCoverage = async (base = start(), slice) => {
39673
39911
  system: builderSystem(),
39674
39912
  allowEmpty: true
39675
39913
  });
39676
- frozen();
39914
+ frozen$1();
39677
39915
  } else {
39678
39916
  await human("uncovered", {
39679
39917
  message: uncoveredMessage(uncovered),
@@ -39682,13 +39920,136 @@ const requireCoverage = async (base = start(), slice) => {
39682
39920
  acceptClean: true
39683
39921
  });
39684
39922
  if (!changes().some((c) => isCode$1(c.path))) return record(covered, uncovered);
39685
- frozen();
39923
+ frozen$1();
39686
39924
  retries = 0;
39687
39925
  }
39688
39926
  await green();
39689
39927
  }
39690
39928
  };
39691
39929
  //#endregion
39930
+ //#region src/workflows/feature/prototype.ts
39931
+ /** The `prototypeRounds` setting. Anything but a non-negative integer fails rather than guess a cap. */
39932
+ const prototypeRounds = () => {
39933
+ const value = vars.prototypeRounds ?? defaults.prototypeRounds;
39934
+ if (!/^(0|[1-9]\d*)$/.test(value)) refuse(`gtd: prototypeRounds must be a non-negative integer, got ${JSON.stringify(value)}`);
39935
+ return Number(value);
39936
+ };
39937
+ const CHUNK = /^(AC-\d+|Problem\b.*?)\s*:\s*(pass|fail|blocked)$/i;
39938
+ const UNGUIDED = /^no testing instructions$/i;
39939
+ /** The `## ` chunks of a prototype report; a heading in neither shape is prose, not an item. */
39940
+ const reportOf = (text) => {
39941
+ let unguided = false;
39942
+ const items = [];
39943
+ const malformed = [];
39944
+ for (const { line, fenced } of markFenced(text)) {
39945
+ const heading = fenced ? null : HEADING.exec(line);
39946
+ if (heading?.[1] !== "##") continue;
39947
+ if (UNGUIDED.test(heading[2])) unguided = true;
39948
+ const chunk = CHUNK.exec(heading[2]);
39949
+ if (chunk === null && /^(ac-\d|problem)/i.test(heading[2])) malformed.push(heading[2]);
39950
+ if (chunk !== null) items.push({
39951
+ subject: /^ac-/i.test(chunk[1]) ? chunk[1].toUpperCase() : chunk[1],
39952
+ verdict: chunk[2].toLowerCase()
39953
+ });
39954
+ }
39955
+ return {
39956
+ unguided,
39957
+ items,
39958
+ malformed
39959
+ };
39960
+ };
39961
+ /**
39962
+ * The report the testing turn left, refused unless it is the only change and
39963
+ * names every criterion once, with its open items: another slice's criteria
39964
+ * are not this slice's to fix.
39965
+ */
39966
+ const requireReport = (slice, retest) => {
39967
+ const others = changes().map((c) => c.path).filter((path) => path !== PROTOTYPE);
39968
+ 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")}`);
39969
+ const text = read(PROTOTYPE);
39970
+ if (text === void 0) return refuse(`gtd land: prototype-report: write ${PROTOTYPE}`);
39971
+ const report = reportOf(text);
39972
+ 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")}`);
39973
+ if (report.unguided && report.items.length === 0 && !retest) return {
39974
+ unguided: true,
39975
+ open: []
39976
+ };
39977
+ const requirements = read(".gtd/REQUIREMENTS.md") ?? "";
39978
+ const ids = criterionIds(requirements, slice);
39979
+ const elsewhere = criterionIds(requirements).filter((id) => !ids.includes(id));
39980
+ const count = (id) => report.items.filter((i) => i.subject === id).length;
39981
+ const wrong = ids.filter((id) => count(id) !== 1);
39982
+ if (wrong.length > 0) refuse(`gtd land: prototype-report: ${PROTOTYPE} needs exactly one chunk headed \`## <ID>: pass|fail|blocked\` for ${wrong.join(", ")}`);
39983
+ return {
39984
+ unguided: false,
39985
+ open: report.items.filter((i) => i.verdict !== "pass" && !elsewhere.includes(i.subject))
39986
+ };
39987
+ };
39988
+ const frozen = () => requireCriteriaFrozen(REQUIREMENTS);
39989
+ const testing = async (slice, retest) => {
39990
+ await agentWithSkills("testing", prototypeTestingPrompt(sliceIds(slice), retest), {
39991
+ label: retest ? "Re-testing the slice by hand" : "Testing the slice by hand",
39992
+ file: PROTOTYPE,
39993
+ model: coder(),
39994
+ system: testerSystem(),
39995
+ allowEmpty: true
39996
+ });
39997
+ return requireReport(slice, retest);
39998
+ };
39999
+ const fixing = async (slice, guard) => {
40000
+ await scope("fix", () => agentWithSkills("fixing", prototypeFixingPrompt(sliceIds(slice)), {
40001
+ label: "Fixing what the hands-on test found",
40002
+ file: PROTOTYPE,
40003
+ model: coder(),
40004
+ system: builderSystem(),
40005
+ allowEmpty: true
40006
+ }));
40007
+ 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`);
40008
+ frozen();
40009
+ guard?.();
40010
+ };
40011
+ /** A person's rest; resolves `true` when they changed something to test again, `false` on a clean overrule. */
40012
+ const askPerson = async (name, message, label) => {
40013
+ await human(name, {
40014
+ message,
40015
+ label,
40016
+ file: PROTOTYPE,
40017
+ acceptClean: true
40018
+ });
40019
+ if (!changes().some((c) => c.path !== ".gtd/PROTOTYPE.md")) return false;
40020
+ frozen();
40021
+ return true;
40022
+ };
40023
+ /**
40024
+ * Use the slice by hand: a testing turn reports on every criterion, a fix turn
40025
+ * takes each failure, and the slice is tested again — until the report is
40026
+ * clean, or `prototypeRounds` fixes are spent and a person decides. `guard`
40027
+ * holds every fix turn, as it holds the implementation's.
40028
+ */
40029
+ const prototypeTest = async (slice, guard) => {
40030
+ const cap = prototypeRounds();
40031
+ if (cap === 0) return;
40032
+ await scope("prototype", async () => {
40033
+ let fixes = 0;
40034
+ let retest = false;
40035
+ for (;;) {
40036
+ const { unguided, open } = await testing(slice, retest);
40037
+ retest = !unguided;
40038
+ if (unguided) {
40039
+ if (!await askPerson("unguided", prototypeUnguidedMessage(), "No way to test by hand")) return;
40040
+ } else if (open.length === 0) return;
40041
+ else if (fixes < cap && open.some((i) => i.verdict === "fail")) {
40042
+ fixes++;
40043
+ await fixing(slice, guard);
40044
+ } else {
40045
+ if (!await askPerson("open", prototypeOpenMessage(open, fixes), "Hands-on test still failing")) return;
40046
+ fixes = 0;
40047
+ }
40048
+ await green(guard);
40049
+ }
40050
+ });
40051
+ };
40052
+ //#endregion
39692
40053
  //#region src/workflows/feature/seam.ts
39693
40054
  /** Naive-pass retries after a `reject` or a red suite; the next one rests at `seam.steer`. */
39694
40055
  const NAIVE_RETRIES = 2;
@@ -39895,8 +40256,10 @@ const implementing = (options) => scope("implement", async () => {
39895
40256
  ...acceptance,
39896
40257
  ids: settled(acceptance.ids, options.slice)
39897
40258
  }, since);
39898
- await green(built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0);
40259
+ const keepsTests = built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0;
40260
+ await green(keepsTests);
39899
40261
  await requireCoverage(options.base, options.slice);
40262
+ await prototypeTest(options.slice, keepsTests);
39900
40263
  const ids = criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
39901
40264
  await closing(moreToBuild(options.more));
39902
40265
  return ids;
@@ -39905,11 +40268,16 @@ const moreToBuild = (more) => typeof more === "function" ? more() : more === tru
39905
40268
  /** The last slice's closing, for a last slice a steer left with no criterion to build. */
39906
40269
  const closeOut = () => scope("implement", () => closing(false));
39907
40270
  /** A slice with `more` after it keeps the criteria and the slicing for the next one. */
39908
- const closing = (more) => run("closing", removeScript(more ? [REFACTOR_NOTE, HANDOFF] : [
40271
+ const closing = (more) => run("closing", removeScript(more ? [
40272
+ REFACTOR_NOTE,
40273
+ HANDOFF,
40274
+ PROTOTYPE
40275
+ ] : [
39909
40276
  REQUIREMENTS,
39910
40277
  REFACTOR_NOTE,
39911
40278
  SLICES,
39912
- HANDOFF
40279
+ HANDOFF,
40280
+ PROTOTYPE
39913
40281
  ]), { label: "Closing out the implementation" });
39914
40282
  /** Resolves `true` when the human steered (edited), `false` on a clean overrule. */
39915
40283
  const steer = async (retry) => {
@@ -40274,6 +40642,7 @@ const ordinaryStart = async () => {
40274
40642
  refusePendingHandOff();
40275
40643
  flagModeOn();
40276
40644
  sliceCap();
40645
+ prototypeRounds();
40277
40646
  const stated = statedCriteria(changes(), head$1());
40278
40647
  await unwind();
40279
40648
  if (changes(".gtd/FEEDBACK.md").some((c) => c.status !== "deleted")) await human("unwind-failed", {
@@ -40308,6 +40677,10 @@ const skills = (vars) => {
40308
40677
  seam: ["incremental-implementation"],
40309
40678
  implement: ["test-driven-development", "incremental-implementation"],
40310
40679
  "implement.fix.suite": ["debugging-and-error-recovery"],
40680
+ "implement.prototype": [],
40681
+ "implement.prototype.fix": ["test-driven-development", "incremental-implementation"],
40682
+ "implement.prototype.fix.suite": ["debugging-and-error-recovery"],
40683
+ "implement.prototype.health": ["debugging-and-error-recovery"],
40311
40684
  "implement.health": ["debugging-and-error-recovery"],
40312
40685
  ...lensScopes("build.quality", lensesOf(vars.qualityReviews ?? ""), (lens) => builtInLenses[lens]?.skills ?? [lens]),
40313
40686
  ...lensScopes("refactor.build.quality", lensesOf(vars.refactorQualityReviews ?? ""), (lens) => builtInLenses[lens]?.skills ?? [lens])
@@ -40485,4 +40858,4 @@ var workflows_default = feature;
40485
40858
  */
40486
40859
  const doors = {};
40487
40860
  //#endregion
40488
- export { COMMENT, ESCALATION, FEEDBACK, FIX_CAP, FlagOptions, HANDOFF, 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, describeEscalation, design, doors, envDefaults, escalate, escalation, escalationExhausted, feature, fix, fixCheck, fixFlow, fixNits, fixQuality, fixReviewNotes, fixRisks, gate, guarded, handOffReview, handOffTail, healthy, holdsCallSites, implement, implementing, isTestFile, judgeSeam, lensEntriesOf, lensOf, lensesOf, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, planner, qualityLap, reUnwind, recheck, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireCriteriaIfEdited, requireNoNewFlags, requireNoTestFiles, requireSlices, requireStableIds, 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, unstableIds, unwind };
40861
+ export { COMMENT, ESCALATION, FEEDBACK, FIX_CAP, FlagOptions, HANDOFF, PROTOTYPE, 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, describeEscalation, design, doors, envDefaults, escalate, escalation, escalationExhausted, feature, fix, fixCheck, fixFlow, fixNits, fixQuality, fixReviewNotes, fixRisks, gate, guarded, handOffReview, handOffTail, healthy, holdsCallSites, implement, implementing, isTestFile, judgeSeam, lensEntriesOf, lensOf, lensesOf, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, planner, qualityLap, reUnwind, recheck, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireCriteriaIfEdited, requireNoNewFlags, requireNoTestFiles, requireSlices, requireStableIds, 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, unstableIds, unwind };