@pmelab/gtd 27.9.0 → 28.1.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 (43) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +53 -27
  3. package/claude/hooks/doors.ts +12 -0
  4. package/claude/hooks/register.tsx +2 -4
  5. package/claude/hooks/ship.ts +115 -20
  6. package/dist/chunks/{reserved-UdSuHDUJ.mjs → reserved-DeBS4Ymh.mjs} +178 -32
  7. package/dist/chunks/{tail-fj60rQ3y.mjs → tail-B2SER2Ih.mjs} +1 -1
  8. package/dist/gtd.bundle.mjs +209 -72
  9. package/dist/testkit.mjs +209 -72
  10. package/dist/workflows/cleanup.mjs +3 -4
  11. package/dist/workflows/feature.mjs +2 -2
  12. package/dist/workflows/fix.mjs +2 -2
  13. package/dist/workflows/review.mjs +2 -2
  14. package/dist/workflows/rollout.mjs +19 -4
  15. package/dist/workflows.mjs +196 -132
  16. package/package.json +1 -1
  17. package/skills/authoring/SKILL.md +4 -3
  18. package/src/cli/Cli.test.ts +39 -0
  19. package/src/cli/Cli.ts +41 -9
  20. package/src/cli/Program.test.ts +31 -0
  21. package/src/cli/Program.ts +14 -7
  22. package/src/edge/Edge.test.ts +259 -3
  23. package/src/edge/Edge.ts +175 -58
  24. package/src/flows/runtime.ts +20 -1
  25. package/src/install/ConfigScaffold.ts +4 -1
  26. package/src/install/Install.test.ts +10 -0
  27. package/src/install/Install.ts +5 -2
  28. package/src/workflow/load.test.ts +82 -5
  29. package/src/workflow/load.ts +40 -16
  30. package/src/workflows/cleanup/cleanup.feature +21 -0
  31. package/src/workflows/cleanup/cleanup.ts +1 -1
  32. package/src/workflows/index.ts +2 -1
  33. package/src/workflows/rollout/rollout.feature +282 -2
  34. package/src/workflows/rollout/rollout.test.ts +96 -2
  35. package/src/workflows/rollout/rollout.ts +41 -3
  36. package/src/workflows/shared/options.test.ts +25 -2
  37. package/src/workflows/shared/options.ts +7 -4
  38. package/src/workflows/shared/registry.test.ts +41 -1
  39. package/src/workflows/shared/registry.ts +26 -1
  40. package/src/workflows/shared/text.test.ts +377 -2
  41. package/src/workflows/shared/text.ts +65 -7
  42. package/src/workflows/shared/vars.ts +2 -0
  43. package/dist/chunks/registry-CP3YuV9j.mjs +0 -101
@@ -21520,8 +21520,11 @@ hands off to \`gtd-build\`, which halts at the blocked gate and prints it,
21520
21520
  rather than starting the review; \`gtd-door fix\` on a GREEN suite is a no-op
21521
21521
  straight back to \`idle\` — the exec'd \`gtd-build\` exits immediately on it.
21522
21522
  \`gtd doors\` lists the doors a repository offers (those of the workflows its
21523
- config exports — the bundled \`fix\` and \`review [base]\` — plus any it
21524
- declares). Refuses with
21523
+ config exports — the bundled \`fix\` and \`review [base]\`, plus the opt-in
21524
+ \`rollout <flag>\` (turn a release flag default-on) and \`cleanup <flag>\`
21525
+ (remove it), which a config adds by exporting \`rollout\` from
21526
+ \`@pmelab/gtd/workflows/rollout\` and \`cleanup\` from
21527
+ \`@pmelab/gtd/workflows/cleanup\` — plus any it declares). Refuses with
21525
21528
  \`usage: gtd-door <name> [args...]\` on stderr and exit \`2\` when the name is
21526
21529
  missing — nothing was even attempted. Default path: \`~/.local/bin/gtd-door\`.
21527
21530
 
@@ -21631,7 +21634,10 @@ const renderBriefing = () => HEADER() + BEAT_PROTOCOL + JSON_FIELD_REFERENCE + D
21631
21634
  const CONFIG_SCAFFOLD = `// The workflows gtd runs here: gtd's shipped feature, review and fix.
21632
21635
  // Every exported function is startable with \`gtd --workflow <name>\`; the
