@pmelab/gtd 27.8.0 → 28.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 (54) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +59 -30
  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-BIsngMGL.mjs} +168 -27
  7. package/dist/chunks/{tail-fj60rQ3y.mjs → tail-DHEaOHyp.mjs} +1 -1
  8. package/dist/chunks/{wire-C12dahrH.mjs → wire-D3Rc5r22.mjs} +14 -1
  9. package/dist/gtd.bundle.mjs +174 -48
  10. package/dist/testkit.mjs +158 -49
  11. package/dist/wire.mjs +2 -2
  12. package/dist/workflows/cleanup.mjs +3 -4
  13. package/dist/workflows/feature.mjs +2 -2
  14. package/dist/workflows/fix.mjs +2 -2
  15. package/dist/workflows/review.mjs +2 -2
  16. package/dist/workflows/rollout.mjs +19 -4
  17. package/dist/workflows.mjs +186 -127
  18. package/package.json +1 -1
  19. package/skills/authoring/SKILL.md +4 -3
  20. package/src/cli/Cli.test.ts +39 -3
  21. package/src/cli/Cli.ts +27 -6
  22. package/src/cli/Program.test.ts +3 -1
  23. package/src/cli/Program.ts +22 -0
  24. package/src/edge/Edge.test.ts +209 -54
  25. package/src/edge/Edge.ts +121 -42
  26. package/src/edge/index.ts +1 -0
  27. package/src/install/ConfigScaffold.ts +4 -1
  28. package/src/install/Install.test.ts +10 -0
  29. package/src/install/Install.ts +5 -2
  30. package/src/replay/Replay.test.ts +32 -0
  31. package/src/replay/Replay.ts +3 -1
  32. package/src/replay/Trailers.test.ts +11 -0
  33. package/src/replay/Trailers.ts +3 -1
  34. package/src/replay/index.ts +1 -1
  35. package/src/wire/Slices.ts +25 -0
  36. package/src/wire/WireSchema.test.ts +18 -0
  37. package/src/wire/golden/1/slices.json +11 -0
  38. package/src/wire/index.ts +1 -0
  39. package/src/workflow/load.test.ts +82 -5
  40. package/src/workflow/load.ts +40 -16
  41. package/src/workflows/cleanup/cleanup.feature +21 -0
  42. package/src/workflows/cleanup/cleanup.ts +1 -1
  43. package/src/workflows/index.ts +2 -1
  44. package/src/workflows/rollout/rollout.feature +282 -2
  45. package/src/workflows/rollout/rollout.test.ts +96 -2
  46. package/src/workflows/rollout/rollout.ts +41 -3
  47. package/src/workflows/shared/options.test.ts +25 -2
  48. package/src/workflows/shared/options.ts +7 -4
  49. package/src/workflows/shared/registry.test.ts +41 -1
  50. package/src/workflows/shared/registry.ts +26 -1
  51. package/src/workflows/shared/text.test.ts +252 -2
  52. package/src/workflows/shared/text.ts +44 -1
  53. package/src/workflows/shared/vars.ts +2 -0
  54. package/dist/chunks/registry-CP3YuV9j.mjs +0 -101
@@ -1,6 +1,6 @@
1
1
  import { read, refuse, removeScript, run, scope, showScript, vars } from "../flows.mjs";
2
2
  import { fixFindings, lensesOf, qualityLap as qualityLap$1, reviewToHandOff } from "../workflow-kit.mjs";
3
- import { C as fixRisks, D as reviewing, E as reviewQuality, G as handOffAwaitReviewMessage, S as fixQuality, T as reviewMissing, f as guarded, g as QUALITY, p as healthy, u as escalation, v as REVIEW, x as fixCheck, y as awaitReview } from "./reserved-UdSuHDUJ.mjs";
3
+ import { C as reviewMissing, T as reviewing, U as handOffAwaitReviewMessage, _ as awaitReview, b as fixQuality, c as escalation, d as healthy, g as REVIEW, m as QUALITY, u as guarded, w as reviewQuality, x as fixRisks, y as fixCheck } from "./reserved-BIsngMGL.mjs";
4
4
  //#region src/workflows/shared/tail.ts
5
5
  /** The reviewer's own `.gtd/REVIEW.md`, kept beside the human's notes so `fix` can tell them apart. */
6
6
  const REVIEW_ORIG = ".gtd/REVIEW.orig.md";
