@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/testkit.mjs
CHANGED
|
@@ -6647,8 +6647,11 @@ hands off to \`gtd-build\`, which halts at the blocked gate and prints it,
|
|
|
6647
6647
|
rather than starting the review; \`gtd-door fix\` on a GREEN suite is a no-op
|
|
6648
6648
|
straight back to \`idle\` — the exec'd \`gtd-build\` exits immediately on it.
|
|
6649
6649
|
\`gtd doors\` lists the doors a repository offers (those of the workflows its
|
|
6650
|
-
config exports — the bundled \`fix\` and \`review [base]
|
|
6651
|
-
|
|
6650
|
+
config exports — the bundled \`fix\` and \`review [base]\`, plus the opt-in
|
|
6651
|
+
\`rollout <flag>\` (turn a release flag default-on) and \`cleanup <flag>\`
|
|
6652
|
+
(remove it), which a config adds by exporting \`rollout\` from
|
|
6653
|
+
\`@pmelab/gtd/workflows/rollout\` and \`cleanup\` from
|
|
6654
|
+
\`@pmelab/gtd/workflows/cleanup\` — plus any it declares). Refuses with
|
|
6652
6655
|
\`usage: gtd-door <name> [args...]\` on stderr and exit \`2\` when the name is
|
|
6653
6656
|
missing — nothing was even attempted. Default path: \`~/.local/bin/gtd-door\`.
|
|
6654
6657
|
|
|
@@ -6758,7 +6761,10 @@ const renderBriefing = () => HEADER() + BEAT_PROTOCOL + JSON_FIELD_REFERENCE + D
|
|
|
6758
6761
|
const CONFIG_SCAFFOLD = `// The workflows gtd runs here: gtd's shipped feature, review and fix.
|
|
6759
6762
|
// Every exported function is startable with \`gtd --workflow <name>\`; the
|
|
6760
6763
|
// default export is what a change in the tree starts. Each workflow brings
|
|
6761
|
-
// its own doors: \`gtd door review [base]\` and \`gtd door fix\`.
|
|
6764
|
+
// its own doors: \`gtd door review [base]\` and \`gtd door fix\`. Export \`rollout\`
|
|
6765
|
+
// from \`@pmelab/gtd/workflows/rollout\` and \`cleanup\` from
|
|
6766
|
+
// \`@pmelab/gtd/workflows/cleanup\` to add \`gtd door rollout <flag>\` and
|
|
6767
|
+
// \`gtd door cleanup <flag>\`.
|
|
6762
6768
|
export {
|
|
6763
6769
|
feature as default,
|
|
6764
6770
|
feature,
|
|
@@ -8104,10 +8110,10 @@ const doorsFrom = (exports) => {
|
|
|
8104
8110
|
};
|
|
8105
8111
|
/**
|
|
8106
8112
|
* Each exported workflow's own doors, onto the name it is exported under. The
|
|
8107
|
-
* `doors` export adds to them and
|
|
8113
|
+
* `doors` export adds to them and declares a name several workflows clash on.
|
|
8108
8114
|
*/
|
|
8109
8115
|
const derivedDoors = (catalogue, declared) => {
|
|
8110
|
-
const
|
|
8116
|
+
const builders = /* @__PURE__ */ new Map();
|
|
8111
8117
|
for (const [workflow, loaded] of catalogue) for (const [name, spec] of Object.entries(loaded.doors ?? {})) {
|
|
8112
8118
|
const door = {
|
|
8113
8119
|
...spec,
|
|
@@ -8115,12 +8121,22 @@ const derivedDoors = (catalogue, declared) => {
|
|
|
8115
8121
|
};
|
|
8116
8122
|
const problem = doorProblem(name, door);
|
|
8117
8123
|
if (problem !== void 0) throw new Error(`workflow "${workflow}" builds door "${name}", which ${problem}`);
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8124
|
+
builders.set(name, [...builders.get(name) ?? [], door]);
|
|
8125
|
+
}
|
|
8126
|
+
const doors = /* @__PURE__ */ new Map();
|
|
8127
|
+
const clashes = /* @__PURE__ */ new Map();
|
|
8128
|
+
for (const [name, built] of builders) {
|
|
8129
|
+
const owner = ownerOf(name, built);
|
|
8130
|
+
if (owner !== void 0) doors.set(name, owner);
|
|
8131
|
+
else if (!declared.has(name)) clashes.set(name, built.map((door) => door.workflow));
|
|
8121
8132
|
}
|
|
8122
|
-
return
|
|
8133
|
+
return {
|
|
8134
|
+
doors: new Map([...doors, ...declared]),
|
|
8135
|
+
clashes
|
|
8136
|
+
};
|
|
8123
8137
|
};
|
|
8138
|
+
/** The export named after the door owns it, else the sole builder; a clash of several builds no door. */
|
|
8139
|
+
const ownerOf = (name, built) => built.find((door) => door.workflow === name) ?? (built.length === 1 ? built[0] : void 0);
|
|
8124
8140
|
/** Sort the doors and check each names a startable workflow. */
|
|
8125
8141
|
const checkDoors = (declared, catalogue) => {
|
|
8126
8142
|
const doors = new Map([...declared].sort(([a], [b]) => a.localeCompare(b)));
|
|
@@ -8151,16 +8167,28 @@ const loadWorkflows = (module, shipped) => {
|
|
|
8151
8167
|
return workflow === void 0 ? [] : [[name, workflow]];
|
|
8152
8168
|
}));
|
|
8153
8169
|
const defaultWorkflow = built.get(value);
|
|
8170
|
+
const derived = rejections.length > 0 ? {
|
|
8171
|
+
doors: /* @__PURE__ */ new Map(),
|
|
8172
|
+
clashes: /* @__PURE__ */ new Map()
|
|
8173
|
+
} : derivedDoors(catalogue, doorsFrom(exports));
|
|
8154
8174
|
return {
|
|
8155
8175
|
file,
|
|
8156
8176
|
catalogue,
|
|
8157
|
-
doors:
|
|
8177
|
+
doors: checkDoors(derived.doors, catalogue),
|
|
8158
8178
|
default: defaultWorkflow && {
|
|
8159
8179
|
...defaultWorkflow,
|
|
8160
8180
|
name: defaultName
|
|
8161
8181
|
},
|
|
8162
8182
|
config: compileConfig(exports["config"], module.filepath),
|
|
8163
|
-
diagnostics: nameRejections(rejections)
|
|
8183
|
+
diagnostics: [...nameRejections(rejections), ...[...derived.clashes].map(([name, builders]) => {
|
|
8184
|
+
const names = [...builders].sort().map((b) => JSON.stringify(b));
|
|
8185
|
+
return {
|
|
8186
|
+
severity: "warning",
|
|
8187
|
+
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`,
|
|
8188
|
+
path: ["doors", name],
|
|
8189
|
+
origin: file.origin
|
|
8190
|
+
};
|
|
8191
|
+
})]
|
|
8164
8192
|
};
|
|
8165
8193
|
};
|
|
8166
8194
|
const evaluateFromDisk = (module, shipped) => {
|
|
@@ -8698,9 +8726,10 @@ const traceEntryOf = (hash, message) => ({
|
|
|
8698
8726
|
hash,
|
|
8699
8727
|
actor: message.parsed?.actor ?? ""
|
|
8700
8728
|
});
|
|
8701
|
-
const costEntriesOf = (message) => message.cost.map((c) => ({
|
|
8729
|
+
const costEntriesOf = (hash, message) => message.cost.map((c) => ({
|
|
8702
8730
|
cost: c.cost,
|
|
8703
|
-
model: c.model ?? "unspecified"
|
|
8731
|
+
model: c.model ?? "unspecified",
|
|
8732
|
+
hash
|
|
8704
8733
|
}));
|
|
8705
8734
|
const hashAt = (history, index) => history[index]?.hash;
|
|
8706
8735
|
const historyFormatFault = (history, location) => formatFault(history.slice(location.baseIndex + 1).concat(history[location.baseIndex]?.message.startsWith("gtd") ? [history[location.baseIndex]] : []).map((c) => ({
|
|
@@ -8740,7 +8769,7 @@ const buildRun = (history, location, closingHash) => {
|
|
|
8740
8769
|
startParentHash,
|
|
8741
8770
|
diffBase: first?.reviewBase ?? startParentHash,
|
|
8742
8771
|
trace: processCommits.map((c, i) => traceEntryOf(c.hash, parsed[i])),
|
|
8743
|
-
costEntries: parsed.flatMap(costEntriesOf),
|
|
8772
|
+
costEntries: parsed.flatMap((m, i) => costEntriesOf(processCommits[i].hash, m)),
|
|
8744
8773
|
judgeVerdicts: parsed.flatMap((m) => m.judge),
|
|
8745
8774
|
marks: parsed.flatMap((m) => m.marks),
|
|
8746
8775
|
approvals: processCommits.flatMap((c, i) => approvalEntryOf(c.hash, parsed[i])),
|
|
@@ -9097,6 +9126,10 @@ const costByModel = (entries) => {
|
|
|
9097
9126
|
cost
|
|
9098
9127
|
})).sort((a, b) => b.cost - a.cost || a.model.localeCompare(b.model));
|
|
9099
9128
|
};
|
|
9129
|
+
const costTotals = (entries) => ({
|
|
9130
|
+
total: entries.reduce((sum, entry) => sum + entry.cost, 0),
|
|
9131
|
+
byModel: costByModel(entries)
|
|
9132
|
+
});
|
|
9100
9133
|
const setupFor = (config, run, workspace, host, startOverrides = {}) => gen(function* () {
|
|
9101
9134
|
const resolved = yield* workflowOf(config, run);
|
|
9102
9135
|
const def = resolved.def;
|
|
@@ -9200,57 +9233,69 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
|
|
|
9200
9233
|
memory: memory.key,
|
|
9201
9234
|
memoryResumed: memory.resumed,
|
|
9202
9235
|
hints: hintsOf(stepDef),
|
|
9203
|
-
cost:
|
|
9204
|
-
total: run.costEntries.reduce((sum, entry) => sum + entry.cost, 0),
|
|
9205
|
-
byModel: costByModel(run.costEntries)
|
|
9206
|
-
},
|
|
9236
|
+
cost: costTotals(run.costEntries),
|
|
9207
9237
|
setup
|
|
9208
9238
|
};
|
|
9209
9239
|
});
|
|
9210
9240
|
const currentRest = restAt(void 0);
|
|
9241
|
+
/** Every slice refusal reads `<command>: refused — <reason>`; replay's own `gtd: ` lead is dropped. */
|
|
9242
|
+
const slicesDivergence = (command, run, outcome) => {
|
|
9243
|
+
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";
|
|
9244
|
+
return /* @__PURE__ */ new Error(`${command}: refused — ${reason}`);
|
|
9245
|
+
};
|
|
9211
9246
|
/**
|
|
9212
|
-
* The
|
|
9213
|
-
*
|
|
9214
|
-
*
|
|
9247
|
+
* The replay `gtd slices` and `gtd summary --slice` share: the slice ranges of
|
|
9248
|
+
* `run`, plus whether HEAD closes it. Reads like `historyTrail`, never
|
|
9249
|
+
* `restAt`: it narrates and writes nothing.
|
|
9215
9250
|
*/
|
|
9216
|
-
const
|
|
9217
|
-
const git = yield* GitService;
|
|
9218
|
-
const config = yield* (yield* ConfigService).load;
|
|
9251
|
+
const replayedSlices = (config, run, how) => gen(function* () {
|
|
9219
9252
|
const workspace = yield* Workspace;
|
|
9220
9253
|
const host = yield* Host;
|
|
9221
|
-
|
|
9222
|
-
if (run.trace.length === 0) return yield* fail$5(/* @__PURE__ */ new Error("gtd slices: refused — HEAD neither closes nor sits inside a process"));
|
|
9254
|
+
if (run.trace.length === 0) return yield* fail$5(/* @__PURE__ */ new Error(`${how.command}: refused — HEAD neither closes nor sits inside a process`));
|
|
9223
9255
|
const closing = run.closingHash;
|
|
9224
9256
|
let outcome;
|
|
9225
9257
|
let versionWarning;
|
|
9226
9258
|
if (closing !== void 0) {
|
|
9227
9259
|
const replayed = yield* replayFinishedProcess(config, run, closing, {
|
|
9228
|
-
command:
|
|
9229
|
-
configReason:
|
|
9230
|
-
notEnded: (o) => slicesDivergence(run, o)
|
|
9260
|
+
command: how.command,
|
|
9261
|
+
configReason: how.configReason,
|
|
9262
|
+
notEnded: (o) => slicesDivergence(how.command, run, o)
|
|
9231
9263
|
});
|
|
9232
9264
|
outcome = replayed.outcome;
|
|
9233
9265
|
versionWarning = replayed.versionWarning;
|
|
9234
9266
|
} else {
|
|
9235
9267
|
const { setup } = yield* setupFor(config, run, workspace, host);
|
|
9236
9268
|
outcome = yield* promise(() => replayFor(setup));
|
|
9237
|
-
if (outcome.kind !== "rest") return yield* fail$5(slicesDivergence(run, outcome));
|
|
9269
|
+
if (outcome.kind !== "rest") return yield* fail$5(slicesDivergence(how.command, run, outcome));
|
|
9238
9270
|
}
|
|
9239
9271
|
return {
|
|
9240
|
-
|
|
9272
|
+
ranges: sliceRangesOf(outcome.slices, run),
|
|
9273
|
+
closed: closing !== void 0,
|
|
9274
|
+
...versionWarning !== void 0 ? { versionWarning } : {}
|
|
9275
|
+
};
|
|
9276
|
+
});
|
|
9277
|
+
/**
|
|
9278
|
+
* The slices of the process HEAD closes or sits inside, current episode only,
|
|
9279
|
+
* stack order. Returns a version warning for a closed run.
|
|
9280
|
+
*/
|
|
9281
|
+
const processSlices = gen(function* () {
|
|
9282
|
+
const git = yield* GitService;
|
|
9283
|
+
const config = yield* (yield* ConfigService).load;
|
|
9284
|
+
const run = yield* runAtHead(git, config.workflow);
|
|
9285
|
+
const { ranges, closed, versionWarning } = yield* replayedSlices(config, run, {
|
|
9286
|
+
command: "gtd slices",
|
|
9287
|
+
configReason: "HEAD's process replays under today's config; revert or stash the edit to list its slices"
|
|
9288
|
+
});
|
|
9289
|
+
return {
|
|
9290
|
+
slices: ranges.map(({ name, base, tip, open }) => ({
|
|
9241
9291
|
name,
|
|
9242
9292
|
base,
|
|
9243
9293
|
tip,
|
|
9244
|
-
closed:
|
|
9294
|
+
closed: closed || !open
|
|
9245
9295
|
})),
|
|
9246
9296
|
...versionWarning !== void 0 ? { versionWarning } : {}
|
|
9247
9297
|
};
|
|
9248
9298
|
});
|
|
9249
|
-
/** Every `gtd slices` refusal reads `gtd slices: refused — <reason>`; replay's own `gtd: ` lead is dropped. */
|
|
9250
|
-
const slicesDivergence = (run, outcome) => {
|
|
9251
|
-
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";
|
|
9252
|
-
return /* @__PURE__ */ new Error(`gtd slices: refused — ${reason}`);
|
|
9253
|
-
};
|
|
9254
9299
|
/**
|
|
9255
9300
|
* Whether `gtd --workflow <name>` can start a process here: the workflow must
|
|
9256
9301
|
* reach a step from the tree the opening commit would capture. That step is
|
|
@@ -9457,27 +9502,85 @@ const snapshotFromRest = (rest, verdicts) => gen(function* () {
|
|
|
9457
9502
|
landing
|
|
9458
9503
|
};
|
|
9459
9504
|
});
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
9464
|
-
|
|
9465
|
-
const
|
|
9466
|
-
|
|
9505
|
+
const narrowToSlice = (run, slice) => {
|
|
9506
|
+
if (slice === void 0) return {
|
|
9507
|
+
trace: run.trace,
|
|
9508
|
+
costEntries: run.costEntries
|
|
9509
|
+
};
|
|
9510
|
+
const from = slice.base === run.startParentHash ? -1 : run.trace.findIndex((e) => e.hash === slice.base);
|
|
9511
|
+
const to = run.trace.findIndex((e) => e.hash === slice.tip);
|
|
9512
|
+
const trace = run.trace.slice(from + 1, to + 1);
|
|
9513
|
+
const hashes = new Set(trace.map((e) => e.hash));
|
|
9514
|
+
return {
|
|
9515
|
+
trace,
|
|
9516
|
+
costEntries: run.costEntries.filter((c) => hashes.has(c.hash))
|
|
9517
|
+
};
|
|
9518
|
+
};
|
|
9519
|
+
const humanCommitsOf = (trace, entryCommit) => trace.filter((entry) => entry.actor === "human" && entry.hash !== entryCommit).map((entry) => ({
|
|
9520
|
+
hash: entry.hash,
|
|
9521
|
+
state: entry.state
|
|
9522
|
+
}));
|
|
9523
|
+
const summaryInput = (run, slice, settings) => {
|
|
9467
9524
|
const entryCommit = run.trace[0].hash;
|
|
9468
|
-
|
|
9525
|
+
const { trace, costEntries } = narrowToSlice(run, slice);
|
|
9526
|
+
const { total, byModel } = costTotals(costEntries);
|
|
9527
|
+
return {
|
|
9469
9528
|
entryCommit,
|
|
9470
|
-
processBase: run.startParentHash,
|
|
9471
|
-
processTip: run.trace[run.trace.length - 1].hash,
|
|
9472
|
-
humanCommits:
|
|
9473
|
-
|
|
9474
|
-
|
|
9475
|
-
})),
|
|
9476
|
-
processCost: run.costEntries.reduce((sum, entry) => sum + entry.cost, 0),
|
|
9477
|
-
processCostByModel: costByModel(run.costEntries),
|
|
9529
|
+
processBase: slice?.base ?? run.startParentHash,
|
|
9530
|
+
processTip: slice?.tip ?? run.trace[run.trace.length - 1].hash,
|
|
9531
|
+
humanCommits: humanCommitsOf(trace, entryCommit),
|
|
9532
|
+
processCost: total,
|
|
9533
|
+
processCostByModel: byModel,
|
|
9478
9534
|
marks: run.marks,
|
|
9479
|
-
...
|
|
9535
|
+
...settings,
|
|
9536
|
+
...slice !== void 0 ? { slice } : {}
|
|
9537
|
+
};
|
|
9538
|
+
};
|
|
9539
|
+
const locateSlice = (config, run, sliceName) => gen(function* () {
|
|
9540
|
+
if (sliceName === void 0) return {};
|
|
9541
|
+
const { ranges, versionWarning } = yield* replayedSlices(config, run, {
|
|
9542
|
+
command: "gtd summary",
|
|
9543
|
+
configReason: "HEAD's process replays under today's config; revert or stash the edit to summarize its slices"
|
|
9480
9544
|
});
|
|
9545
|
+
const warn = versionWarning !== void 0 ? { versionWarning } : {};
|
|
9546
|
+
const at = ranges.findIndex((r) => r.name === sliceName);
|
|
9547
|
+
if (at < 0) return {
|
|
9548
|
+
unknownSlice: `gtd summary: refused — no slice named "${sliceName}"; the slices, in stack order: ${ranges.map((r) => r.name).join(", ")}`,
|
|
9549
|
+
...warn
|
|
9550
|
+
};
|
|
9551
|
+
const { base, tip } = ranges[at];
|
|
9552
|
+
return {
|
|
9553
|
+
slice: {
|
|
9554
|
+
name: sliceName,
|
|
9555
|
+
base,
|
|
9556
|
+
tip,
|
|
9557
|
+
index: at + 1,
|
|
9558
|
+
count: ranges.length
|
|
9559
|
+
},
|
|
9560
|
+
...warn
|
|
9561
|
+
};
|
|
9562
|
+
});
|
|
9563
|
+
/**
|
|
9564
|
+
* A plain call never replays; under a slice, `entryCommit` and `marks` stay
|
|
9565
|
+
* whole-process.
|
|
9566
|
+
*/
|
|
9567
|
+
const summaryFor = (run, sliceName) => gen(function* () {
|
|
9568
|
+
const config = yield* (yield* ConfigService).load;
|
|
9569
|
+
const host = yield* Host;
|
|
9570
|
+
const resolved = yield* workflowOf(config, run);
|
|
9571
|
+
const summary = resolved.def.summary;
|
|
9572
|
+
if (summary === void 0) return { prompt: void 0 };
|
|
9573
|
+
if (sliceName === void 0 && run.trace.length === 0) return { prompt: void 0 };
|
|
9574
|
+
const located = yield* locateSlice(config, run, sliceName);
|
|
9575
|
+
if ("unknownSlice" in located) return {
|
|
9576
|
+
prompt: void 0,
|
|
9577
|
+
...located
|
|
9578
|
+
};
|
|
9579
|
+
const { slice, versionWarning } = located;
|
|
9580
|
+
return {
|
|
9581
|
+
prompt: summary(summaryInput(run, slice, settingsFor(resolved, run.pinnedVars, host.env, {}, {}))),
|
|
9582
|
+
...versionWarning !== void 0 ? { versionWarning } : {}
|
|
9583
|
+
};
|
|
9481
9584
|
});
|
|
9482
9585
|
/** 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. */
|
|
9483
9586
|
const inRepoConfigPaths = (root, files, recorded) => {
|
|
@@ -9542,13 +9645,14 @@ const replayFinishedProcess = (config, run, hash, how) => gen(function* () {
|
|
|
9542
9645
|
const git = yield* GitService;
|
|
9543
9646
|
const workspace = yield* Workspace;
|
|
9544
9647
|
const host = yield* Host;
|
|
9648
|
+
const recorded = parseCommitMessage(run.episode.commits.find((c) => c.hash === hash)?.message ?? "").version;
|
|
9649
|
+
const versionWarning = versionWarningFor(recorded);
|
|
9650
|
+
const withWarning = (err) => versionWarning === void 0 ? err : /* @__PURE__ */ new Error(`${err.message}\n${versionWarning}`);
|
|
9545
9651
|
const differing = yield* differingConfigPaths(git, workspace, host.root, config, run, hash);
|
|
9546
|
-
if (differing.length > 0) return yield* fail$5(configRefusal(how.command, hash, differing, how.configReason));
|
|
9652
|
+
if (differing.length > 0) return yield* fail$5(withWarning(configRefusal(how.command, hash, differing, how.configReason)));
|
|
9547
9653
|
const { setup } = yield* setupFor(config, run, workspace, host);
|
|
9548
9654
|
const outcome = yield* promise(() => replayFor(setup));
|
|
9549
|
-
if (outcome.kind !== "ended") return yield* fail$5(how.notEnded(outcome));
|
|
9550
|
-
const recorded = parseCommitMessage(run.episode.commits.find((c) => c.hash === hash)?.message ?? "").version;
|
|
9551
|
-
const versionWarning = versionWarningFor(recorded);
|
|
9655
|
+
if (outcome.kind !== "ended") return yield* fail$5(withWarning(how.notEnded(outcome)));
|
|
9552
9656
|
return {
|
|
9553
9657
|
outcome,
|
|
9554
9658
|
recorded,
|
|
@@ -10237,12 +10341,16 @@ const planLanding = (opts = {}, current) => gen(function* () {
|
|
|
10237
10341
|
* closes or sits inside its own closing message. Writes nothing — no git, no
|
|
10238
10342
|
* state transition, no file. Refuses (throws, mapped to the runtime-error
|
|
10239
10343
|
* exit code) when the workflow declares no `summary` prompt, or the resolved
|
|
10240
|
-
* run has no commits to name.
|
|
10344
|
+
* run has no commits to name. The edge never narrates: the CLI prints the
|
|
10345
|
+
* version warning, before the unknown-slice refusal; a replay refusal carries
|
|
10346
|
+
* it in its message.
|
|
10241
10347
|
*/
|
|
10242
|
-
const runSummaryCommand = (out) => gen(function* () {
|
|
10243
|
-
const
|
|
10244
|
-
if (
|
|
10245
|
-
|
|
10348
|
+
const runSummaryCommand = (out, sliceName) => gen(function* () {
|
|
10349
|
+
const { prompt, versionWarning, unknownSlice } = yield* summaryFor(yield* summaryRun, sliceName);
|
|
10350
|
+
if (versionWarning !== void 0) yield* (yield* Narrator).warn(versionWarning);
|
|
10351
|
+
if (unknownSlice !== void 0) return yield* fail$5(new Error(unknownSlice));
|
|
10352
|
+
if (prompt === void 0) return yield* fail$5(/* @__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"));
|
|
10353
|
+
out.write(prompt.endsWith("\n") ? prompt : prompt + "\n");
|
|
10246
10354
|
});
|
|
10247
10355
|
/** 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. */
|
|
10248
10356
|
const runBaseCommand = (out) => gen(function* () {
|
|
@@ -10780,7 +10888,7 @@ const dispatchVoidCommand = (command, json, out) => {
|
|
|
10780
10888
|
case "uncheck": return runUncheckCommand(command.file);
|
|
10781
10889
|
case "install": return runInstallCommand(out);
|
|
10782
10890
|
case "loop": return runLoopCommand();
|
|
10783
|
-
case "summary": return runSummaryCommand(out);
|
|
10891
|
+
case "summary": return runSummaryCommand(out, command.slice);
|
|
10784
10892
|
case "base": return runBaseCommand(out);
|
|
10785
10893
|
case "judge": return runJudgeCommand(json, out);
|
|
10786
10894
|
case "judgeAnswer": return runJudgeAnswerCommand(json, out);
|
|
@@ -10948,6 +11056,21 @@ const FLAGS = [
|
|
|
10948
11056
|
"GTD_JUDGE_MODEL, then judge.json, then sonnet"
|
|
10949
11057
|
]
|
|
10950
11058
|
},
|
|
11059
|
+
{
|
|
11060
|
+
name: "--slice",
|
|
11061
|
+
arity: 1,
|
|
11062
|
+
repeatable: false,
|
|
11063
|
+
scope: (kind) => kind === "summary",
|
|
11064
|
+
decode: ([raw]) => right(raw),
|
|
11065
|
+
scopeError: "gtd: --slice is only valid for `gtd summary`",
|
|
11066
|
+
valueHint: "<name>",
|
|
11067
|
+
help: [
|
|
11068
|
+
"(gtd summary only) the closing-message prompt for one",
|
|
11069
|
+
"slice, named as `gtd slices` lists it: its own range, human",
|
|
11070
|
+
"commits and cost. An unknown name is refused (exit 1) with",
|
|
11071
|
+
"every valid slice listed"
|
|
11072
|
+
]
|
|
11073
|
+
},
|
|
10951
11074
|
{
|
|
10952
11075
|
name: "--workflow",
|
|
10953
11076
|
arity: 1,
|
|
@@ -11252,10 +11375,15 @@ const COMMAND_ROWS = [
|
|
|
11252
11375
|
"no state transition, no file, no session identity — the",
|
|
11253
11376
|
"driver pipes the output to a cold agent and does what it",
|
|
11254
11377
|
"wants with the result (a squash, an amend, a PR body).",
|
|
11255
|
-
"
|
|
11256
|
-
"
|
|
11257
|
-
"
|
|
11258
|
-
"
|
|
11378
|
+
"--slice <name> prints it for one slice instead: its own",
|
|
11379
|
+
"range, human commits and cost (see `gtd slices`); it also",
|
|
11380
|
+
"refuses an unknown slice name, and a HEAD-closing process",
|
|
11381
|
+
"whose config files differ from the working tree or whose",
|
|
11382
|
+
"replay diverged; an open last slice is provisional. Refuses",
|
|
11383
|
+
"(exit 1) when the workflow declares no summary: template,",
|
|
11384
|
+
"or when the resolved run has no commits to name — plain",
|
|
11385
|
+
"gtd summary is runnable any time before the next thing",
|
|
11386
|
+
"lands on the branch"
|
|
11259
11387
|
]
|
|
11260
11388
|
},
|
|
11261
11389
|
{
|
|
@@ -11284,10 +11412,10 @@ const COMMAND_ROWS = [
|
|
|
11284
11412
|
"positional args to that workflow's process settings — the",
|
|
11285
11413
|
"same start `gtd --workflow` makes, printed the same way.",
|
|
11286
11414
|
"Each workflow the config exports brings its own doors (gtd's",
|
|
11287
|
-
"`fix
|
|
11288
|
-
"the config's `doors` export. An
|
|
11289
|
-
"too many args, is a usage error
|
|
11290
|
-
"the door's synopsis"
|
|
11415
|
+
"`fix`, `review [base]`, `rollout <flag>` and",
|
|
11416
|
+
"`cleanup <flag>`), plus the config's `doors` export. An",
|
|
11417
|
+
"unknown door, or too few or too many args, is a usage error",
|
|
11418
|
+
"(exit 2) naming the doors or the door's synopsis"
|
|
11291
11419
|
]
|
|
11292
11420
|
},
|
|
11293
11421
|
{
|
|
@@ -11712,6 +11840,15 @@ const parseArgv = (argv) => {
|
|
|
11712
11840
|
verbose
|
|
11713
11841
|
};
|
|
11714
11842
|
}
|
|
11843
|
+
if (kind === "summary") return {
|
|
11844
|
+
kind: "command",
|
|
11845
|
+
command: {
|
|
11846
|
+
kind: "summary",
|
|
11847
|
+
...bag["--slice"] !== void 0 ? { slice: bag["--slice"] } : {}
|
|
11848
|
+
},
|
|
11849
|
+
json,
|
|
11850
|
+
verbose
|
|
11851
|
+
};
|
|
11715
11852
|
if (kind === "historyShow") return {
|
|
11716
11853
|
kind: "command",
|
|
11717
11854
|
command: {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { agent, changes, glob, read, refuse, scope, start, vars } from "../flows.mjs";
|
|
2
|
-
import { a as flagShipped,
|
|
3
|
-
import { i as refusePendingHandOff, r as handOffTail } from "../chunks/tail-
|
|
4
|
-
import { i as requireFlag, r as registryEntry, t as flagReferences } from "../chunks/registry-CP3YuV9j.mjs";
|
|
2
|
+
import { a as flagShipped, ct as defaults, dt as flagReferences, i as skills, lt as envDefaults, mt as requireFlag, n as summary, pt as registryEntry, r as access, t as steering, v as coder } from "../chunks/reserved-DeBS4Ymh.mjs";
|
|
3
|
+
import { i as refusePendingHandOff, r as handOffTail } from "../chunks/tail-B2SER2Ih.mjs";
|
|
5
4
|
//#region src/workflows/cleanup/cleanup.ts
|
|
6
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.`;
|
|
7
6
|
const cleanupFlow = async () => {
|
|
@@ -29,7 +28,7 @@ const cleanupFlow = async () => {
|
|
|
29
28
|
});
|
|
30
29
|
await handOffTail(false, start(), guard);
|
|
31
30
|
};
|
|
32
|
-
const cleanup = flagShipped(cleanupFlow);
|
|
31
|
+
const cleanup = flagShipped(cleanupFlow, "cleanup");
|
|
33
32
|
//#endregion
|
|
34
33
|
//#region src/workflows/cleanup/index.ts
|
|
35
34
|
var cleanup_default = cleanup;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { answered, changes, changesSince, glob, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, numeric, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restoreScript, revertScript, run, scope, seamGate, start, vars } from "../flows.mjs";
|
|
2
2
|
import { review as review$1 } from "../workflow-kit.mjs";
|
|
3
|
-
import { $ as
|
|
4
|
-
import { a as tail, i as refusePendingHandOff, n as closeReview } from "../chunks/tail-
|
|
3
|
+
import { $ as reviewEditsCapture, A as architectureGateAnswerMessage, B as designTriagePrompt, C as reviewMissing, D as architectSystem, E as agentWithSkills, F as buildReviewTriageMessage, G as implementBuildingPrompt, I as builderSystem, J as packagesItemBuildingPrompt, K as implementFixPrompt, L as deferNotesScript, M as buildReviewAwaitAfterDeferMessage, N as buildReviewCollectingPrompt, O as architectureAuthorPrompt, P as buildReviewFixNitsPrompt, Q as reviewEditNotesCapture, R as designGateAnswerMessage, S as planner, T as reviewing, W as idleMessage, X as refactorBuildingPrompt, Y as packagesItemFixSuitePrompt, Z as refactorFixPrompt, _ as awaitReview, at as startGateBlockedMessage, ct as defaults, d as healthy, f as ARCHITECTURE, g as REVIEW, h as REQUIREMENTS, i as skills, it as seamSteerMessage, j as buildReviewAnswerQuestionsPrompt, k as architectureDecomposePrompt, l as gate, lt as envDefaults, n as summary, nt as reviewerSystem, o as shipped, ot as unwindFailedMessage, p as FEEDBACK, q as naivePassPrompt, r as access, rt as seamByHandMessage, st as unwindFailure, t as steering, tt as reviewNotesCapture, v as coder, x as fixRisks, y as fixCheck, z as designSystem } from "../chunks/reserved-DeBS4Ymh.mjs";
|
|
4
|
+
import { a as tail, i as refusePendingHandOff, n as closeReview } from "../chunks/tail-B2SER2Ih.mjs";
|
|
5
5
|
//#region src/workflows/feature/tier.ts
|
|
6
6
|
/** GTD_FLAG_NAIVE_PASS, GTD_FLAG_RISK_TIER: both pinned at process start, so a mid-process flip never moves a process. */
|
|
7
7
|
const riskTierOn = () => vars.flag_naive_pass === "1" && vars.flag_risk_tier === "1";
|
package/dist/workflows/fix.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { read, refuse, removeScript, run, scope, start } from "../flows.mjs";
|
|
2
2
|
import { d as reviewNotes, f as reviewRisks } from "../chunks/steering-DVILanVf.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-
|
|
3
|
+
import { E as agentWithSkills, H as fixReviewNotesPrompt, V as finisherSystem, ct as defaults, g as REVIEW, i as skills, lt as envDefaults, n as summary, o as shipped, r as access, s as baseline, t as steering, v as coder } from "../chunks/reserved-DeBS4Ymh.mjs";
|
|
4
|
+
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-B2SER2Ih.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,6 +1,6 @@
|
|
|
1
1
|
import { start } from "../flows.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { i as refusePendingHandOff, r as handOffTail } from "../chunks/tail-
|
|
2
|
+
import { ct as defaults, et as reviewGateBlockedMessage, i as skills, l as gate, lt as envDefaults, n as summary, o as shipped, r as access, t as steering } from "../chunks/reserved-DeBS4Ymh.mjs";
|
|
3
|
+
import { i as refusePendingHandOff, r as handOffTail } from "../chunks/tail-B2SER2Ih.mjs";
|
|
4
4
|
//#region src/workflows/review/review.ts
|
|
5
5
|
/**
|
|
6
6
|
* Pure review of everything since `reviewBase`: a green gate, the quality
|
|
@@ -1,9 +1,23 @@
|
|
|
1
|
-
import { agent, changesSince, head, read, refuse, scope, vars } from "../flows.mjs";
|
|
2
|
-
import { a as flagShipped,
|
|
3
|
-
import { i as requireFlag, n as flipProblems } from "../chunks/registry-CP3YuV9j.mjs";
|
|
1
|
+
import { agent, changesSince, glob, head, mark, read, refuse, removeScript, run, scope, vars } from "../flows.mjs";
|
|
2
|
+
import { a as flagShipped, ct as defaults, ft as flipProblems, i as skills, l as gate, lt as envDefaults, mt as requireFlag, n as summary, p as FEEDBACK, r as access, t as steering, ut as flagMentions, v as coder } from "../chunks/reserved-DeBS4Ymh.mjs";
|
|
4
3
|
//#region src/workflows/rollout/rollout.ts
|
|
5
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.`;
|
|
6
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.";
|
|
6
|
+
const MARKED_MENTIONS = 50;
|
|
7
|
+
const listMentions = async (registry, name, guard) => {
|
|
8
|
+
await run("mentions", removeScript([FEEDBACK]), { label: "Listing flag mentions" });
|
|
9
|
+
guard();
|
|
10
|
+
const mentions = flagMentions(glob("**").filter((path) => path !== registry && path !== ".gtd" && !path.startsWith(".gtd/")).sort().map((path) => ({
|
|
11
|
+
path,
|
|
12
|
+
text: read(path) ?? ""
|
|
13
|
+
})), name);
|
|
14
|
+
mark("flag-mentions", String(mentions.length));
|
|
15
|
+
for (const m of mentions.slice(0, MARKED_MENTIONS)) try {
|
|
16
|
+
mark("flag-mention", `${m.path}:${m.line}`);
|
|
17
|
+
} catch {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
7
21
|
const rolloutFlow = async () => {
|
|
8
22
|
const registry = vars.flagRegistry ?? "";
|
|
9
23
|
const name = vars.flag ?? "";
|
|
@@ -23,9 +37,10 @@ const rolloutFlow = async () => {
|
|
|
23
37
|
});
|
|
24
38
|
guard();
|
|
25
39
|
await gate("suite", suiteBlockedMessage(), guard);
|
|
40
|
+
await listMentions(registry, name, guard);
|
|
26
41
|
});
|
|
27
42
|
};
|
|
28
|
-
const rollout = flagShipped(rolloutFlow);
|
|
43
|
+
const rollout = flagShipped(rolloutFlow, "rollout");
|
|
29
44
|
//#endregion
|
|
30
45
|
//#region src/workflows/rollout/index.ts
|
|
31
46
|
var rollout_default = rollout;
|