@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
package/dist/steering.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { A as formatFinding, C as openThreadFindings, D as getParseCount, E as parseThreadsWithFindings, O as headingSectionBodies, S as THREAD_REPLY_ACTION_TITLE, T as parseThreads, _ as reviewRisks, a as unansweredQuestions, b as isAnswered, c as stripCodeThreads, d as ANSWER_FIX, f as ANSWER_LEAD, g as reviewNotes, h as reviewDecisions, i as steeringFormatOrFreeForm, k as headingSections, l as freeFormFormat, m as reviewChunks, n as openThreads, o as viewOf, p as ANSWER_WONT_FIX, r as steeringFormatFor, s as parseCodeThreads, t as BUILT_IN_MODE_NAMES, u as applySteeringEdits, v as unansweredNotes, w as parseFootnotes, x as FOOTNOTE_ACTION_TITLE, y as FREE_TEXT_PLACEHOLDER } from "./chunks/steering-BXwjmocB.mjs";
2
- export { ANSWER_FIX, ANSWER_LEAD, ANSWER_WONT_FIX, BUILT_IN_MODE_NAMES, FOOTNOTE_ACTION_TITLE, FREE_TEXT_PLACEHOLDER, THREAD_REPLY_ACTION_TITLE, applySteeringEdits, formatFinding, freeFormFormat, getParseCount, headingSectionBodies, headingSections, isAnswered, openThreadFindings, openThreads, parseCodeThreads, parseFootnotes, parseThreads, parseThreadsWithFindings, reviewChunks, reviewDecisions, reviewNotes, reviewRisks, steeringFormatFor, steeringFormatOrFreeForm, stripCodeThreads, unansweredNotes, unansweredQuestions, viewOf };
1
+ import { A as headingSections, C as THREAD_REPLY_ACTION_TITLE, D as parseThreadsWithFindings, E as parseThreads, O as getParseCount, S as FOOTNOTE_ACTION_TITLE, T as parseFootnotes, _ as reviewRisks, a as unansweredQuestions, b as FREE_TEXT_PLACEHOLDER, c as stripCodeThreads, d as ANSWER_FIX, f as ANSWER_LEAD, g as reviewNotes, h as reviewDecisions, i as steeringFormatOrFreeForm, j as formatFinding, k as headingSectionBodies, l as freeFormFormat, m as reviewChunks, n as openThreads, o as viewOf, p as ANSWER_WONT_FIX, r as steeringFormatFor, s as parseCodeThreads, t as BUILT_IN_MODE_NAMES, u as applySteeringEdits, v as severityLine, w as openThreadFindings, x as isAnswered, y as unansweredNotes } from "./chunks/steering-DtUVUz9l.mjs";
2
+ export { ANSWER_FIX, ANSWER_LEAD, ANSWER_WONT_FIX, BUILT_IN_MODE_NAMES, FOOTNOTE_ACTION_TITLE, FREE_TEXT_PLACEHOLDER, THREAD_REPLY_ACTION_TITLE, applySteeringEdits, formatFinding, freeFormFormat, getParseCount, headingSectionBodies, headingSections, isAnswered, openThreadFindings, openThreads, parseCodeThreads, parseFootnotes, parseThreads, parseThreadsWithFindings, reviewChunks, reviewDecisions, reviewNotes, reviewRisks, severityLine, steeringFormatFor, steeringFormatOrFreeForm, stripCodeThreads, unansweredNotes, unansweredQuestions, viewOf };
package/dist/testkit.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { answered, applyScript, checkScript, configProblems, configure, installContext, isFactoryWorkflow, moveScript, numeric, protocolMismatch, removeScript, restoreScript, revertScript, showScript, t as flows_exports, writeScript } from "./flows.mjs";
2
2
  import { A as beatKindOf, B as ACCESS_REFUSAL, C as renderLandPlain, D as renderBeatJson, E as beatDocument, O as statusOf, S as renderLandJson, T as renderBeatPlain, U as renderFormat, _ as selectPath, a as contextLimitFault, g as resolveSession, h as globMatches, j as demandOf, l as parseContextWindow, m as slicesDocument, o as contextShapeFault, p as renderSlicesJson, v as judgeJson, x as noopText, y as landFields } from "./chunks/wire-BJuZWR3C.mjs";