@@ -475,4 +475,17 @@ const compile = (glob) => {
475
475
  };
476
476
  const globMatches = (path, glob) => compile(glob).test(path);
477
477
  //#endregion
478
- export { EXIT_SIGTERM as C, UNATTRIBUTED_MODEL as D, ACCESS_REFUSAL as E, WIRE_SCHEMA as O, EXIT_SIGINT as S, EXIT_USAGE_ERROR as T, demandOf as _, landFields as a, EXIT_OK as b, renderLandJson as c, renderBeatPlain as d, beatDocument as f, beatKindOf as g, BEAT_KINDS as h, judgeJson as i, renderFormat as k, renderLandPlain as l, statusOf as m, resolveSession as n, landProseText as o, renderBeatJson as p, selectPath as r, noopText as s, globMatches as t, DEMAND_BRIEFING as u, stallDiagnosis as v, EXIT_STRAYS_FOUND as w, EXIT_RUNTIME_ERROR as x, EXIT_CODES as y };
478
+ //#region src/wire/Slices.ts
479
+ /** The ONLY place `gtd slices --json`'s shape is built; copies exactly the four fields, in order. */
480
+ const slicesDocument = (slices) => ({
481
+ schema: 1,
482
+ slices: slices.map(({ name, base, tip, closed }) => ({
483
+ name,
484
+ base,
485
+ tip,
486
+ closed
487
+ }))
488
+ });
489
+ const renderSlicesJson = (document) => JSON.stringify(document) + "\n";
490
+ //#endregion
491
+ export { WIRE_SCHEMA as A, EXIT_RUNTIME_ERROR as C, EXIT_USAGE_ERROR as D, EXIT_STRAYS_FOUND as E, ACCESS_REFUSAL as O, EXIT_OK as S, EXIT_SIGTERM as T, BEAT_KINDS as _, selectPath as a, stallDiagnosis as b, landProseText as c, renderLandPlain as d, DEMAND_BRIEFING as f, statusOf as g, renderBeatJson as h, resolveSession as i, renderFormat as j, UNATTRIBUTED_MODEL as k, noopText as l, beatDocument as m, slicesDocument as n, judgeJson as o, renderBeatPlain as p, globMatches as r, landFields as s, renderSlicesJson as t, renderLandJson as u, beatKindOf as v, EXIT_SIGINT as w, EXIT_CODES as x, demandOf as y };
@@ -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,
@@ -37652,6 +37658,22 @@ var init_Glob = __esmMin((() => {
37652
37658
  globMatches = (path, glob) => compile(glob).test(path);
37653
37659
  }));
37654
37660
  //#endregion
37661
+ //#region src/wire/Slices.ts
37662
+ var slicesDocument, renderSlicesJson;
37663
+ var init_Slices = __esmMin((() => {
37664
+ init_constants();
37665
+ slicesDocument = (slices) => ({
37666
+ schema: 1,
37667
+ slices: slices.map(({ name, base, tip, closed }) => ({
37668
+ name,
37669
+ base,
37670
+ tip,
37671
+ closed
37672
+ }))
37673
+ });
37674
+ renderSlicesJson = (document) => JSON.stringify(document) + "\n";
37675
+ }));
37676
+ //#endregion
37655
37677
  //#region src/wire/index.ts