21633
21636
  // default export is what a change in the tree starts. Each workflow brings
21634
- // its own doors: \`gtd door review [base]\` and \`gtd door fix\`.
21637
+ // its own doors: \`gtd door review [base]\` and \`gtd door fix\`. Export \`rollout\`
21638
+ // from \`@pmelab/gtd/workflows/rollout\` and \`cleanup\` from
21639
+ // \`@pmelab/gtd/workflows/cleanup\` to add \`gtd door rollout <flag>\` and
21640
+ // \`gtd door cleanup <flag>\`.
21635
21641
  export {
21636
21642
  feature as default,
21637
21643
  feature,
@@ -38993,10 +38999,10 @@ const doorsFrom = (exports) => {
38993
38999
  };
38994
39000
  /**
38995
39001
  * Each exported workflow's own doors, onto the name it is exported under. The
38996
- * `doors` export adds to them and settles a name two workflows both declare.
39002
+ * `doors` export adds to them and declares a name several workflows clash on.
38997
39003
  */
38998
39004
  const derivedDoors = (catalogue, declared) => {
38999
- const doors = /* @__PURE__ */ new Map();
39005
+ const builders = /* @__PURE__ */ new Map();
39000
39006
  for (const [workflow, loaded] of catalogue) for (const [name, spec] of Object.entries(loaded.doors ?? {})) {
39001
39007
  const door = {
39002
39008
  ...spec,
@@ -39004,12 +39010,22 @@ const derivedDoors = (catalogue, declared) => {
39004
39010
  };
39005
39011
  const problem = doorProblem(name, door);
39006
39012
  if (problem !== void 0) throw new Error(`workflow "${workflow}" builds door "${name}", which ${problem}`);
39007
- const other = doors.get(name);
39008
- if (other !== void 0 && !declared.has(name)) throw new Error(`door "${name}" is built by workflows "${other.workflow}" and "${workflow}" — declare it in the "doors" export to choose one (a declared door replaces the derived one, so restate its args and vars)`);
39009
- doors.set(name, door);
39013
+ builders.set(name, [...builders.get(name) ?? [], door]);
39014
+ }
39015
+ const doors = /* @__PURE__ */ new Map();
39016
+ const clashes = /* @__PURE__ */ new Map();
39017
+ for (const [name, built] of builders) {
39018
+ const owner = ownerOf(name, built);
39019
+ if (owner !== void 0) doors.set(name, owner);
39020
+ else if (!declared.has(name)) clashes.set(name, built.map((door) => door.workflow));
39010
39021
  }
39011
- return new Map([...doors, ...declared]);
39022
+ return {
39023
+ doors: new Map([...doors, ...declared]),
39024
+ clashes
39025
+ };
39012
39026
  };
39027
+ /** The export named after the door owns it, else the sole builder; a clash of several builds no door. */
39028
+ const ownerOf = (name, built) => built.find((door) => door.workflow === name) ?? (built.length === 1 ? built[0] : void 0);
39013
39029
  /** Sort the doors and check each names a startable workflow. */
39014
39030
  const checkDoors = (declared, catalogue) => {
39015
39031
  const doors = new Map([...declared].sort(([a], [b]) => a.localeCompare(b)));
@@ -39040,16 +39056,28 @@ const loadWorkflows = (module, shipped) => {
39040
39056
  return workflow === void 0 ? [] : [[name, workflow]];
39041
39057
  }));
39042
39058
  const defaultWorkflow = built.get(value);
39059
+ const derived = rejections.length > 0 ? {
39060
+ doors: /* @__PURE__ */ new Map(),
39061
+ clashes: /* @__PURE__ */ new Map()
39062
+ } : derivedDoors(catalogue, doorsFrom(exports));
39043
39063
  return {
39044
39064
  file,
39045
39065
  catalogue,
39046
- doors: rejections.length > 0 ? /* @__PURE__ */ new Map() : checkDoors(derivedDoors(catalogue, doorsFrom(exports)), catalogue),
39066
+ doors: checkDoors(derived.doors, catalogue),
39047
39067
  default: defaultWorkflow && {
39048
39068
  ...defaultWorkflow,
39049
39069
  name: defaultName
39050
39070
  },
39051
39071
  config: compileConfig(exports["config"], module.filepath),
39052
- diagnostics: nameRejections(rejections)
39072
+ diagnostics: [...nameRejections(rejections), ...[...derived.clashes].map(([name, builders]) => {
39073
+ const names = [...builders].sort().map((b) => JSON.stringify(b));
39074
+ return {
39075
+ severity: "warning",
39076
+ message: `door "${name}" is built by workflows ${names.slice(0, -1).join(", ")} and ${names.at(-1)} and none is exported under that name — declare it in the "doors" export`,
39077
+ path: ["doors", name],
39078
+ origin: file.origin
39079
+ };
39080
+ })]
39053
39081
  };
39054
39082
  };