3
- import { A as formatFinding, E as parseThreadsWithFindings, O as headingSectionBodies, a as unansweredQuestions, k as headingSections, n as openThreads, r as steeringFormatFor, s as parseCodeThreads, t as BUILT_IN_MODE_NAMES } from "./chunks/steering-BXwjmocB.mjs";
3
+ import { A as headingSections, D as parseThreadsWithFindings, a as unansweredQuestions, j as formatFinding, k as headingSectionBodies, n as openThreads, r as steeringFormatFor, s as parseCodeThreads, t as BUILT_IN_MODE_NAMES } from "./chunks/steering-DtUVUz9l.mjs";
4
4
  import { $ as logWarning, $i as BaseProto, $n as map$5, $r as make$19, $t as provide$1, A as catchIf, Ai as add$1, An as scopeMake, Ar as flatMap$3, At as suspend$5, B as flatMap$4, Bi as getOrElse, Bn as get$3, Br as sync$1, Bt as unsafeMakeLatch, C as all$1, Ci as toArray, Cn as isInterrupted, Cr as exitFail, Ct as sandbox, D as async, Di as GenericTag, Dn as squash, Dr as exitZipRight, Dt as scopedWith$1, E as asVoid$1, Ei as unsafeHead, En as reduceWithContext, Er as exitVoid, Et as scoped$4, F as ensuring$2, Fi as fromIterable, Fn as join$3, Fr as map$3, Ft as tryMap, G as forkIn, Gi as match$4, Gn as TypeId$10, Gr as whenEffect, Gt as zipLeft, H as fnUntraced, Hi as isNone, Hn as modify$1, Hr as uninterruptible, Ht as when, I as exit, Ii as splitAt, In as sequential, Ir as onInterrupt, It as tryPromise, J as ignore, Ji as isLeft, Jn as all, Jr as DeferredTypeId$1, Jt as spaced$1, K as forkScoped, Ki as none, Kn as Class, Kr as withFiberRuntime, Kt as zipRight$3, L as fail$5, Li as filter$1, Ln as bind, Lr as scopeClose, Lt as try_, M as die$2, Mi as getOption, Mn as _await$2, Mr as interruptAsFiber, Mt as tap, N as dieMessage, Ni as omit, Nn as inheritAll$1, Nr as interruptFiber, Nt as tapErrorCause, O as catchAll$1, Oi as Reference, On as ensuring$3, Or as fiberId, Ot as serviceOption, P as either, Pi as empty$5, Pn as interruptAllAs$1, Pr as interruptible$1, Pt as timeout, Q as isEffect, Qi as pipeArguments, Qn as isFailure, Qr as ids, Qt as mergeAll$2, R as failCause$4, Ri as flatMap$5, Rn as bindTo, Rr as succeed$4, Rt as uninterruptible$1, S as addFinalizer$1, Si as takeRight, Sn as isDieType, Sr as exitAs, St as runtime, T as as, Ti as unsafeGet, Tn as map$6, Tr as exitSucceed, Tt as scopeWith, U as forEach$2, Ui as isSome, Un as set$1, Ur as uninterruptibleMask$1, Ut as withFiberRuntime$1, V as flatten$4, Vi as getOrUndefined, Vn as make$16, Vr as tap$1, Vt as unsafeMakeSemaphore$1, W as forkDaemon, Wi as map$8, Wn as update$1, Wr as void_$2, Wt as zip$1, X as interruptible, Xi as match$2, Xn as fail$4, Xr as combine, Xt as tagged, Y as interrupt$4, Yi as left, Yn as die, Yr as empty$6, Yt as TaggedError$1, Z as intoDeferred, Zi as right, Zn as failCause$3, Zr as combineAll, Zt as fromEffect$5, _ as decodeSync, _i as of, _n as fail$3, _r as deferredIsDone, _t as raceWith, a as Literal, aa as constFalse, ai as size$2, an as updateContext$1, ar as zipRight$2, at as matchCause, b as acquireRelease, bi as reverse, bn as interrupt$3, br as deferredUnsafeDone, bt as runPromise, c as String$, ca as constVoid, ci as append$1, cn as Scope, cr as asyncInterrupt, ct as negate, d as Union, da as pipe, di as empty$4, dn as close, dr as deferredAwait, dt as orElse, ea as NodeInspectSymbol, ei as none$1, en as provideMerge$1, er as mapBoth, et as makeSemaphore, f as between, fi as filter, fn as extend, fr as deferredComplete, ft as orElseSucceed, gi as map$7, gn as die$1, gr as deferredInterruptWith, gt as race, hi as isEmpty$2, hn as NoSuchElementException, hr as deferredInterrupt, ht as provideService, i as Defect, ia as hasProperty, ii as has, in as unsafeRunPromiseExit, ir as zip$2, it as match$5, j as catchTag, ji as get$5, jn as scopeTag, jr as interrupt$2, jt as sync$2, k as catchAllCause, ki as Tag, kn as forEachConcurrentDiscard, kr as fiberRefLocally, kt as succeed$6, l as Struct, la as dual, li as appendAll, ln as addFinalizer, lr as catchAll, lt as onError, m as optional$1, mi as isChunk, mn as make$18, mr as deferredFailCause, mt as provide$2, n as Boolean$, na as toJSON, ni as make$17, nn as succeed$3, nr as succeed$5, nt as mapBoth$1, o as Never, oa as constTrue, oi as decode, on as forkIn$1, or as as$1, ot as matchCauseEffect, p as decodeUnknown, pi as fromIterable$1, pn as fork, pr as deferredFail, pt as promise, q as gen, qi as some, qn as spanTag, qr as zipRight$1, qt as zipWith, r as Cause, ra as equals, ri as set$2, rn as unsafeFork, rr as void_$3, rt as mapError, s as Number$, sa as constUndefined, si as isFinite, sn as unsafeMakeSemaphore, sr as asVoid, st as matchEffect, t as Array$, ta as format, ti as get$4, tn as scoped$3, tr as match$3, tt as map$4, u as TaggedError, ua as identity, ui as drop, un as addFinalizerExit, ur as currentContext$1, ut as orDie, v as encodeSync, vi as prepend, vn as failureOrCause, vr as deferredMake, vt as repeat, w as annotateLogs, wi as unsafeFromArray, wn as isInterruptedOnly, wr as exitFailCause, wt as scope, x as acquireUseRelease$2, xi as take$2, xn as interruptors, xr as deferredUnsafeMake, xt as runPromiseExit$1, y as _void, yi as reduce, yn as flipCauseOption, yr as deferredSucceed, yt as retry, z as fiberIdWith, zi as fromNullable, zn as sleep, zr as suspend$4, zt as uninterruptibleMask } from "./chunks/Schema-C6fLlQzx.mjs";
