@pmelab/gtd 57.0.0 → 58.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +9 -8
  3. package/dist/chunks/{reserved-X2RkNHin.mjs → reserved-BzAFXuz_.mjs} +26 -19
  4. package/dist/chunks/{review-v7sh5GF4.mjs → review-CVqRS0mc.mjs} +2 -2
  5. package/dist/chunks/{tail-S04KEewi.mjs → tail-JQzB21Q3.mjs} +1 -1
  6. package/dist/workflows/cleanup.mjs +2 -2
  7. package/dist/workflows/feature.mjs +52 -13
  8. package/dist/workflows/fix.mjs +2 -2
  9. package/dist/workflows/review.mjs +3 -3
  10. package/dist/workflows/rollout.mjs +1 -1
  11. package/dist/workflows.mjs +74 -28
  12. package/package.json +1 -1
  13. package/src/workflows/feature/acceptance-criteria.feature +32 -0
  14. package/src/workflows/feature/criteria-coverage.feature +0 -1
  15. package/src/workflows/feature/criteria.ts +10 -4
  16. package/src/workflows/feature/fast-path.feature +4 -0
  17. package/src/workflows/feature/feature.test.ts +3 -39
  18. package/src/workflows/feature/feature.ts +3 -4
  19. package/src/workflows/feature/lens-models.feature +0 -1
  20. package/src/workflows/feature/lens-review.feature +0 -1
  21. package/src/workflows/feature/naive-acceptance.feature +4 -0
  22. package/src/workflows/feature/naive-test.feature +11 -6
  23. package/src/workflows/feature/one-step-land.feature +4 -0
  24. package/src/workflows/feature/planning.ts +2 -0
  25. package/src/workflows/feature/refactor-absorbs.feature +4 -0
  26. package/src/workflows/feature/refactor-first.feature +4 -0
  27. package/src/workflows/feature/refactor-lens-models.feature +4 -0
  28. package/src/workflows/feature/refactor-rebuild.feature +4 -0
  29. package/src/workflows/feature/refactor-review.feature +20 -0
  30. package/src/workflows/feature/release-flag.feature +167 -0
  31. package/src/workflows/feature/release-flag.test.ts +49 -0
  32. package/src/workflows/feature/release-flag.ts +46 -0
  33. package/src/workflows/feature/review-gate.feature +0 -1
  34. package/src/workflows/feature/reviewer-walkthrough.feature +0 -1
  35. package/src/workflows/feature/risk-tier.feature +32 -0
  36. package/src/workflows/feature/seam-rejection.feature +8 -0
  37. package/src/workflows/feature/seam.feature +8 -0
  38. package/src/workflows/feature/seam.test.ts +20 -7
  39. package/src/workflows/feature/seam.ts +7 -7
  40. package/src/workflows/feature/standards.feature +0 -1
  41. package/src/workflows/shared/options.test.ts +3 -10
  42. package/src/workflows/shared/options.ts +0 -9
  43. package/src/workflows/shared/text.test.ts +36 -28
  44. package/src/workflows/shared/text.ts +37 -20
  45. package/src/workflows/shared/vars.ts +0 -1
  46. package/src/workflows/feature/flag-mode.feature +0 -88
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gtd",
3
- "version": "57.0.0",
3
+ "version": "58.0.0",
4
4
  "description": "Drive the gtd loop inside Claude Code: agent turns run as subagents, gtd decides every beat",
