@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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +53 -27
- package/claude/hooks/doors.ts +12 -0
- package/claude/hooks/register.tsx +2 -4
- package/claude/hooks/ship.ts +115 -20
- package/dist/chunks/{reserved-UdSuHDUJ.mjs → reserved-DeBS4Ymh.mjs} +178 -32
- package/dist/chunks/{tail-fj60rQ3y.mjs → tail-B2SER2Ih.mjs} +1 -1
- package/dist/gtd.bundle.mjs +209 -72
- package/dist/testkit.mjs +209 -72
- package/dist/workflows/cleanup.mjs +3 -4
- package/dist/workflows/feature.mjs +2 -2
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +2 -2
- package/dist/workflows/rollout.mjs +19 -4
- package/dist/workflows.mjs +196 -132
- package/package.json +1 -1
- package/skills/authoring/SKILL.md +4 -3
- package/src/cli/Cli.test.ts +39 -0
- package/src/cli/Cli.ts +41 -9
- package/src/cli/Program.test.ts +31 -0
- package/src/cli/Program.ts +14 -7
- package/src/edge/Edge.test.ts +259 -3
- package/src/edge/Edge.ts +175 -58
- package/src/flows/runtime.ts +20 -1
- package/src/install/ConfigScaffold.ts +4 -1
- package/src/install/Install.test.ts +10 -0
- package/src/install/Install.ts +5 -2
- package/src/workflow/load.test.ts +82 -5
- package/src/workflow/load.ts +40 -16
- package/src/workflows/cleanup/cleanup.feature +21 -0
- package/src/workflows/cleanup/cleanup.ts +1 -1
- package/src/workflows/index.ts +2 -1
- package/src/workflows/rollout/rollout.feature +282 -2
- package/src/workflows/rollout/rollout.test.ts +96 -2
- package/src/workflows/rollout/rollout.ts +41 -3
- package/src/workflows/shared/options.test.ts +25 -2
- package/src/workflows/shared/options.ts +7 -4
- package/src/workflows/shared/registry.test.ts +41 -1
- package/src/workflows/shared/registry.ts +26 -1
- package/src/workflows/shared/text.test.ts +377 -2
- package/src/workflows/shared/text.ts +65 -7
- package/src/workflows/shared/vars.ts +2 -0
- package/dist/chunks/registry-CP3YuV9j.mjs +0 -101
package/dist/gtd.bundle.mjs
CHANGED
|
@@ -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]
|
|
21524
|
-
|
|
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
|
|
39002
|
+
* `doors` export adds to them and declares a name several workflows clash on.
|
|
38997
39003
|
*/
|
|
38998
39004
|
const derivedDoors = (catalogue, declared) => {
|
|
38999
|
-
const
|
|
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
|
-
|
|
39008
|
-
|
|
39009
|
-
|
|
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
|
|
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:
|
|
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
|
|
40109
|
-
*
|
|
40110
|
-
*
|
|
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
|
|
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
|
-
|
|
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:
|
|
40125
|
-
configReason:
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
40357
|
-
|
|
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
|
|
40363
|
-
|
|
40364
|
-
|
|
40365
|
-
|
|
40366
|
-
|
|
40367
|
-
|
|
40368
|
-
|
|
40369
|
-
|
|
40370
|
-
|
|
40371
|
-
})),
|
|
40372
|
-
|
|
40373
|
-
|
|
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
|
|
41141
|
-
if (
|
|
41142
|
-
|
|
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
|
-
"
|
|
42158
|
-
"
|
|
42159
|
-
"
|
|
42160
|
-
"
|
|
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
|
|
42190
|
-
"the config's `doors` export. An
|
|
42191
|
-
"too many args, is a usage error
|
|
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: {
|