39055
39083
  const evaluateFromDisk = (module, shipped) => {
@@ -39594,9 +39622,10 @@ const traceEntryOf = (hash, message) => ({
39594
39622
  hash,
39595
39623
  actor: message.parsed?.actor ?? ""
39596
39624
  });
39597
- const costEntriesOf = (message) => message.cost.map((c) => ({
39625
+ const costEntriesOf = (hash, message) => message.cost.map((c) => ({
39598
39626
  cost: c.cost,
39599
- model: c.model ?? "unspecified"
39627
+ model: c.model ?? "unspecified",
39628
+ hash
39600
39629
  }));
39601
39630
  const hashAt = (history, index) => history[index]?.hash;
39602
39631
  const historyFormatFault = (history, location) => formatFault(history.slice(location.baseIndex + 1).concat(history[location.baseIndex]?.message.startsWith("gtd") ? [history[location.baseIndex]] : []).map((c) => ({
@@ -39636,7 +39665,7 @@ const buildRun = (history, location, closingHash) => {
39636
39665
  startParentHash,
39637
39666
  diffBase: first?.reviewBase ?? startParentHash,
39638
39667
  trace: processCommits.map((c, i) => traceEntryOf(c.hash, parsed[i])),
39639
- costEntries: parsed.flatMap(costEntriesOf),
39668
+ costEntries: parsed.flatMap((m, i) => costEntriesOf(processCommits[i].hash, m)),
39640
39669
  judgeVerdicts: parsed.flatMap((m) => m.judge),
39641
39670
  marks: parsed.flatMap((m) => m.marks),
39642
39671
  approvals: processCommits.flatMap((c, i) => approvalEntryOf(c.hash, parsed[i])),
@@ -39993,6 +40022,10 @@ const costByModel = (entries) => {
39993
40022
  cost
39994
40023
  })).sort((a, b) => b.cost - a.cost || a.model.localeCompare(b.model));
39995
40024
  };
40025
+ const costTotals = (entries) => ({
40026
+ total: entries.reduce((sum, entry) => sum + entry.cost, 0),
40027
+ byModel: costByModel(entries)
40028
+ });
39996
40029
  const setupFor = (config, run, workspace, host, startOverrides = {}) => gen(function* () {
39997
40030
  const resolved = yield* workflowOf(config, run);
39998
40031
  const def = resolved.def;
@@ -40096,57 +40129,69 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
40096
40129
  memory: memory.key,
40097
40130
  memoryResumed: memory.resumed,
40098
40131
  hints: hintsOf(stepDef),
40099
- cost: {
40100
- total: run.costEntries.reduce((sum, entry) => sum + entry.cost, 0),
40101
- byModel: costByModel(run.costEntries)
40102
- },
40132
+ cost: costTotals(run.costEntries),
40103
40133
  setup
40104
40134
  };
40105
40135
  });
40106
40136
  const currentRest = restAt(void 0);
40137
+ /** Every slice refusal reads `<command>: refused — <reason>`; replay's own `gtd: ` lead is dropped. */
40138
+ const slicesDivergence = (command, run, outcome) => {
40139
+ const reason = outcome.kind === "rest" && run.closingHash !== void 0 ? `${run.closingHash.slice(0, 7)} closes a process that does not end under today's config` : replayError(outcome)?.message.replace(/^gtd: /, "") ?? "replay failed";
40140
+ return /* @__PURE__ */ new Error(`${command}: refused — ${reason}`);
40141
+ };
40107
40142
  /**
40108
- * The slices of the process HEAD closes or sits inside, current episode only,
40109
- * stack order. Reads like `historyTrail`, never `restAt`: it narrates and
40110
- * writes nothing, and returns a version warning for a closed run.
40143
+ * The replay `gtd slices` and `gtd summary --slice` share: the slice ranges of
40144
+ * `run`, plus whether HEAD closes it. Reads like `historyTrail`, never
40145
+ * `restAt`: it narrates and writes nothing.
40111
40146
  */
40112
- const processSlices = gen(function* () {
40113
- const git = yield* GitService;
40114
- const config = yield* (yield* ConfigService).load;
40147
+ const replayedSlices = (config, run, how) => gen(function* () {
40115
40148
  const workspace = yield* Workspace;
40116
40149
  const host = yield* Host;
40117
- const run = yield* runAtHead(git, config.workflow);
40118
- if (run.trace.length === 0) return yield* fail$7(/* @__PURE__ */ new Error("gtd slices: refused — HEAD neither closes nor sits inside a process"));
40150
+ if (run.trace.length === 0) return yield* fail$7(/* @__PURE__ */ new Error(`${how.command}: refused — HEAD neither closes nor sits inside a process`));
40119
40151
  const closing = run.closingHash;
40120
40152
  let outcome;
40121
40153
  let versionWarning;
40122
40154
  if (closing !== void 0) {
40123
40155
  const replayed = yield* replayFinishedProcess(config, run, closing, {
40124
- command: "gtd slices",
40125
- configReason: "HEAD's process replays under today's config; revert or stash the edit to list its slices",
40126
- notEnded: (o) => slicesDivergence(run, o)
40156
+ command: how.command,
40157
+ configReason: how.configReason,
40158
+ notEnded: (o) => slicesDivergence(how.command, run, o)
40127
40159
  });
40128
40160
  outcome = replayed.outcome;
40129
40161
  versionWarning = replayed.versionWarning;
40130
40162
  } else {
40131
40163
  const { setup } = yield* setupFor(config, run, workspace, host);
40132
40164
  outcome = yield* promise(() => replayFor(setup));
40133
- if (outcome.kind !== "rest") return yield* fail$7(slicesDivergence(run, outcome));
40165
+ if (outcome.kind !== "rest") return yield* fail$7(slicesDivergence(how.command, run, outcome));
40134
40166
  }
40135
40167
  return {
40136
- slices: sliceRangesOf(outcome.slices, run).map(({ name, base, tip, open }) => ({
40168
+ ranges: sliceRangesOf(outcome.slices, run),
40169
+ closed: closing !== void 0,
40170
+ ...versionWarning !== void 0 ? { versionWarning } : {}
40171
+ };
40172
+ });
40173
+ /**
40174
+ * The slices of the process HEAD closes or sits inside, current episode only,
40175
+ * stack order. Returns a version warning for a closed run.
40176
+ */
40177
+ const processSlices = gen(function* () {
40178
+ const git = yield* GitService;
40179
+ const config = yield* (yield* ConfigService).load;
40180
+ const run = yield* runAtHead(git, config.workflow);
40181
+ const { ranges, closed, versionWarning } = yield* replayedSlices(config, run, {
40182
+ command: "gtd slices",
40183
+ configReason: "HEAD's process replays under today's config; revert or stash the edit to list its slices"
40184
+ });
40185
+ return {
40186
+ slices: ranges.map(({ name, base, tip, open }) => ({
40137
40187
  name,
40138
40188
  base,
40139
40189
  tip,
40140
- closed: closing !== void 0 || !open
40190
+ closed: closed || !open
40141
40191
  })),
40142
40192
  ...versionWarning !== void 0 ? { versionWarning } : {}
40143
40193
  };
40144
40194
  });
40145
- /** Every `gtd slices` refusal reads `gtd slices: refused — <reason>`; replay's own `gtd: ` lead is dropped. */
40146
- const slicesDivergence = (run, outcome) => {
40147
- const reason = outcome.kind === "rest" && run.closingHash !== void 0 ? `${run.closingHash.slice(0, 7)} closes a process that does not end under today's config` : replayError(outcome)?.message.replace(/^gtd: /, "") ?? "replay failed";
40148
- return /* @__PURE__ */ new Error(`gtd slices: refused — ${reason}`);
40149
- };
40150
40195
  /**
40151
40196
  * Whether `gtd --workflow <name>` can start a process here: the workflow must
40152
40197
  * reach a step from the tree the opening commit would capture. That step is
@@ -40353,27 +40398,85 @@ const snapshotFromRest = (rest, verdicts) => gen(function* () {
40353
40398
  landing
40354
40399
  };
40355
40400
  });
40356
- /** `gtd summary`'s prompt for `run`, or `undefined` when the workflow has none or there is nothing to summarize. */
40357
- const summaryFor = (run) => gen(function* () {
40401
+ const narrowToSlice = (run, slice) => {
40402
+ if (slice === void 0) return {
40403
+ trace: run.trace,
40404
+ costEntries: run.costEntries
40405
+ };
40406
+ const from = slice.base === run.startParentHash ? -1 : run.trace.findIndex((e) => e.hash === slice.base);
40407
+ const to = run.trace.findIndex((e) => e.hash === slice.tip);
40408
+ const trace = run.trace.slice(from + 1, to + 1);
40409
+ const hashes = new Set(trace.map((e) => e.hash));
40410
+ return {
40411
+ trace,
40412
+ costEntries: run.costEntries.filter((c) => hashes.has(c.hash))
40413
+ };
40414
+ };
40415
+ const humanCommitsOf = (trace, entryCommit) => trace.filter((entry) => entry.actor === "human" && entry.hash !== entryCommit).map((entry) => ({
40416
+ hash: entry.hash,
40417
+ state: entry.state
40418
+ }));
40419
+ const summaryInput = (run, slice, settings) => {
40420
+ const entryCommit = run.trace[0].hash;
40421
+ const { trace, costEntries } = narrowToSlice(run, slice);
40422
+ const { total, byModel } = costTotals(costEntries);
40423
+ return {
40424
+ entryCommit,
40425
+ processBase: slice?.base ?? run.startParentHash,
40426
+ processTip: slice?.tip ?? run.trace[run.trace.length - 1].hash,
40427
+ humanCommits: humanCommitsOf(trace, entryCommit),
40428
+ processCost: total,
40429
+ processCostByModel: byModel,
40430
+ marks: run.marks,
40431
+ ...settings,
40432
+ ...slice !== void 0 ? { slice } : {}
40433
+ };
40434
+ };
40435
+ const locateSlice = (config, run, sliceName) => gen(function* () {
40436
+ if (sliceName === void 0) return {};
40437
+ const { ranges, versionWarning } = yield* replayedSlices(config, run, {
40438
+ command: "gtd summary",
40439
+ configReason: "HEAD's process replays under today's config; revert or stash the edit to summarize its slices"
40440
+ });
40441
+ const warn = versionWarning !== void 0 ? { versionWarning } : {};
40442
+ const at = ranges.findIndex((r) => r.name === sliceName);
40443
+ if (at < 0) return {
40444
+ unknownSlice: `gtd summary: refused — no slice named "${sliceName}"; the slices, in stack order: ${ranges.map((r) => r.name).join(", ")}`,
40445
+ ...warn
40446
+ };
40447
+ const { base, tip } = ranges[at];
40448
+ return {
40449
+ slice: {
40450
+ name: sliceName,
40451
+ base,
40452
+ tip,
40453
+ index: at + 1,
40454
+ count: ranges.length
40455
+ },
40456
+ ...warn
40457
+ };
40458
+ });
40459
+ /**
40460
+ * A plain call never replays; under a slice, `entryCommit` and `marks` stay
40461
+ * whole-process.
40462
+ */
40463
+ const summaryFor = (run, sliceName) => gen(function* () {
40358
40464
  const config = yield* (yield* ConfigService).load;
40359
40465
  const host = yield* Host;
40360
40466
  const resolved = yield* workflowOf(config, run);
40361
40467
  const summary = resolved.def.summary;
40362
- if (summary === void 0 || run.trace.length === 0) return void 0;
40363
- const entryCommit = run.trace[0].hash;
40364
- return summary({
40365
- entryCommit,
40366
- processBase: run.startParentHash,
40367
- processTip: run.trace[run.trace.length - 1].hash,
40368
- humanCommits: run.trace.filter((entry) => entry.actor === "human" && entry.hash !== entryCommit).map((entry) => ({
40369
- hash: entry.hash,
40370
- state: entry.state
40371
- })),
40372
- processCost: run.costEntries.reduce((sum, entry) => sum + entry.cost, 0),
40373
- processCostByModel: costByModel(run.costEntries),
40374
- marks: run.marks,
40375
- ...settingsFor(resolved, run.pinnedVars, host.env, {}, {})
40376
- });
40468
+ if (summary === void 0) return { prompt: void 0 };
40469
+ if (sliceName === void 0 && run.trace.length === 0) return { prompt: void 0 };
40470
+ const located = yield* locateSlice(config, run, sliceName);
40471
+ if ("unknownSlice" in located) return {
40472
+ prompt: void 0,
40473
+ ...located
40474
+ };
40475
+ const { slice, versionWarning } = located;
40476
+ return {
40477
+ prompt: summary(summaryInput(run, slice, settingsFor(resolved, run.pinnedVars, host.env, {}, {}))),
40478
+ ...versionWarning !== void 0 ? { versionWarning } : {}
40479
+ };
40377
40480
  });
40378
40481
  /** Repository-relative paths of the config files today's config and the process's own record name; one outside the repository cannot be read at a ref, so it is left out. */
40379
40482
  const inRepoConfigPaths = (root, files, recorded) => {
@@ -40438,13 +40541,14 @@ const replayFinishedProcess = (config, run, hash, how) => gen(function* () {
40438
40541
  const git = yield* GitService;
40439
40542
  const workspace = yield* Workspace;
40440
40543
  const host = yield* Host;
40544
+ const recorded = parseCommitMessage(run.episode.commits.find((c) => c.hash === hash)?.message ?? "").version;
40545
+ const versionWarning = versionWarningFor(recorded);
40546
+ const withWarning = (err) => versionWarning === void 0 ? err : /* @__PURE__ */ new Error(`${err.message}\n${versionWarning}`);
40441
40547
  const differing = yield* differingConfigPaths(git, workspace, host.root, config, run, hash);
40442
- if (differing.length > 0) return yield* fail$7(configRefusal(how.command, hash, differing, how.configReason));
40548
+ if (differing.length > 0) return yield* fail$7(withWarning(configRefusal(how.command, hash, differing, how.configReason)));
40443
40549
  const { setup } = yield* setupFor(config, run, workspace, host);
40444
40550
  const outcome = yield* promise(() => replayFor(setup));
40445
- if (outcome.kind !== "ended") return yield* fail$7(how.notEnded(outcome));
40446
- const recorded = parseCommitMessage(run.episode.commits.find((c) => c.hash === hash)?.message ?? "").version;
40447
- const versionWarning = versionWarningFor(recorded);
40551
+ if (outcome.kind !== "ended") return yield* fail$7(withWarning(how.notEnded(outcome)));
40448
40552
  return {
40449
40553
  outcome,
40450
40554
  recorded,
@@ -41134,12 +41238,16 @@ const planLanding = (opts = {}, current) => gen(function* () {
41134
41238
  * closes or sits inside its own closing message. Writes nothing — no git, no
41135
41239
  * state transition, no file. Refuses (throws, mapped to the runtime-error
41136
41240
  * exit code) when the workflow declares no `summary` prompt, or the resolved
41137
- * run has no commits to name.
41241
+ * run has no commits to name. The edge never narrates: the CLI prints the
41242
+ * version warning, before the unknown-slice refusal; a replay refusal carries
41243
+ * it in its message.
41138
41244
  */
41139
- const runSummaryCommand = (out) => gen(function* () {
41140
- const rendered = yield* summaryFor(yield* summaryRun);
41141
- if (rendered === void 0) return yield* fail$7(/* @__PURE__ */ new Error("gtd summary: refused — either this workflow declares no \"summary:\" template, or there is no finished/in-flight process to summarize at HEAD"));
41142
- out.write(rendered.endsWith("\n") ? rendered : rendered + "\n");
41245
+ const runSummaryCommand = (out, sliceName) => gen(function* () {
41246
+ const { prompt, versionWarning, unknownSlice } = yield* summaryFor(yield* summaryRun, sliceName);
41247
+ if (versionWarning !== void 0) yield* (yield* Narrator).warn(versionWarning);
41248
+ if (unknownSlice !== void 0) return yield* fail$7(new Error(unknownSlice));
41249
+ if (prompt === void 0) return yield* fail$7(/* @__PURE__ */ new Error("gtd summary: refused — either this workflow declares no \"summary:\" template, or there is no finished/in-flight process to summarize at HEAD"));
41250
+ out.write(prompt.endsWith("\n") ? prompt : prompt + "\n");
41143
41251
  });
41144
41252
  /** Prints the review anchor hash bare, so an external tool can be pointed at the range under review. Nothing is written. Refuses when the run has no commits, where the hash would name a range with no review. */
41145
41253
  const runBaseCommand = (out) => gen(function* () {
@@ -41680,7 +41788,7 @@ const dispatchVoidCommand = (command, json, out) => {
41680
41788
  case "uncheck": return runUncheckCommand(command.file);
41681
41789
  case "install": return runInstallCommand(out);
41682
41790
  case "loop": return runLoopCommand();
41683
- case "summary": return runSummaryCommand(out);
41791
+ case "summary": return runSummaryCommand(out, command.slice);
41684
41792
  case "base": return runBaseCommand(out);
41685
41793
  case "judge": return runJudgeCommand(json, out);
41686
41794
  case "judgeAnswer": return runJudgeAnswerCommand(json, out);
@@ -41850,6 +41958,21 @@ const FLAGS = [
41850
41958
  "GTD_JUDGE_MODEL, then judge.json, then sonnet"
41851
41959
  ]
41852
41960
  },
41961
+ {
41962
+ name: "--slice",
41963
+ arity: 1,
41964
+ repeatable: false,
41965
+ scope: (kind) => kind === "summary",
41966
+ decode: ([raw]) => right(raw),
41967
+ scopeError: "gtd: --slice is only valid for `gtd summary`",
41968
+ valueHint: "<name>",
41969
+ help: [
41970
+ "(gtd summary only) the closing-message prompt for one",
41971
+ "slice, named as `gtd slices` lists it: its own range, human",
41972
+ "commits and cost. An unknown name is refused (exit 1) with",
41973
+ "every valid slice listed"
41974
+ ]
41975
+ },
41853
41976
  {
41854
41977
  name: "--workflow",
41855
41978
  arity: 1,
@@ -42154,10 +42277,15 @@ const COMMAND_ROWS = [
42154
42277
  "no state transition, no file, no session identity — the",
42155
42278
  "driver pipes the output to a cold agent and does what it",
42156
42279
  "wants with the result (a squash, an amend, a PR body).",
42157
- "Refuses (exit 1) when the workflow declares no summary:",
42158
- "template, or when the resolved run has no commits to name",
42159
- "— runnable any time before the next thing lands on the",
42160
- "branch"
42280
+ "--slice <name> prints it for one slice instead: its own",
42281
+ "range, human commits and cost (see `gtd slices`); it also",
42282
+ "refuses an unknown slice name, and a HEAD-closing process",
42283
+ "whose config files differ from the working tree or whose",
42284
+ "replay diverged; an open last slice is provisional. Refuses",
42285
+ "(exit 1) when the workflow declares no summary: template,",
42286
+ "or when the resolved run has no commits to name — plain",
42287
+ "gtd summary is runnable any time before the next thing",
42288
+ "lands on the branch"
42161
42289
  ]
42162
42290
  },
42163
42291
  {
@@ -42186,10 +42314,10 @@ const COMMAND_ROWS = [
42186
42314
  "positional args to that workflow's process settings — the",
42187
42315
  "same start `gtd --workflow` makes, printed the same way.",
42188
42316
  "Each workflow the config exports brings its own doors (gtd's",
42189
- "`fix` brings `fix`, `review` brings `review [base]`), plus",
42190
- "the config's `doors` export. An unknown door, or too few or",
42191
- "too many args, is a usage error (exit 2) naming the doors or",
42192
- "the door's synopsis"
42317
+ "`fix`, `review [base]`, `rollout <flag>` and",
42318
+ "`cleanup <flag>`), plus the config's `doors` export. An",
42319
+ "unknown door, or too few or too many args, is a usage error",
42320
+ "(exit 2) naming the doors or the door's synopsis"
42193
42321
  ]
42194
42322
  },
42195
42323
  {
@@ -42614,6 +42742,15 @@ const parseArgv = (argv) => {
42614
42742
  verbose
42615
42743
  };
42616
42744
  }
42745
+ if (kind === "summary") return {
42746
+ kind: "command",
42747
+ command: {
42748
+ kind: "summary",
42749
+ ...bag["--slice"] !== void 0 ? { slice: bag["--slice"] } : {}
42750
+ },
42751
+ json,
42752
+ verbose
42753
+ };
42617
42754
  if (kind === "historyShow") return {
42618
42755
  kind: "command",
42619
42756
  command: {