5
5
  "author": {
6
6
  "name": "Philipp Melab"
package/README.md CHANGED
@@ -692,14 +692,15 @@ build, one quality lap, one review — however many criteria it has. Sizing the
692
692
  work is yours: split a change too big for one process into several before the
693
693
  sketch.
694
694
 
695
- **Flag mode (`flagMode`, on by default).** On, the naive pass's judge runs as
696
- above and a rebuilt implementation is asked for a new release flag (none at a
697
- `low` risk tier); a pass the judge lets land is not asked for one. A project
698
- that is not publicly released turns it off explicitly —
699
- `feature({ flagMode: false })` in `gtd.config.ts`, it is never detected: then no
700
- flag is asked for, no seam judge or refactor runs, and the naive pass lands as
701
- the implementation, straight through the health loop and the quality lap. See
702
- [`flagMode`](docs/configuration.md#the-bundled-workflows-settings).
695
+ **Release flags (only when asked for).** A change ships unflagged unless its
696
+ requirements ask for a flag: a `## Release Flag` section in
697
+ `.gtd/REQUIREMENTS.md` reading `new` (a new release flag) or `existing: <NAME>`
698
+ (behind a flag that already exists). Triage writes it only when the sketch asks
699
+ for a flag; you can add, change or delete it at the design gate. With it, the
700
+ naive pass's judge runs as above and the build adds the new flag, or uses the
701
+ named one; without it, no seam judge or refactor runs and the naive pass lands
702
+ as the implementation, straight through the health loop and the quality lap. See
703
+ [Release flags](docs/configuration.md#release-flags).
703
704
 
704
705
  **Risk tier (flag `GTD_FLAG_RISK_TIER=1`, off by default).** Triage proposes a
705
706
  risk tier, `low` or `high`, and the production signals to watch after merge.
@@ -237,7 +237,6 @@ const defaults = {
237
237
  standardMinP: "0.8",
238
238
  refactorFlagPattern: "\\bGTD_FLAG_\\w+",
239
239
  seamAllow: [...testFileGlobs, ".gtd/**"].join(", "),
240
- flagMode: "true",
241
240
  naiveTest: "false",
242
241
  flag_risk_tier: "0",
243
242
  qualityReviews: "correctness, owasp-security, ponytail-review, test-audit, conventions, spec-challenge, criteria-coverage",
@@ -671,6 +670,18 @@ in its docs or agent instructions (\`AGENTS.md\`, \`CLAUDE.md\`). When
671
670
  none is declared, fall back to directories and packages. Landing is
672
671
  refused while the section is missing.
673
672
  `;
673
+ const releaseFlagBlock = `
674
+ ## Release flag
675
+
676
+ Only when the sketch asks outright for the change to ship behind a
677
+ feature or release flag, \`.gtd/REQUIREMENTS.md\` carries a
678
+ \`## Release Flag\` section whose first line is \`new\` (a new
679
+ flag) or \`existing: <NAME>\` (behind a flag that already exists,
680
+ named as the sketch names it). Never add one on your own judgement:
681
+ without the section the change ships unflagged. A later lap keeps the
682
+ section exactly as the last settled file had it, unless the human
683
+ changed it. Landing is refused while its first line is anything else.
684
+ `;
674
685
  const riskTierBlock = `
675
686
  ## Risk tier and production signals
676
687
 
@@ -710,7 +721,7 @@ ${footnoteFoldIn}${codeThreadReplies()}
710
721
  or both
711
722
  - Whichever lap this is, leave \`.gtd/REQUIREMENTS.md\`
712
723
  uncommitted and finish once it reflects this lap's own work
713
- ${acceptanceCriteriaBlock}${impactBlock}
724
+ ${acceptanceCriteriaBlock}${impactBlock}${releaseFlagBlock}
714
725
  Every lap re-explores and leaves \`## Impact\` present. On a return
715
726
  lap or review loop-back the human may have edited \`## Impact\` (or
716
727
  footnoted it): read that edit from history (\`git log -1 -p --
@@ -780,11 +791,12 @@ ${stateFileRules}
780
791
  - The only state file this turn touches is \`.gtd/REQUIREMENTS.md\`
781
792
  — no other files for notes or output
782
793
  - \`.gtd/REQUIREMENTS.md\` already holds the settled acceptance
783
- criteria. Add a \`## Impact\` section and change nothing else — a
784
- turn that touches anything outside it is refused
794
+ criteria. Add a \`## Impact\` section — and a \`## Release Flag\`
795
+ section when the sketch asks for one — and change nothing else: a
796
+ turn that touches anything outside them is refused
785
797
  - No human reviews this before the build: put any surprise in the
786
798
  Impact prose, never as a question
787
- ${impactBlock}
799
+ ${impactBlock}${releaseFlagBlock}
788
800
  - Leave \`.gtd/REQUIREMENTS.md\` uncommitted and finish once it holds \`## Impact\`
789
801
  `;
790
802
  const clearCriteriaQuestion = {
@@ -843,6 +855,7 @@ What each change does next (then run \`gtd land\`):
843
855
  - **Accept as-is** — change nothing and re-run; the plan stands as written. Refused while a thread is open.
844
856
  - **Revise answers** — tick exactly one option per open question (replace \`_your answer_\` for your own) to send it back for the agent to fold your answers in, or delete a question to skip it.
845
857
  - **Discuss** — start or continue a thread (\`- H:\`); the agent replies and this gate stops again.
858
+ - **Ask for a release flag** — add \`## Release Flag\` with \`new\` or \`existing: <NAME>\` as its first line, or delete the section to ship unflagged.
846
859
  ${riskTier ? RISK_TIER_GATE_LINE : ""}`;
847
860
  const builderSystem = () => `${builderPersona}
848
861
 
@@ -1522,21 +1535,22 @@ const afterRefactorLine = (rebuild) => `- A reviewed refactor has just landed an
1522
1535
  though never a release flag. Build only what is still missing, held to every
1523
1536
  rule below; if nothing is, ${carries(rebuild) ? "carry over the acceptance tests below and change nothing else" : "change nothing and finish your turn"}
1524
1537
  `;
1525
- const flagLine = (flagMode, tier, amend) => !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
1526
- repository's flag conventions`;
1527
- const implementBuildingPrompt = (reference, tier, amend, afterRefactor, flagMode = true, rebuild = {}) => `${stateFileRules}
1538
+ const flagLine = (flag, amend) => flag.kind === "none" ? "- Add no release flag — the requirements ask for none" : flag.kind === "existing" ? `- Put the new behaviour behind the existing flag \`${flag.name}\`, as the
1539
+ requirements ask — add no new flag` : 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, as the requirements ask,
1540
+ following the repository's flag conventions`;
1541
+ const implementBuildingPrompt = (reference, amend, afterRefactor, flag = { kind: "none" }, rebuild = {}) => `${stateFileRules}
1528
1542
  - Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
1529
1543
  ${impactHints}
1530
1544
  ${criteriaBinding(true)}
1531
1545
  ${afterRefactor === true ? afterRefactorLine(rebuild) : ""}${amend === true ? `- The change already exists in the tree from an earlier lap; amend it to meet
1532
1546
  the re-derived requirements instead of rewriting it
1533
- ` : flagMode ? `- Implement them properly, fitting the codebase's seams and conventions
1547
+ ` : flag.kind !== "none" ? `- Implement them properly, fitting the codebase's seams and conventions
1534
1548
  ` : `- Implement them the quickest way that works — the naive version. Do not
1535
1549
  restructure, abstract or refactor around the change
1536
1550
  `}${reference === void 0 || amend === true ? "" : `- Reference only: \`git show ${reference}\` shows an earlier naive attempt.
1537
1551
  Read it for ideas; never apply, cherry-pick or check it out — write
1538
1552
  the change fresh${carries(rebuild) ? ", its acceptance tests excepted (below)" : ""}
1539
- `}${rebuildLines(rebuild)}${flagLine(flagMode, tier, amend)}
1553
+ `}${rebuildLines(rebuild)}${flagLine(flag, amend)}
1540
1554
  - Write the tests first, then the code that makes them pass
1541
1555
  ${behaviourTests}
1542
1556
  - Leave \`.gtd/REQUIREMENTS.md\` in place and everything uncommitted, then
@@ -1555,7 +1569,7 @@ An edit together with an approval counts as steer. Only
1555
1569
  \`.gtd/REQUIREMENTS.md\` is meant to change here: any other edit lands on the
1556
1570
  branch unjudged.
1557
1571
  `;
1558
- /** `judged`: whether an overruled pass still meets the seam judge (`flagMode` on). */
1572
+ /** `judged`: whether an overruled pass still meets the seam judge (a release flag asked for). */
1559
1573
  const seamSteerMessage = (retry, judged = true) => isFault(retry) ? `The naive pass failed three times and the retries are spent. The last
1560
1574
  attempt's tests (\`git show ${retry.hash}\`) fall short:
1561
1575
  ${retry.fault}
@@ -1879,19 +1893,12 @@ const ShippedOptions = Struct({
1879
1893
  testCommand: setting,
1880
1894
  plannerModel: setting,
1881
1895
  coderModel: setting,
1882
- flagMode: optionalWith(Boolean$, { exact: true }),
1883
1896
  naiveTest: optionalWith(Boolean$, { exact: true })
1884
1897
  });
1885
1898
  const over = (base, options) => ({
1886
1899
  ...base,
1887
1900
  ...Object.fromEntries(Object.entries(options).filter(([name]) => Object.hasOwn(base, name)).map(([name, value]) => [name, String(value)]))
1888
1901
  });
1889
- /** The `flagMode` setting, pinned at process start. Anything but true/false fails rather than guess a side. */
1890
- const flagModeOn = () => {
1891
- const value = vars.flagMode ?? defaults.flagMode;
1892
- if (value !== "true" && value !== "false") refuse(`gtd: flagMode must be "true" or "false", got ${JSON.stringify(value)}`);
1893
- return value === "true";
1894
- };
1895
1902
  /** The `naiveTest` setting, pinned at process start. Anything but true/false fails rather than guess. */
1896
1903
  const naiveTestOn = () => {
1897
1904
  const value = vars.naiveTest ?? defaults.naiveTest;
@@ -1980,4 +1987,4 @@ const steering = {
1980
1987
  [REVIEW]: "review"
1981
1988
  };
1982
1989
  //#endregion
1983
- export { naiveTryMessage as $, reviewQuality as A, oneLine as At, designImpactPrompt as B, collecting as C, envDefaults as Ct, fixRisks as D, conflictFor as Dt, fixQuality as E, conflictBody as Et, builderSystem as F, flagReferences as Ft, finisherSystem as G, designTriagePrompt as H, clearCriteriaMessage as I, flipProblems as It, idleMessage as J, fixReviewNotesPrompt as K, clearCriteriaQuestion as L, registryEntry as Lt, agentWithSkills as M, renderStandards as Mt, buildReviewCollectingPrompt as N, standardKey as Nt, planner as O, conflictTitle as Ot, buildReviewTriageMessage as P, flagMentions as Pt, naivePassPrompt as Q, criterionLine as R, requireFlag as Rt, coder as S, defaults as St, fixNits as T, STANDARDS as Tt, findingGateFile as U, designSystem as V, findingGateMessage as W, implementFixPrompt as X, implementBuildingPrompt as Y, naiveApplyFailure as Z, REFACTOR_NOTE as _, startGateBlockedMessage as _t, flagModeOn as a, refactorDroppedRefusal as at, answerReviewQuestions as b, withGuidance as bt, shipped as c, refactorRules as ct, guarded as d, reviewFeedbackFixPrompt as dt, nitFixBlock as et, healthy as f, reviewGateBlockedMessage as ft, REFACTOR as g, seamSteerMessage as gt, QUALITY as h, seamByHandMessage as ht, skills as i, refactorBuildingPrompt as it, reviewing as j, parseStandards as jt, reviewMissing as k, isConflictTitle as kt, baseline as l, reviewEditNotesCapture as lt, NAIVE_TEST as m, reviewerSystem as mt, summary as n, qualityFindingsFile as nt, flagShipped as o, refactorFixPrompt as ot, FEEDBACK as p, reviewNotesCapture as pt, handOffAwaitReviewMessage as q, access as r, qualityTriageMessage as rt, naiveTestOn as s, refactorNotePrompt as st, steering as t, normaliseCriteriaScript as tt, gate as u, reviewEditsCapture as ut, REQUIREMENTS as v, unwindFailedMessage as vt, fixCheck as w, testFileGlobs as wt, awaitReview as x, withSummary as xt, REVIEW as y, unwindFailure as yt, designGateAnswerMessage as z };
1990
+ export { nitFixBlock as $, reviewing as A, parseStandards as At, designSystem as B, fixCheck as C, testFileGlobs as Ct, planner as D, conflictTitle as Dt, fixRisks as E, conflictFor as Et, clearCriteriaMessage as F, flipProblems as Ft, fixReviewNotesPrompt as G, findingGateFile as H, clearCriteriaQuestion as I, registryEntry as It, implementBuildingPrompt as J, handOffAwaitReviewMessage as K, criterionLine as L, requireFlag as Lt, buildReviewCollectingPrompt as M, standardKey as Mt, buildReviewTriageMessage as N, flagMentions as Nt, reviewMissing as O, isConflictTitle as Ot, builderSystem as P, flagReferences as Pt, naiveTryMessage as Q, designGateAnswerMessage as R, collecting as S, envDefaults as St, fixQuality as T, conflictBody as Tt, findingGateMessage as U, designTriagePrompt as V, finisherSystem as W, naiveApplyFailure as X, implementFixPrompt as Y, naivePassPrompt as Z, REQUIREMENTS as _, unwindFailedMessage as _t, flagShipped as a, refactorFixPrompt as at, awaitReview as b, withSummary as bt, baseline as c, reviewEditNotesCapture as ct, healthy as d, reviewGateBlockedMessage as dt, normaliseCriteriaScript as et, FEEDBACK as f, reviewNotesCapture as ft, REFACTOR_NOTE as g, startGateBlockedMessage as gt, REFACTOR as h, seamSteerMessage as ht, skills as i, refactorDroppedRefusal as it, agentWithSkills as j, renderStandards as jt, reviewQuality as k, oneLine as kt, gate as l, reviewEditsCapture as lt, QUALITY as m, seamByHandMessage as mt, summary as n, qualityTriageMessage as nt, naiveTestOn as o, refactorNotePrompt as ot, NAIVE_TEST as p, reviewerSystem as pt, idleMessage as q, access as r, refactorBuildingPrompt as rt, shipped as s, refactorRules as st, steering as t, qualityFindingsFile as tt, guarded as u, reviewFeedbackFixPrompt as ut, REVIEW as v, unwindFailure as vt, fixNits as w, STANDARDS as wt, coder as x, defaults as xt, answerReviewQuestions as y, withGuidance as yt, designImpactPrompt as z };
@@ -1,8 +1,8 @@
1
1
  import { answered, changesSince, head, judge, numeric, read, refuse, requireRevert, restoreScript, run, vars, writeScript } from "../flows.mjs";
2
2
  import { S as reviewSections, _ as REVIEW_SECTIONS, v as reviewChunks, x as reviewRisks } from "./steering-CCNvngSH.mjs";
3
3
  import { review } from "../workflow-kit.mjs";
4
- import { At as oneLine, C as collecting, D as fixRisks, Dt as conflictFor, Et as conflictBody, Mt as renderStandards, Nt as standardKey, Ot as conflictTitle, P as buildReviewTriageMessage, T as fixNits, Tt as STANDARDS, b as answerReviewQuestions, d as guarded, et as nitFixBlock, f as healthy, j as reviewing, jt as parseStandards, k as reviewMissing, kt as isConflictTitle, lt as reviewEditNotesCapture, pt as reviewNotesCapture, ut as reviewEditsCapture, w as fixCheck, x as awaitReview, xt as withSummary, y as REVIEW } from "./reserved-X2RkNHin.mjs";
5
- import { i as isOptionalObject, n as closeReview, o as tail, s as NO_FINDINGS } from "./tail-S04KEewi.mjs";
4
+ import { $ as nitFixBlock, A as reviewing, At as parseStandards, C as fixCheck, Dt as conflictTitle, E as fixRisks, Et as conflictFor, Mt as standardKey, N as buildReviewTriageMessage, O as reviewMissing, Ot as isConflictTitle, S as collecting, Tt as conflictBody, b as awaitReview, bt as withSummary, ct as reviewEditNotesCapture, d as healthy, ft as reviewNotesCapture, jt as renderStandards, kt as oneLine, lt as reviewEditsCapture, u as guarded, v as REVIEW, w as fixNits, wt as STANDARDS, y as answerReviewQuestions } from "./reserved-BzAFXuz_.mjs";
5
+ import { i as isOptionalObject, n as closeReview, o as tail, s as NO_FINDINGS } from "./tail-JQzB21Q3.mjs";
6
6
  //#region src/workflows/shared/learn.ts
7
7
  const SUMMARY_MAX = 200;
8
8
  const summary = (body) => {
@@ -1,7 +1,7 @@
1
1
  import { answered, head, human, judge, mark, numeric, read, refuse, removeScript, run, scope, sectionBodies, showScript, vars, writeScript } from "../flows.mjs";
2
2
  import { F as formatFinding, p as findingEntries, r as steeringFormatFor, x as reviewRisks } from "./steering-CCNvngSH.mjs";
3
3
  import { lensEntriesOf, lensOf, qualityLap, reviewToHandOff } from "../workflow-kit.mjs";
4
- import { A as reviewQuality, D as fixRisks, E as fixQuality, U as findingGateFile, W as findingGateMessage, bt as withGuidance, d as guarded, f as healthy, h as QUALITY, j as reviewing, k as reviewMissing, nt as qualityFindingsFile, q as handOffAwaitReviewMessage, rt as qualityTriageMessage, w as fixCheck, x as awaitReview, xt as withSummary, y as REVIEW } from "./reserved-X2RkNHin.mjs";
4
+ import { A as reviewing, C as fixCheck, E as fixRisks, H as findingGateFile, K as handOffAwaitReviewMessage, O as reviewMissing, T as fixQuality, U as findingGateMessage, b as awaitReview, bt as withSummary, d as healthy, k as reviewQuality, m as QUALITY, nt as qualityTriageMessage, tt as qualityFindingsFile, u as guarded, v as REVIEW, yt as withGuidance } from "./reserved-BzAFXuz_.mjs";
5
5
  //#region src/workflows/shared/lenses.ts
6
6
  const NO_FINDINGS = {
7
7
  fixed: [],
@@ -1,6 +1,6 @@
1
1
  import { agent, changes, glob, read, refuse, scope, start, vars } from "../flows.mjs";
2
- import { Ct as envDefaults, Ft as flagReferences, Lt as registryEntry, Rt as requireFlag, S as coder, St as defaults, i as skills, n as summary, o as flagShipped, r as access, t as steering } from "../chunks/reserved-X2RkNHin.mjs";
3
- import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-S04KEewi.mjs";
2
+ import { It as registryEntry, Lt as requireFlag, Pt as flagReferences, St as envDefaults, a as flagShipped, i as skills, n as summary, r as access, t as steering, x as coder, xt as defaults } from "../chunks/reserved-BzAFXuz_.mjs";
3
+ import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-JQzB21Q3.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, restoreScript, revertScript, run, scope, start, vars, wrote } from "../flows.mjs";
2
2
  import { lensEntriesOf } from "../workflow-kit.mjs";
3
- import { $ as naiveTryMessage, B as designImpactPrompt, C as collecting, Ct as envDefaults, F as builderSystem, H as designTriagePrompt, I as clearCriteriaMessage, J as idleMessage, L as clearCriteriaQuestion, M as agentWithSkills, N as buildReviewCollectingPrompt, O as planner, Q as naivePassPrompt, R as criterionLine, S as coder, St as defaults, T as fixNits, V as designSystem, X as implementFixPrompt, Y as implementBuildingPrompt, Z as naiveApplyFailure, _ as REFACTOR_NOTE, _t as startGateBlockedMessage, a as flagModeOn, at as refactorDroppedRefusal, b as answerReviewQuestions, c as shipped, ct as refactorRules, f as healthy, g as REFACTOR, gt as seamSteerMessage, ht as seamByHandMessage, i as skills, it as refactorBuildingPrompt, m as NAIVE_TEST, mt as reviewerSystem, n as summary, ot as refactorFixPrompt, p as FEEDBACK, r as access, s as naiveTestOn, st as refactorNotePrompt, t as steering, tt as normaliseCriteriaScript, u as gate, v as REQUIREMENTS, vt as unwindFailedMessage, wt as testFileGlobs, yt as unwindFailure, z as designGateAnswerMessage } from "../chunks/reserved-X2RkNHin.mjs";
4
- import { a as refusePendingHandOff } from "../chunks/tail-S04KEewi.mjs";
5
- import { n as isCode$1, r as reUnwind, t as buildTail } from "../chunks/review-v7sh5GF4.mjs";
3
+ import { B as designSystem, Ct as testFileGlobs, D as planner, F as clearCriteriaMessage, I as clearCriteriaQuestion, J as implementBuildingPrompt, L as criterionLine, M as buildReviewCollectingPrompt, P as builderSystem, Q as naiveTryMessage, R as designGateAnswerMessage, S as collecting, St as envDefaults, V as designTriagePrompt, X as naiveApplyFailure, Y as implementFixPrompt, Z as naivePassPrompt, _ as REQUIREMENTS, _t as unwindFailedMessage, at as refactorFixPrompt, d as healthy, et as normaliseCriteriaScript, f as FEEDBACK, g as REFACTOR_NOTE, gt as startGateBlockedMessage, h as REFACTOR, ht as seamSteerMessage, i as skills, it as refactorDroppedRefusal, j as agentWithSkills, l as gate, mt as seamByHandMessage, n as summary, o as naiveTestOn, ot as refactorNotePrompt, p as NAIVE_TEST, pt as reviewerSystem, q as idleMessage, r as access, rt as refactorBuildingPrompt, s as shipped, st as refactorRules, t as steering, vt as unwindFailure, w as fixNits, x as coder, xt as defaults, y as answerReviewQuestions, z as designImpactPrompt } from "../chunks/reserved-BzAFXuz_.mjs";
4
+ import { a as refusePendingHandOff } from "../chunks/tail-JQzB21Q3.mjs";
5
+ import { n as isCode$1, r as reUnwind, t as buildTail } from "../chunks/review-CVqRS0mc.mjs";
6
6
  import { posix } from "node:path";
7
7
  //#region src/workflows/shared/fences.ts
8
8
  const markFenced = (text) => {
@@ -23,6 +23,43 @@ const HEADING = /^ {0,3}(#{1,6})\s+(.*?)\s*#*\s*$/;
23
23
  /** A list item, its checkbox dropped: its text. */
24
24
  const ITEM = /^ ?(?:[-*+]|\d+[.)])\s+(?:\[[ xX]\]\s+)?(.*)$/;
25
25
  //#endregion
26
+ //#region src/workflows/feature/release-flag.ts
27
+ const NAME = /^existing:\s*`?([A-Za-z_][\w.-]*)`?$/i;
28
+ const isFlagHeading = (h) => h !== null && h[1].length === 2 && /^release flag$/i.test(h[2].trim());
29
+ /** The first line under `## Release Flag`, `undefined` without the section. */
30
+ const requestLine = (text) => {
31
+ const lines = markFenced(text);
32
+ const at = lines.findIndex((l) => !l.fenced && isFlagHeading(HEADING.exec(l.line)));
33
+ if (at === -1) return void 0;
34
+ const body = lines.slice(at + 1);
35
+ const end = body.findIndex((l) => !l.fenced && HEADING.test(l.line));
36
+ return (end === -1 ? body : body.slice(0, end)).find((l) => l.line.trim() !== "")?.line.trim();
37
+ };
38
+ /** What `text` asks for, or why it cannot be read. No section asks for no flag. */
39
+ const releaseFlagOf = (text) => {
40
+ const line = requestLine(text);
41
+ if (line === void 0 || /^none$/i.test(line)) return { kind: "none" };
42
+ if (/^new$/i.test(line)) return { kind: "new" };
43
+ const name = NAME.exec(line)?.[1];
44
+ return name === void 0 ? { unreadable: line } : {
45
+ kind: "existing",
46
+ name
47
+ };
48
+ };
49
+ /** Refuse `file` when its `## Release Flag` section is neither `none`, `new` nor `existing: <NAME>`. */
50
+ const requireReleaseFlag = (file) => {
51
+ const request = releaseFlagOf(read(file) ?? "");
52
+ if (!("unreadable" in request)) return;
53
+ refuse(`gtd land: release-flag: ${file}'s "## Release Flag" reads ${JSON.stringify(request.unreadable)} — its first line must be \`new\`, \`existing: <NAME>\` or \`none\`, or leave the section out for no flag`);
54
+ };
55
+ /** What `.gtd/REQUIREMENTS.md` asks for now; a person's unreadable edit is refused. */
56
+ const requestedFlag = () => {
57
+ requireReleaseFlag(REQUIREMENTS);
58
+ return releaseFlagOf(read(".gtd/REQUIREMENTS.md") ?? "");
59
+ };
60
+ /** Whether the change goes behind a release flag: only then does the seam judge rule. */
61
+ const flagged = () => requestedFlag().kind !== "none";
62
+ //#endregion
26
63
  //#region src/workflows/feature/criteria.ts
27
64
  /** Below this the judge's `yes` is not trusted: a wrong skip builds against criteria nobody can check. */
28
65
  const CLEAR_MIN_P = .8;
@@ -114,14 +151,15 @@ const requireCriteria = (file) => {
114
151
  refuse(`gtd land: acceptance-criteria: ${file} has ${criteria.length === 0 ? `no "## Acceptance Criteria" list` : unnamed.length > 0 ? `${unnamed.length} criterion without an ID:\n${unnamed.map((c) => ` - ${c.text}`).join("\n")}` : `duplicate IDs: ${[...new Set(twice)].join(", ")}`} — list every criterion under "## Acceptance Criteria" as "- AC-<n>: <checkable criterion>", each ID unique and never reused`);
115
152
  };
116
153
  const isImpactHeading = (h) => h !== null && h[1].length === 2 && /^impact$/i.test(h[2].trim());
154
+ const isImpactTurnHeading = (h) => h !== null && h[1].length === 2 && /^(impact|release flag)$/i.test(h[2].trim());
117
155
  /** Whether `text` has an unfenced, level-2 `## Impact` heading. */
118
156
  const hasImpact = (text) => markFenced(text).some((l) => !l.fenced && isImpactHeading(HEADING.exec(l.line)));
119
- /** `text` without its `## Impact` section, whitespace collapsed. */
157
+ /** `text` without its `## Impact` and `## Release Flag` sections, whitespace collapsed. */
120
158
  const withoutImpact = (text) => {
121
159
  let inImpact = false;
122
160
  return markFenced(text).filter((l) => {
123
161
  const h = l.fenced ? null : HEADING.exec(l.line);
124
- if (h !== null && h[1].length <= 2) inImpact = isImpactHeading(h);
162
+ if (h !== null && h[1].length <= 2) inImpact = isImpactTurnHeading(h);
125
163
  return !inImpact;
126
164
  }).map((l) => l.line).join(" ").replace(/\s+/g, " ").trim();
127
165
  };
@@ -138,12 +176,12 @@ const requireImpact = (file) => {
138
176
  if (hasImpact(text)) return;
139
177
  refuse(`gtd land: impact: ${file} has no "## Impact" section — explore the codebase and add "## Impact" naming the units, public surfaces and surprises this change touches`);
140
178
  };
141
- /** Refuse a turn that changed `file` beyond its `## Impact` section. */
179
+ /** Refuse a turn that changed `file` beyond its `## Impact` and `## Release Flag` sections. */
142
180
  const requireImpactOnly = (file) => {
143
181
  const change = changes().get(file);
144
182
  if (change === void 0) return;
145
183
  if (withoutImpact(change.before ?? "") === withoutImpact(change.after ?? "")) return;
146
- refuse(`gtd land: impact-only: this turn may only add "## Impact" to ${file} — restore everything else`);
184
+ refuse(`gtd land: impact-only: this turn may only add "## Impact" (and "## Release Flag") to ${file} — restore everything else`);
147
185
  };
148
186
  const idNumber = (id) => Number(id.slice(3));
149
187
  /** Why `listed` renumbers or reuses an earlier ID; `undefined` when it does neither. */
@@ -190,6 +228,7 @@ const impact = async () => {
190
228
  requireCriteriaFrozen(REQUIREMENTS);
191
229
  requireImpact(REQUIREMENTS);
192
230
  requireImpactOnly(REQUIREMENTS);
231
+ requireReleaseFlag(REQUIREMENTS);
193
232
  };
194
233
  /**
195
234
  * The skip path: when the sketch stated clear, checkable criteria, write them
@@ -293,6 +332,7 @@ const design = (base, guard) => scope("design", async () => {
293
332
  requireReplies(REQUIREMENTS);
294
333
  requireCriteria(REQUIREMENTS);
295
334
  requireImpact(REQUIREMENTS);
335
+ requireReleaseFlag(REQUIREMENTS);
296
336
  guard?.();
297
337
  } while (await noteGate(REQUIREMENTS, answerProductQuestions));
298
338
  });
@@ -658,7 +698,7 @@ const applied = async (tip) => {
658
698
  return !wrote(".gtd/FEEDBACK.md") && codeChanges().length > 0;
659
699
  };
660
700
  const build = async (options) => {
661
- await agentWithSkills("building", implementBuildingPrompt(options.reference, options.tier, options.amend, options.afterRefactor, flagModeOn(), options.rebuild), {
701
+ await agentWithSkills("building", implementBuildingPrompt(options.reference, options.amend, options.afterRefactor, requestedFlag(), options.rebuild), {
662
702
  label: "Implementing",
663
703
  model: coder(),
664
704
  system: builderSystem(),
@@ -685,7 +725,7 @@ const implementing = (options) => scope("implement", async () => {
685
725
  /** Resolves `true` when the human steered (edited), `false` on a clean overrule. */
686
726
  const steer = async (retry) => {
687
727
  await human("steer", {
688
- message: seamSteerMessage(retry, flagModeOn()),
728
+ message: seamSteerMessage(retry, flagged()),
689
729
  label: "Steer or overrule the naive pass",
690
730
  file: REQUIREMENTS,
691
731
  acceptClean: true
@@ -764,7 +804,7 @@ const tried = async (tip) => {
764
804
  const before = head();
765
805
  await run("applying", applyScript(tip.hash, FEEDBACK, naiveApplyFailure(tip.hash), [".gtd"]), { label: "Applying the naive pass to try it" });
766
806
  await human("try", {
767
- message: naiveTryMessage(flagModeOn()),
807
+ message: naiveTryMessage(flagged()),
768
808
  label: "Try the naive pass",
769
809
  file: NAIVE_TEST,
770
810
  acceptClean: true
@@ -798,7 +838,7 @@ const decide = () => scope("seam", async () => {
798
838
  continue;
799
839
  }
800
840
  }
801
- const outcome = verdict ?? (flagModeOn() ? await ruled(tip, vetting.again) : {
841
+ const outcome = verdict ?? (flagged() ? await ruled(tip, vetting.again) : {
802
842
  reference: tip.hash,
803
843
  tip
804
844
  });
@@ -867,7 +907,7 @@ const unwind = () => {
867
907
  return run("unwind", revertScript(commit, FEEDBACK, unwindFailure(commit)), { label: "Unwinding your input" });
868
908
  };
869
909
  /**
870
- * A naive pass (judged only with `flagMode` on), then implement and the
910
+ * A naive pass (judged only when a release flag is asked for), then implement and the
871
911
  * quality lap, then the review; design must have run since `firstBase`.
872
912
  */
873
913
  const buildFeature = async (firstBase) => {
@@ -959,7 +999,6 @@ const ordinaryStart = async () => {
959
999
  file: ".gtd/TODO.md"
960
1000
  });
961
1001
  refusePendingHandOff();
962
- flagModeOn();
963
1002
  naiveTestOn();
964
1003
  const stated = statedCriteria(changes(), head());
965
1004
  await unwind();
@@ -1,7 +1,7 @@
1
1
  import { read, refuse, removeScript, run, scope, start } from "../flows.mjs";
2
2
  import { b as reviewNotes, x as reviewRisks } from "../chunks/steering-CCNvngSH.mjs";
3
- import { Ct as envDefaults, G as finisherSystem, K as fixReviewNotesPrompt, M as agentWithSkills, S as coder, St as defaults, c as shipped, i as skills, l as baseline, n as summary, r as access, t as steering, y as REVIEW } from "../chunks/reserved-X2RkNHin.mjs";
4
- import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-S04KEewi.mjs";
3
+ import { G as fixReviewNotesPrompt, St as envDefaults, W as finisherSystem, c as baseline, i as skills, j as agentWithSkills, n as summary, r as access, s as shipped, t as steering, v as REVIEW, x as coder, xt as defaults } from "../chunks/reserved-BzAFXuz_.mjs";
4
+ import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-JQzB21Q3.mjs";
5
5
  //#region src/workflows/fix/fix.ts
6
6
  /**
7
7
  * Fix every note a human left in a handed-off `.gtd/REVIEW.md` and every
@@ -1,7 +1,7 @@
1
1
  import { read, refuse, removeScript, run, start } from "../flows.mjs";
2
- import { Ct as envDefaults, G as finisherSystem, M as agentWithSkills, S as coder, St as defaults, c as shipped, dt as reviewFeedbackFixPrompt, ft as reviewGateBlockedMessage, i as skills, n as summary, r as access, t as steering, u as gate, v as REQUIREMENTS } from "../chunks/reserved-X2RkNHin.mjs";
3
- import { a as refusePendingHandOff } from "../chunks/tail-S04KEewi.mjs";
4
- import { r as reUnwind, t as buildTail } from "../chunks/review-v7sh5GF4.mjs";
2
+ import { St as envDefaults, W as finisherSystem, _ as REQUIREMENTS, dt as reviewGateBlockedMessage, i as skills, j as agentWithSkills, l as gate, n as summary, r as access, s as shipped, t as steering, ut as reviewFeedbackFixPrompt, x as coder, xt as defaults } from "../chunks/reserved-BzAFXuz_.mjs";
3
+ import { a as refusePendingHandOff } from "../chunks/tail-JQzB21Q3.mjs";
4
+ import { r as reUnwind, t as buildTail } from "../chunks/review-CVqRS0mc.mjs";
5
5
  //#region src/workflows/review/review.ts
6
6
  const fixFeedback = async () => {
7
7
  await agentWithSkills("feedback.fixing", reviewFeedbackFixPrompt(), {
@@ -1,5 +1,5 @@
1
1
  import { agent, changesSince, glob, head, mark, read, refuse, removeScript, run, scope, vars } from "../flows.mjs";
2
- import { Ct as envDefaults, It as flipProblems, Pt as flagMentions, Rt as requireFlag, S as coder, St as defaults, i as skills, n as summary, o as flagShipped, p as FEEDBACK, r as access, t as steering, u as gate } from "../chunks/reserved-X2RkNHin.mjs";
2
+ import { Ft as flipProblems, Lt as requireFlag, Nt as flagMentions, St as envDefaults, a as flagShipped, f as FEEDBACK, i as skills, l as gate, n as summary, r as access, t as steering, x as coder, xt as defaults } from "../chunks/reserved-BzAFXuz_.mjs";
3
3
  //#region src/workflows/rollout/rollout.ts
4
4
  const flippingPrompt = (name, registry) => `Roll out the release flag "${name}": in ${registry}, set \`defaultOn: true\` on its entry. Change nothing else — no other entry, no other field, no other file, no comments.`;
5
5
  const suiteBlockedMessage = () => "The suite is red after the rollout. Fix it by hand — a rollout may only touch the flag's registry entry — then land.";