37656
37678
  var init_wire = __esmMin((() => {
37657
37679
  init_constants();
@@ -37666,6 +37688,7 @@ var init_wire = __esmMin((() => {
37666
37688
  init_Select();
37667
37689
  init_Sessions();
37668
37690
  init_Glob();
37691
+ init_Slices();
37669
37692
  }));
37670
37693
  //#endregion
37671
37694
  //#region src/replay/RefName.ts
@@ -37867,7 +37890,7 @@ const TRAILER_READERS = {
37867
37890
  if (value !== "") into.branch ??= value;
37868
37891
  },
37869
37892
  "Gtd-Version": (value, into) => {
37870
- if (value !== "") into.version ??= value;
37893
+ if (value !== "" && !/[\u0000-\u001f\u007f-\u009f]/.test(value)) into.version ??= value;
37871
37894
  },
37872
37895
  "Gtd-Kept": (value, into) => {
37873
37896
  if (HASH_RE.test(value)) into.kept ??= value;
@@ -38305,7 +38328,8 @@ const replay = async (input) => {
38305
38328
  via,
38306
38329
  trace,
38307
38330
  landed,
38308
- marks
38331
+ marks,
38332
+ slices: slices.map((s) => ({ ...s }))
38309
38333
  };
38310
38334
  const changesSince = (hash) => {
38311
38335
  const tree = treeAt(hash);
@@ -38975,10 +38999,10 @@ const doorsFrom = (exports) => {
38975
38999
  };
38976
39000
  /**
38977
39001
  * Each exported workflow's own doors, onto the name it is exported under. The
38978
- * `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.
38979
39003
  */
38980
39004
  const derivedDoors = (catalogue, declared) => {
38981
- const doors = /* @__PURE__ */ new Map();
39005
+ const builders = /* @__PURE__ */ new Map();
38982
39006
  for (const [workflow, loaded] of catalogue) for (const [name, spec] of Object.entries(loaded.doors ?? {})) {
38983
39007
  const door = {
38984
39008
  ...spec,
@@ -38986,12 +39010,22 @@ const derivedDoors = (catalogue, declared) => {
38986
39010
  };
38987
39011
  const problem = doorProblem(name, door);
38988
39012
  if (problem !== void 0) throw new Error(`workflow "${workflow}" builds door "${name}", which ${problem}`);
38989
- const other = doors.get(name);
38990
- 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)`);
38991
- 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));
38992
39021
  }
38993
- return new Map([...doors, ...declared]);
39022
+ return {
39023
+ doors: new Map([...doors, ...declared]),
39024
+ clashes
39025
+ };
38994
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);
38995
39029
  /** Sort the doors and check each names a startable workflow. */
38996
39030
  const checkDoors = (declared, catalogue) => {
38997
39031
  const doors = new Map([...declared].sort(([a], [b]) => a.localeCompare(b)));
@@ -39022,16 +39056,28 @@ const loadWorkflows = (module, shipped) => {
39022
39056
  return workflow === void 0 ? [] : [[name, workflow]];
39023
39057
  }));
39024
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));
39025
39063
  return {
39026
39064
  file,
39027
39065
  catalogue,
39028
- doors: rejections.length > 0 ? /* @__PURE__ */ new Map() : checkDoors(derivedDoors(catalogue, doorsFrom(exports)), catalogue),
39066
+ doors: checkDoors(derived.doors, catalogue),
39029
39067
  default: defaultWorkflow && {
39030
39068
  ...defaultWorkflow,
39031
39069
  name: defaultName
39032
39070
  },
39033
39071
  config: compileConfig(exports["config"], module.filepath),
39034
- 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
+ })]
39035
39081
  };
39036
39082
  };
39037
39083
  const evaluateFromDisk = (module, shipped) => {
@@ -39746,22 +39792,14 @@ const runAtTip = (git, def, tip) => gen(function* () {
39746
39792
  if (closing.length === 0) closing = yield* git.commitHistory(void 0, tip.hash);
39747
39793
  return yield* runOf([...before, ...closing], { ...location }, tip.hash);
39748
39794
  });
39749
- /** The process HEAD closes or sits inside — `gtd summary`'s run. */
39750
- const summaryRun = gen(function* () {
39751
- const git = yield* GitService;
39752
- const def = (yield* (yield* ConfigService).load).workflow;
39795
+ const runAtHead = (git, def) => gen(function* () {
39753
39796
  return yield* runAtTip(git, def, (yield* git.subjectHistory(1, 0))[0]);
39754
39797
  });
39755
- /** The process whose finishing commit is `tip` — reads from `tip`'s own ancestry, so it works when no branch reaches it. */
39756
- const closedRunAt = (tip) => gen(function* () {
39798
+ /** The process HEAD closes or sits inside — `gtd summary`'s run. */
39799
+ const summaryRun = gen(function* () {
39757
39800
  const git = yield* GitService;
39758
39801
  const def = (yield* (yield* ConfigService).load).workflow;
39759
- const hash = yield* git.resolveRef(tip);
39760
- const subject = yield* git.lastCommitSubject(hash);
39761
- return yield* runAtTip(git, def, {
39762
- hash,
39763
- subject
39764
- });
39802
+ return yield* runAtHead(git, def);
39765
39803
  });
39766
39804
  const commitTree = (workspace, hash) => {
39767
39805
  let entries;
@@ -40090,12 +40128,54 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
40090
40128
  total: run.costEntries.reduce((sum, entry) => sum + entry.cost, 0),
40091
40129
  byModel: costByModel(run.costEntries)
40092
40130
  },
40093
- setup,
40094
- slices: sliceRangesOf(outcome.slices, run)
40131
+ setup
40095
40132
  };
40096
40133
  });
40097
40134
  const currentRest = restAt(void 0);
40098
40135
  /**
40136
+ * The slices of the process HEAD closes or sits inside, current episode only,
40137
+ * stack order. Reads like `historyTrail`, never `restAt`: it narrates and
40138
+ * writes nothing, and returns a version warning for a closed run.
40139
+ */
40140
+ const processSlices = gen(function* () {
40141
+ const git = yield* GitService;
40142
+ const config = yield* (yield* ConfigService).load;
40143
+ const workspace = yield* Workspace;
40144
+ const host = yield* Host;
40145
+ const run = yield* runAtHead(git, config.workflow);
40146
+ if (run.trace.length === 0) return yield* fail$7(/* @__PURE__ */ new Error("gtd slices: refused — HEAD neither closes nor sits inside a process"));
40147
+ const closing = run.closingHash;
40148
+ let outcome;
40149
+ let versionWarning;
40150
+ if (closing !== void 0) {
40151
+ const replayed = yield* replayFinishedProcess(config, run, closing, {
40152
+ command: "gtd slices",
40153
+ configReason: "HEAD's process replays under today's config; revert or stash the edit to list its slices",
40154
+ notEnded: (o) => slicesDivergence(run, o)
40155
+ });
40156
+ outcome = replayed.outcome;
40157
+ versionWarning = replayed.versionWarning;
40158
+ } else {
40159
+ const { setup } = yield* setupFor(config, run, workspace, host);
40160
+ outcome = yield* promise(() => replayFor(setup));
40161
+ if (outcome.kind !== "rest") return yield* fail$7(slicesDivergence(run, outcome));
40162
+ }
40163
+ return {
40164
+ slices: sliceRangesOf(outcome.slices, run).map(({ name, base, tip, open }) => ({
40165
+ name,
40166
+ base,
40167
+ tip,
40168
+ closed: closing !== void 0 || !open
40169
+ })),
40170
+ ...versionWarning !== void 0 ? { versionWarning } : {}
40171
+ };
40172
+ });
40173
+ /** Every `gtd slices` refusal reads `gtd slices: refused — <reason>`; replay's own `gtd: ` lead is dropped. */
40174
+ const slicesDivergence = (run, outcome) => {
40175
+ 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";
40176
+ return /* @__PURE__ */ new Error(`gtd slices: refused — ${reason}`);
40177
+ };
40178
+ /**
40099
40179
  * Whether `gtd --workflow <name>` can start a process here: the workflow must
40100
40180
  * reach a step from the tree the opening commit would capture. That step is
40101
40181
  * what the opening commit's subject names.
@@ -40329,7 +40409,7 @@ const inRepoConfigPaths = (root, files, recorded) => {
40329
40409
  for (const file of files) paths.add(relative(root, resolve(root, file)).split(sep).join("/"));
40330
40410
  return [...paths].filter((p) => p !== "" && !p.startsWith("..") && !isAbsolute(p)).sort();
40331
40411
  };
40332
- const configRefusal = (tip, paths) => /* @__PURE__ */ new Error(`gtd history: refused — config differs at ${tip.slice(0, 7)}: ${paths.join(", ")}. A history ref replays under today's config, so it is unreadable after the config changes`);
40412
+ const configRefusal = (command, tip, paths, reason = "A history ref replays under today's config, so it is unreadable after the config changes") => /* @__PURE__ */ new Error(`${command}: refused — config differs at ${tip.slice(0, 7)}: ${paths.join(", ")}. ${reason}`);
40333
40413
  const differingConfigPaths = (git, workspace, root, config, run, hash) => gen(function* () {
40334
40414
  const differing = [];
40335
40415
  for (const path of inRepoConfigPaths(root, Object.keys(config.fingerprint), run.pinnedConfig?.files ?? {})) if ((yield* orElseSucceed(git.readFileAtRef(hash, path), () => void 0)) !== workspace.readSync(path)) differing.push(path);
@@ -40360,22 +40440,19 @@ const versionWarningFor = (recorded) => recorded === GTD_VERSION ? void 0 : `gtd
40360
40440
  const historyTrail = (tip) => gen(function* () {
40361
40441
  const git = yield* GitService;
40362
40442
  const config = yield* (yield* ConfigService).load;
40363
- const workspace = yield* Workspace;
40364
- const host = yield* Host;
40365
40443
  const hash = yield* git.resolveRef(tip);
40366
- const run = yield* closedRunAt(hash);
40367
- const differing = yield* differingConfigPaths(git, workspace, host.root, config, run, hash);
40368
- if (differing.length > 0) return yield* fail$7(configRefusal(hash, differing));
40369
- const { setup } = yield* setupFor(config, run, workspace, host);
40370
- yield* workspace.episodeTrees(run.episode.base, run.episode.commits.map((c) => c.hash));
40371
- const outcome = yield* promise(() => replayFor(setup));
40372
- if (outcome.kind !== "ended") return yield* fail$7(notClosedError(hash, outcome));
40373
- const steps = historySteps(run);
40374
- const recorded = parseCommitMessage(yield* git.lastCommitMessage(hash)).version;
40375
- const versionWarning = versionWarningFor(recorded);
40444
+ const subject = yield* git.lastCommitSubject(hash);
40445
+ const run = yield* runAtTip(git, config.workflow, {
40446
+ hash,
40447
+ subject
40448
+ });
40449
+ const { outcome, recorded, versionWarning } = yield* replayFinishedProcess(config, run, hash, {
40450
+ command: "gtd history",
40451
+ notEnded: (o) => notClosedError(hash, o)
40452
+ });
40376
40453
  return {
40377
40454
  tip: hash,
40378
- steps,
40455
+ steps: historySteps(run),
40379
40456
  ended: outcome.via,
40380
40457
  version: {
40381
40458
  recorded,
@@ -40384,6 +40461,24 @@ const historyTrail = (tip) => gen(function* () {
40384
40461
  ...versionWarning !== void 0 ? { versionWarning } : {}
40385
40462
  };
40386
40463
  });
40464
+ /** Refuses on config drift before replaying, so a stale config never runs the flow. */
40465
+ const replayFinishedProcess = (config, run, hash, how) => gen(function* () {
40466
+ const git = yield* GitService;
40467
+ const workspace = yield* Workspace;
40468
+ const host = yield* Host;
40469
+ const differing = yield* differingConfigPaths(git, workspace, host.root, config, run, hash);
40470
+ if (differing.length > 0) return yield* fail$7(configRefusal(how.command, hash, differing, how.configReason));
40471
+ const { setup } = yield* setupFor(config, run, workspace, host);
40472
+ const outcome = yield* promise(() => replayFor(setup));
40473
+ if (outcome.kind !== "ended") return yield* fail$7(how.notEnded(outcome));
40474
+ const recorded = parseCommitMessage(run.episode.commits.find((c) => c.hash === hash)?.message ?? "").version;
40475
+ const versionWarning = versionWarningFor(recorded);
40476
+ return {
40477
+ outcome,
40478
+ recorded,
40479
+ ...versionWarning !== void 0 ? { versionWarning } : {}
40480
+ };
40481
+ });
40387
40482
  //#endregion
40388
40483
  //#region src/emit/GitScript.ts
40389
40484
  const shellQuote$1 = (value) => `'${value.replace(/'/g, "'\\''")}'`;
@@ -41256,6 +41351,17 @@ const runDoorsCommand = (json, out) => gen(function* () {
41256
41351
  if (json.kind === "document") out.write(`${JSON.stringify(document)}\n`);
41257
41352
  else yield* writeSelection(out, document, json.path);
41258
41353
  });
41354
+ const runSlicesCommand = (json, out) => gen(function* () {
41355
+ const { slices, versionWarning } = yield* processSlices;
41356
+ if (versionWarning !== void 0) yield* (yield* Narrator).warn(versionWarning);
41357
+ if (json.kind === "off") {
41358
+ out.write(slices.map((s) => `${s.name} ${s.base}..${s.tip}\n`).join(""));
41359
+ return;
41360
+ }
41361
+ const document = slicesDocument(slices);
41362
+ if (json.kind === "document") out.write(renderSlicesJson(document));
41363
+ else yield* writeSelection(out, document, json.path);
41364
+ });
41259
41365
  /**
41260
41366
  * Ends the process underway without completing it. Nothing is discarded: a
41261
41367
  * `git reset --mixed` to the process's start commit leaves every turn
@@ -41591,6 +41697,7 @@ const dispatchVoidCommand = (command, json, out) => {
41591
41697
  case "start": return runStartCommand(command.actor, command.workflow, command.vars, out, command.label);
41592
41698
  case "door": return runDoorCommand(command.name, command.args, out);
41593
41699
  case "doors": return runDoorsCommand(json, out);
41700
+ case "slices": return runSlicesCommand(json, out);
41594
41701
  case "abandon": return runAbandonCommand(out);
41595
41702
  case "restore": return runRestoreCommand(out);
41596
41703
  case "next": return runNextCommand(json, out);
@@ -41649,12 +41756,12 @@ const FLAGS = [
41649
41756
  name: "--json",
41650
41757
  arity: "optional",
41651
41758
  repeatable: false,
41652
- scope: (kind) => kind === "next" || kind === "land" || kind === "judge" || kind === "judgeAnswer" || kind === "doors" || kind === "historyList" || kind === "historyShow" || kind === "strays",
41759
+ scope: (kind) => kind === "next" || kind === "land" || kind === "judge" || kind === "judgeAnswer" || kind === "doors" || kind === "slices" || kind === "historyList" || kind === "historyShow" || kind === "strays",
41653
41760
  decode: ([raw]) => right(raw ?? ""),
41654
- scopeError: "gtd: --json is only valid for `gtd next`/`gtd land`/`gtd judge`/`gtd judge answer`/`gtd doors`/`gtd history list`/`gtd history show`/`gtd strays` — every other command prints plain text; see `gtd install` for the driver protocol briefing",
41761
+ scopeError: "gtd: --json is only valid for `gtd next`/`gtd land`/`gtd judge`/`gtd judge answer`/`gtd doors`/`gtd slices`/`gtd history list`/`gtd history show`/`gtd strays` — every other command prints plain text; see `gtd install` for the driver protocol briefing",
41655
41762
  valueHint: "<path>",
41656
41763
  help: [
41657
- "(gtd next/gtd land/gtd judge/gtd judge answer/gtd doors/gtd history",
41764
+ "(gtd next/gtd land/gtd judge/gtd judge answer/gtd doors/gtd slices/gtd history",
41658
41765
  "list/gtd history show/gtd strays only) output structured JSON. Bare --json prints the whole document;",
41659
41766
  "--json=<path> (a dotted key path into that document, e.g.",
41660
41767
  "kind, content, session.id) prints just that value: a",
@@ -42107,10 +42214,10 @@ const COMMAND_ROWS = [
42107
42214
  "positional args to that workflow's process settings — the",
42108
42215
  "same start `gtd --workflow` makes, printed the same way.",
42109
42216
  "Each workflow the config exports brings its own doors (gtd's",
42110
- "`fix` brings `fix`, `review` brings `review [base]`), plus",
42111
- "the config's `doors` export. An unknown door, or too few or",
42112
- "too many args, is a usage error (exit 2) naming the doors or",
42113
- "the door's synopsis"
42217
+ "`fix`, `review [base]`, `rollout <flag>` and",
42218
+ "`cleanup <flag>`), plus the config's `doors` export. An",
42219
+ "unknown door, or too few or too many args, is a usage error",
42220
+ "(exit 2) naming the doors or the door's synopsis"
42114
42221
  ]
42115
42222
  },
42116
42223
  {
@@ -42124,6 +42231,25 @@ const COMMAND_ROWS = [
42124
42231
  "value off it. Writes nothing"
42125
42232
  ]
42126
42233
  },
42234
+ {
42235
+ token: "slices",
42236
+ kind: "slices",
42237
+ arity: "none",
42238
+ details: [
42239
+ "List the slices of the process HEAD closes or sits inside,",
42240
+ "first slice first: one `name base..tip` line each (full",
42241
+ "hashes; plain output carries no promise). --json prints",
42242
+ "{ schema: 1, slices: [{ name, base, tip, closed }] };",
42243
+ "--json=<path> reads one value off it. Writes nothing: no",
42244
+ "git, no state transition, no session identity. When HEAD",
42245
+ "closes a process, every slice is listed as closed; a gtd",
42246
+ "version mismatch only warns on stderr. Refuses (exit 1)",
42247
+ "when HEAD neither closes nor sits inside a process, or when",
42248
+ "today's config disagrees with history about the process:",
42249
+ "HEAD closes it and its config files differ from the working",
42250
+ "tree, or the replay diverged"
42251
+ ]
42252
+ },
42127
42253
  {
42128
42254
  token: "judge",
42129
42255
  kind: "judge",