5
5
  import { createRequire } from "node:module";
6
6
  import * as Crypto from "node:crypto";
package/dist/web/main.js CHANGED
@@ -31565,6 +31565,8 @@ const qaDescriptor = {
31565
31565
  unansweredQuestions,
31566
31566
  openThreads: openThreadsOf
31567
31567
  };
31568
+ /** A chunk's leading `_critical: security_` paragraph: the tier gtd ranked it with, and why. */
31569
+ const SEVERITY_LINE_RE = /^_((?:critical|should|could)(?::[^_\n]*)?)_$/;
31568
31570
  /**
31569
31571
  * The canonical sample. Every detail here is load-bearing for the formatter
31570
31572
  * round-trip: both footnote bodies exceed 80 characters so a reflow actually
@@ -31855,12 +31857,26 @@ const answerListOf = (content, body) => {
31855
31857
  })
31856
31858
  };
31857
31859
  };
31860
+ /** A leading severity line is the chunk's tier, never its prose. */
31861
+ const splitSeverity = (content, run) => {
31862
+ const first = run[0];
31863
+ const label = first?.type === "paragraph" ? SEVERITY_LINE_RE.exec(sourceText(content, first).trim())?.[1] : void 0;
31864
+ if (label === void 0) return { leadingRun: run };
31865
+ return {
31866
+ severity: {
31867
+ label,
31868
+ line: toLspPosition(first.position.start).line
31869
+ },
31870
+ leadingRun: run.slice(1)
31871
+ };
31872
+ };
31858
31873
  /** 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). */
31859
31874
  const parseChunkBody = (content, lines, title, body) => {
31860
31875
  const answer = answerListOf(content, body);
31861
31876
  const isAnswer = (n) => answer !== void 0 && (n === answer.list || n === body[answer.lead]);
31862
31877
  const firstPointerIndex = body.findIndex((n) => !isAnswer(n) && taskItems(n).some((item) => hasPointerToken(content, item)));
31863
- const leadingRun = (firstPointerIndex === -1 ? body : body.slice(0, firstPointerIndex)).filter((n) => !isAnswer(n));
31878
+ const run = (firstPointerIndex === -1 ? body : body.slice(0, firstPointerIndex)).filter((n) => !isAnswer(n));
31879
+ const { severity, leadingRun } = splitSeverity(content, run);
31864
31880
  const descriptionNodes = blockNodesOfRun(content, leadingRun);
31865
31881
  const descriptionInline = collapseImages(blockRunInline(content, leadingRun));
31866
31882
  const description = leadingRun.filter((n) => n.type !== "footnoteDefinition").map((n) => sourceText(content, n)).join(" ").trim();
@@ -31879,6 +31895,7 @@ const parseChunkBody = (content, lines, title, body) => {
31879
31895
  files,
31880
31896
  errors,
31881
31897
  ...answer === void 0 ? {} : { answers: answer.options },
31898
+ ...severity === void 0 ? {} : { severity },
31882
31899
  boxed: body.some((n) => !isAnswer(n) && taskItems(n).length > 0)
31883
31900
  };
31884
31901
  };
@@ -31915,7 +31932,7 @@ const parseChangesets = (tree, content, lines) => {
31915
31932
  const errors = [];
31916
31933
  for (const { heading, headingLine, body } of splitChunks(tree)) {
31917
31934
  const title = headingText$1(content, heading);
31918
- const { description, descriptionNodes, descriptionInline, files, answers, boxed, errors: bodyErrors } = parseChunkBody(content, lines, title, body);
31935
+ const { description, descriptionNodes, descriptionInline, files, answers, severity, boxed, errors: bodyErrors } = parseChunkBody(content, lines, title, body);
31919
31936
  errors.push(...bodyErrors);
31920
31937
  if (files.length === 0 && boxed) errors.push({
31921
31938
  message: `Chunk "${title}" has no file pointers`,
@@ -31929,7 +31946,8 @@ const parseChangesets = (tree, content, lines) => {
31929
31946
  descriptionInline,
31930
31947
  files,
31931
31948
  headingLine,
31932
- ...answers === void 0 ? {} : { answers }
31949
+ ...answers === void 0 ? {} : { answers },
31950
+ ...severity === void 0 ? {} : { severity }
31933
31951
  });
31934
31952
  }
31935
31953
  if (changesets.length === 0) errors.push({ message: "REVIEW.md has no '##' chunks" });
@@ -32230,6 +32248,7 @@ const reviewView = (content) => {
32230
32248
  detail: chunk.description,
32231
32249
  detailInline: chunk.descriptionInline,
32232
32250
  body: chunk.descriptionNodes,
32251
+ ...chunk.severity === void 0 ? {} : { status: chunk.severity.label },
32233
32252
  anchor: {
32234
32253
  kind: "chunk",
32235
32254
  index: chunkIndex
@@ -34779,6 +34798,11 @@ const ChunkRow = ({ chunk, chunkIndex, state }) => {
34779
34798
  disabled: hunks.length === 0,
34780
34799
  className: "flex-1 text-left",
34781
34800
  children: [
34801
+ chunk.status !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
34802
+ "data-testid": `chunk-severity-${chunkIndex}`,
34803
+ className: cn("text-small font-semibold", chunk.status.startsWith("critical") ? "text-warning" : "text-muted"),
34804
+ children: chunk.status
34805
+ }),
34782
34806
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
34783
34807
  className: "font-semibold",
34784
34808
  children: chunk.title
@@ -1,5 +1,5 @@
1
1
  import { changes, changesSince, hasThreadFor, head, mark, read, refuse, requireReplies, requireThreadsClosed, scope } from "./flows.mjs";
2
- import { T as parseThreads, _ as reviewRisks, c as stripCodeThreads, g as reviewNotes, h as reviewDecisions, v as unansweredNotes } from "./chunks/steering-BXwjmocB.mjs";
2
+ import { E as parseThreads, _ as reviewRisks, c as stripCodeThreads, g as reviewNotes, h as reviewDecisions, y as unansweredNotes } from "./chunks/steering-DtUVUz9l.mjs";
3
3
  //#region src/workflow-kit/health.ts
4
4
  /** Hand a red suite that repeated attempts could not fix to a person, until `limit` rounds are spent. */
5
5
  const escalation = async (count, steps) => {
@@ -1,6 +1,6 @@
1
1
  import { agent, changes, glob, read, refuse, scope, start, vars } from "../flows.mjs";
2
- import { Dt as envDefaults, Et as defaults, Ht as requireFlag, S as coder, Vt as registryEntry, i as skills, n as summary, o as flagShipped, r as access, t as steering, zt as flagReferences } from "../chunks/reserved-CYZ1bSBy.mjs";
3
- import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-C1Oi2YEy.mjs";
2
+ import { C as coder, It as defaults, Lt as envDefaults, Qt as requireFlag, Yt as flagReferences, Zt as registryEntry, i as skills, n as summary, o as flagShipped, r as access, t as steering } from "../chunks/reserved-CUkk9XgL.mjs";
3
+ import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-CX6Pdsql.mjs";
4
4
  //#region src/workflows/cleanup/cleanup.ts
5
5
  const removingPrompt = (name, registry) => `Clean up the release flag "${name}", which is default-on: delete its entry in ${registry}, and remove every \`GTD_FLAG_${name.toUpperCase()}\` check from the shipped sources. Keep the flag-on path; delete code only the off path reached, including tests that pin the off path.`;
6
6
  const cleanupFlow = async () => {
@@ -1,8 +1,8 @@
1
1
  import { agent, answered, applyScript, changes, changesSince, check, codeChanges, env, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, matches, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireThreadsClosed, revertScript, run, scope, slice, start, vars, wrote } from "../flows.mjs";
2
2
  import { lensEntriesOf } from "../workflow-kit.mjs";
3
- import { $ as naivePassPrompt, B as coverageQuestion, C as collecting, Dt as envDefaults, Et as defaults, F as builderSystem, G as designTriagePrompt, H as criterionLine, I as clearCriteriaMessage, L as clearCriteriaQuestion, M as agentWithSkills, N as buildReviewCollectingPrompt, O as planner, Ot as testFileGlobs, Q as naiveApplyFailure, S as coder, St as unwindFailure, T as fixNits, U as designGateAnswerMessage, V as coveringPrompt, W as designSystem, X as implementBuildingPrompt, Y as idleMessage, Z as implementFixPrompt, _ as REQUIREMENTS, _t as slicingPrompt, a as flagModeOn, at as refactorDroppedRefusal, b as answerReviewQuestions, bt as uncoveredMessage, ct as refactorRules, d as healthy, et as normaliseCriteriaScript, f as FEEDBACK, g as REFACTOR_NOTE, gt as seamSteerMessage, h as REFACTOR, ht as seamByHandMessage, i as skills, it as refactorBuildingPrompt, l as gate, mt as reviewerSystem, n as summary, ot as refactorFixPrompt, p as HANDOFF, r as access, s as shipped, st as refactorNotePrompt, t as steering, tt as oversizedMessage, vt as startGateBlockedMessage, xt as unwindFailedMessage, y as SLICES, z as coverageMessage } from "../chunks/reserved-CYZ1bSBy.mjs";
4
- import { a as refusePendingHandOff, c as joinReports, o as tail } from "../chunks/tail-C1Oi2YEy.mjs";
5
- import { i as reUnwind, n as humanReview, r as isCode$1, t as buildTail } from "../chunks/review-BewyJflS.mjs";
3
+ import { $ as implementFixPrompt, C as coder, Ct as seamByHandMessage, Dt as testerSystem, E as fixNits, Et as startGateBlockedMessage, G as designGateAnswerMessage, H as coverageQuestion, I as builderSystem, It as defaults, K as designSystem, Lt as envDefaults, Mt as unwindFailure, N as agentWithSkills, P as buildReviewCollectingPrompt, Q as implementBuildingPrompt, R as clearCriteriaMessage, Rt as testFileGlobs, St as reviewerSystem, Tt as slicingPrompt, U as coveringPrompt, V as coverageMessage, W as criterionLine, Z as idleMessage, _ as REFACTOR_NOTE, a as flagModeOn, at as prototypeOpenMessage, b as SLICES, d as healthy, dt as refactorBuildingPrompt, et as naiveApplyFailure, f as FEEDBACK, ft as refactorDroppedRefusal, g as REFACTOR, ht as refactorRules, i as skills, it as prototypeFixingPrompt, jt as unwindFailedMessage, k as planner, kt as uncoveredMessage, l as gate, m as PROTOTYPE, mt as refactorNotePrompt, n as summary, nt as normaliseCriteriaScript, ot as prototypeTestingPrompt, p as HANDOFF, pt as refactorFixPrompt, q as designTriagePrompt, r as access, rt as oversizedMessage, s as shipped, st as prototypeUnguidedMessage, t as steering, tt as naivePassPrompt, v as REQUIREMENTS, w as collecting, wt as seamSteerMessage, x as answerReviewQuestions, z as clearCriteriaQuestion } from "../chunks/reserved-CUkk9XgL.mjs";
4
+ import { a as refusePendingHandOff, c as joinReports, o as tail } from "../chunks/tail-CX6Pdsql.mjs";
5
+ import { i as reUnwind, n as humanReview, r as isCode$1, t as buildTail } from "../chunks/review-BaCXlIT2.mjs";
6
6
  import { posix } from "node:path";
7
7
  //#region src/workflows/shared/fences.ts
8
8
  const markFenced = (text) => {
@@ -561,7 +561,9 @@ const reviewedRefactor = async (shape, options = {}) => {
561
561
  feedback,
562
562
  fixAccess: strayAccess(p.strays),
563
563
  lensBase: entry,
564
- rules: refactorRules(p.strays, p.pattern)
564
+ rules: refactorRules(p.strays, p.pattern),
565
+ walkthrough: true,
566
+ lap: "refactor"
565
567
  });
566
568
  if (outcome.verdict === "signoff") return true;
567
569
  await reUnwind(outcome);
@@ -576,7 +578,7 @@ const reviewedRefactor = async (shape, options = {}) => {
576
578
  const COVERED_MIN_P = .7;
577
579
  /** Covering turns before the process rests at `uncovered`; a human edit there refills them. */
578
580
  const COVERING_RETRIES = 2;
579
- const frozen = () => requireCriteriaFrozen(REQUIREMENTS);
581
+ const frozen$1 = () => requireCriteriaFrozen(REQUIREMENTS);
580
582
  const green = (guard) => healthy(() => agentWithSkills("fix.suite.fixing", implementFixPrompt(), {
581
583
  label: "Fixing the check",
582
584
  file: FEEDBACK,
@@ -584,7 +586,7 @@ const green = (guard) => healthy(() => agentWithSkills("fix.suite.fixing", imple
584
586
  system: builderSystem()
585
587
  }), {
586
588
  guard: () => {
587
- frozen();
589
+ frozen$1();
588
590
  guard?.();
589
591
  },
590
592
  guardRests: false
@@ -637,7 +639,7 @@ const requireCoverage = async (base = start(), slice) => {
637
639
  system: builderSystem(),
638
640
  allowEmpty: true
639
641
  });
640
- frozen();
642
+ frozen$1();
641
643
  } else {
642
644
  await human("uncovered", {
643
645
  message: uncoveredMessage(uncovered),
@@ -646,13 +648,136 @@ const requireCoverage = async (base = start(), slice) => {
646
648
  acceptClean: true
647
649
  });
648
650
  if (!changes().some((c) => isCode$1(c.path))) return record(covered, uncovered);
649
- frozen();
651
+ frozen$1();
650
652
  retries = 0;
651
653
  }
652
654
  await green();
653
655
  }
654
656
  };
655
657
  //#endregion
658
+ //#region src/workflows/feature/prototype.ts
659
+ /** The `prototypeRounds` setting. Anything but a non-negative integer fails rather than guess a cap. */
660
+ const prototypeRounds = () => {
661
+ const value = vars.prototypeRounds ?? defaults.prototypeRounds;
662
+ if (!/^(0|[1-9]\d*)$/.test(value)) refuse(`gtd: prototypeRounds must be a non-negative integer, got ${JSON.stringify(value)}`);
663
+ return Number(value);
664
+ };
665
+ const CHUNK = /^(AC-\d+|Problem\b.*?)\s*:\s*(pass|fail|blocked)$/i;
666
+ const UNGUIDED = /^no testing instructions$/i;
667
+ /** The `## ` chunks of a prototype report; a heading in neither shape is prose, not an item. */
668
+ const reportOf = (text) => {
669
+ let unguided = false;
670
+ const items = [];
671
+ const malformed = [];
672
+ for (const { line, fenced } of markFenced(text)) {
673
+ const heading = fenced ? null : HEADING.exec(line);
674
+ if (heading?.[1] !== "##") continue;
675
+ if (UNGUIDED.test(heading[2])) unguided = true;
676
+ const chunk = CHUNK.exec(heading[2]);
677
+ if (chunk === null && /^(ac-\d|problem)/i.test(heading[2])) malformed.push(heading[2]);
678
+ if (chunk !== null) items.push({
679
+ subject: /^ac-/i.test(chunk[1]) ? chunk[1].toUpperCase() : chunk[1],
680
+ verdict: chunk[2].toLowerCase()
681
+ });
682
+ }
683
+ return {
684
+ unguided,
685
+ items,
686
+ malformed
687
+ };
688
+ };
689
+ /**
690
+ * The report the testing turn left, refused unless it is the only change and
691
+ * names every criterion once, with its open items: another slice's criteria
692
+ * are not this slice's to fix.
693
+ */
694
+ const requireReport = (slice, retest) => {
695
+ const others = changes().map((c) => c.path).filter((path) => path !== PROTOTYPE);
696
+ 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")}`);
697
+ const text = read(PROTOTYPE);
698
+ if (text === void 0) return refuse(`gtd land: prototype-report: write ${PROTOTYPE}`);
699
+ const report = reportOf(text);
700
+ 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")}`);
701
+ if (report.unguided && report.items.length === 0 && !retest) return {
702
+ unguided: true,
703
+ open: []
704
+ };
705
+ const requirements = read(".gtd/REQUIREMENTS.md") ?? "";
706
+ const ids = criterionIds(requirements, slice);
707
+ const elsewhere = criterionIds(requirements).filter((id) => !ids.includes(id));
708
+ const count = (id) => report.items.filter((i) => i.subject === id).length;
709
+ const wrong = ids.filter((id) => count(id) !== 1);
710
+ if (wrong.length > 0) refuse(`gtd land: prototype-report: ${PROTOTYPE} needs exactly one chunk headed \`## <ID>: pass|fail|blocked\` for ${wrong.join(", ")}`);
711
+ return {
712
+ unguided: false,
713
+ open: report.items.filter((i) => i.verdict !== "pass" && !elsewhere.includes(i.subject))
714
+ };
715
+ };
716
+ const frozen = () => requireCriteriaFrozen(REQUIREMENTS);
717
+ const testing = async (slice, retest) => {
718
+ await agentWithSkills("testing", prototypeTestingPrompt(sliceIds(slice), retest), {
719
+ label: retest ? "Re-testing the slice by hand" : "Testing the slice by hand",
720
+ file: PROTOTYPE,
721
+ model: coder(),
722
+ system: testerSystem(),
723
+ allowEmpty: true
724
+ });
725
+ return requireReport(slice, retest);
726
+ };
727
+ const fixing = async (slice, guard) => {
728
+ await scope("fix", () => agentWithSkills("fixing", prototypeFixingPrompt(sliceIds(slice)), {
729
+ label: "Fixing what the hands-on test found",
730
+ file: PROTOTYPE,
731
+ model: coder(),
732
+ system: builderSystem(),
733
+ allowEmpty: true
734
+ }));
735
+ 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`);
736
+ frozen();
737
+ guard?.();
738
+ };
739
+ /** A person's rest; resolves `true` when they changed something to test again, `false` on a clean overrule. */
740
+ const askPerson = async (name, message, label) => {
741
+ await human(name, {
742
+ message,
743
+ label,
744
+ file: PROTOTYPE,
745
+ acceptClean: true
746
+ });
747
+ if (!changes().some((c) => c.path !== ".gtd/PROTOTYPE.md")) return false;
748
+ frozen();
749
+ return true;
750
+ };
751
+ /**
752
+ * Use the slice by hand: a testing turn reports on every criterion, a fix turn
753
+ * takes each failure, and the slice is tested again — until the report is
754
+ * clean, or `prototypeRounds` fixes are spent and a person decides. `guard`
755
+ * holds every fix turn, as it holds the implementation's.
756
+ */
757
+ const prototypeTest = async (slice, guard) => {
758
+ const cap = prototypeRounds();
759
+ if (cap === 0) return;
760
+ await scope("prototype", async () => {
761
+ let fixes = 0;
762
+ let retest = false;
763
+ for (;;) {
764
+ const { unguided, open } = await testing(slice, retest);
765
+ retest = !unguided;
766
+ if (unguided) {
767
+ if (!await askPerson("unguided", prototypeUnguidedMessage(), "No way to test by hand")) return;
768
+ } else if (open.length === 0) return;
769
+ else if (fixes < cap && open.some((i) => i.verdict === "fail")) {
770
+ fixes++;
771
+ await fixing(slice, guard);
772
+ } else {
773
+ if (!await askPerson("open", prototypeOpenMessage(open, fixes), "Hands-on test still failing")) return;
774
+ fixes = 0;
775
+ }
776
+ await green(guard);
777
+ }
778
+ });
779
+ };
780
+ //#endregion
656
781
  //#region src/workflows/feature/seam.ts
657
782
  /** Naive-pass retries after a `reject` or a red suite; the next one rests at `seam.steer`. */
658
783
  const NAIVE_RETRIES = 2;
@@ -859,8 +984,10 @@ const implementing = (options) => scope("implement", async () => {
859
984
  ...acceptance,
860
985
  ids: settled(acceptance.ids, options.slice)
861
986
  }, since);
862
- await green(built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0);
987
+ const keepsTests = built && acceptance !== void 0 ? () => requireAcceptanceKept(acceptance) : void 0;
988
+ await green(keepsTests);
863
989
  await requireCoverage(options.base, options.slice);
990
+ await prototypeTest(options.slice, keepsTests);
864
991
  const ids = criterionIds(read(".gtd/REQUIREMENTS.md") ?? "");
865
992
  await closing(moreToBuild(options.more));
866
993
  return ids;
@@ -869,11 +996,16 @@ const moreToBuild = (more) => typeof more === "function" ? more() : more === tru
869
996
  /** The last slice's closing, for a last slice a steer left with no criterion to build. */
870
997
  const closeOut = () => scope("implement", () => closing(false));
871
998
  /** A slice with `more` after it keeps the criteria and the slicing for the next one. */
872
- const closing = (more) => run("closing", removeScript(more ? [REFACTOR_NOTE, HANDOFF] : [
999
+ const closing = (more) => run("closing", removeScript(more ? [
1000
+ REFACTOR_NOTE,
1001
+ HANDOFF,
1002
+ PROTOTYPE
1003
+ ] : [
873
1004
  REQUIREMENTS,
874
1005
  REFACTOR_NOTE,
875
1006
  SLICES,
876
- HANDOFF
1007
+ HANDOFF,
1008
+ PROTOTYPE
877
1009
  ]), { label: "Closing out the implementation" });
878
1010
  /** Resolves `true` when the human steered (edited), `false` on a clean overrule. */
879
1011
  const steer = async (retry) => {
@@ -1238,6 +1370,7 @@ const ordinaryStart = async () => {
1238
1370
  refusePendingHandOff();
1239
1371
  flagModeOn();
1240
1372
  sliceCap();
1373
+ prototypeRounds();
1241
1374
  const stated = statedCriteria(changes(), head());
1242
1375
  await unwind();
1243
1376
  if (changes(".gtd/FEEDBACK.md").some((c) => c.status !== "deleted")) await human("unwind-failed", {
@@ -1,7 +1,7 @@
1
1
  import { read, refuse, removeScript, run, scope, start } from "../flows.mjs";
2
- import { _ as reviewRisks, g as reviewNotes } from "../chunks/steering-BXwjmocB.mjs";
3
- import { Dt as envDefaults, Et as defaults, K as finisherSystem, M as agentWithSkills, S as coder, c as baseline, i as skills, n as summary, q as fixReviewNotesPrompt, r as access, s as shipped, t as steering, v as REVIEW } from "../chunks/reserved-CYZ1bSBy.mjs";
4
- import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-C1Oi2YEy.mjs";
2
+ import { _ as reviewRisks, g as reviewNotes } from "../chunks/steering-DtUVUz9l.mjs";
3
+ import { C as coder, It as defaults, J as finisherSystem, Lt as envDefaults, N as agentWithSkills, Y as fixReviewNotesPrompt, c as baseline, i as skills, n as summary, r as access, s as shipped, t as steering, y as REVIEW } from "../chunks/reserved-CUkk9XgL.mjs";
4
+ import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-CX6Pdsql.mjs";
5
5
  //#region src/workflows/fix/fix.ts
6
6
  /**
7
7
  * Fix every note a human left in a handed-off `.gtd/REVIEW.md` and every
@@ -1,7 +1,7 @@
1
1
  import { read, refuse, removeScript, run, start } from "../flows.mjs";
2
- import { Dt as envDefaults, Et as defaults, K as finisherSystem, M as agentWithSkills, S as coder, _ as REQUIREMENTS, dt as reviewFeedbackFixPrompt, ft as reviewGateBlockedMessage, i as skills, l as gate, n as summary, r as access, s as shipped, t as steering } from "../chunks/reserved-CYZ1bSBy.mjs";
3
- import { a as refusePendingHandOff } from "../chunks/tail-C1Oi2YEy.mjs";
4
- import { i as reUnwind, t as buildTail } from "../chunks/review-BewyJflS.mjs";
2
+ import { C as coder, It as defaults, J as finisherSystem, Lt as envDefaults, N as agentWithSkills, i as skills, l as gate, n as summary, r as access, s as shipped, t as steering, v as REQUIREMENTS, vt as reviewFeedbackFixPrompt, yt as reviewGateBlockedMessage } from "../chunks/reserved-CUkk9XgL.mjs";
3
+ import { a as refusePendingHandOff } from "../chunks/tail-CX6Pdsql.mjs";
4
+ import { i as reUnwind, t as buildTail } from "../chunks/review-BaCXlIT2.mjs";
5
5
  //#region src/workflows/review/review.ts
6
6
  const fixFeedback = async () => {
7
7
  await agentWithSkills("feedback.fixing", reviewFeedbackFixPrompt(), {
@@ -1,5 +1,5 @@
1
1
  import { agent, changesSince, glob, head, mark, read, refuse, removeScript, run, scope, vars } from "../flows.mjs";
2
- import { Bt as flipProblems, Dt as envDefaults, Et as defaults, Ht as requireFlag, Rt as flagMentions, S as coder, f as FEEDBACK, i as skills, l as gate, n as summary, o as flagShipped, r as access, t as steering } from "../chunks/reserved-CYZ1bSBy.mjs";
2
+ import { C as coder, It as defaults, Jt as flagMentions, Lt as envDefaults, Qt as requireFlag, Xt as flipProblems, f as FEEDBACK, i as skills, l as gate, n as summary, o as flagShipped, r as access, t as steering } from "../chunks/reserved-CUkk9XgL.mjs";
3
3
  //#region src/workflows/rollout/rollout.ts
4
4
  const flippingPrompt = (name, registry) => `Roll out the release flag "${name}": in ${registry}, set \`defaultOn: true\` on its entry. Change nothing else — no other entry, no other field, no other file, no comments.`;
5
5
  const suiteBlockedMessage = () => "The suite is red after the rollout. Fix it by hand — a rollout may only touch the flag's registry entry — then land.";