@pmelab/gtd 52.1.0 → 53.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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +91 -129
- package/claude/hooks/drive.ts +0 -3
- package/claude/hooks/register.tsx +3 -68
- package/claude/hooks/ship.ts +85 -1595
- package/dist/chunks/{reserved-DxB5CROj.mjs → reserved-CQ7amcKG.mjs} +45 -132
- package/dist/chunks/{review-CKMSdvgW.mjs → review-CNgN_V8T.mjs} +2 -2
- package/dist/chunks/{tail-QZc7Uz2U.mjs → tail-JZ9PAOwp.mjs} +2 -14
- package/dist/chunks/{wire-BJuZWR3C.mjs → wire-C12dahrH.mjs} +2 -130
- package/dist/flows.mjs +1 -16
- package/dist/gtd.bundle.mjs +476 -982
- package/dist/testkit.mjs +58 -417
- package/dist/wire.mjs +2 -2
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +112 -363
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +3 -3
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +311 -674
- package/package.json +1 -1
- package/src/cli/Cli.test.ts +1 -86
- package/src/cli/Cli.ts +13 -133
- package/src/cli/Metrics.test.ts +21 -31
- package/src/cli/Metrics.ts +25 -36
- package/src/cli/Program.test.ts +1 -35
- package/src/cli/Program.ts +5 -64
- package/src/cli/ToolCommands.ts +0 -3
- package/src/edge/Edge.test.ts +20 -322
- package/src/edge/Edge.ts +10 -181
- package/src/edge/index.ts +0 -1
- package/src/flows/runtime.test.ts +0 -27
- package/src/flows/runtime.ts +1 -49
- package/src/install/Install.ts +15 -24
- package/src/replay/Replay.test.ts +0 -169
- package/src/replay/Replay.ts +4 -93
- package/src/replay/index.ts +0 -1
- package/src/testkit/Layers.ts +0 -1
- package/src/wire/BeatDocument.ts +2 -12
- package/src/wire/BeatStatus.ts +0 -3
- package/src/wire/WireSchema.test.ts +0 -18
- package/src/wire/index.ts +0 -2
- package/src/wire/types.ts +0 -7
- package/src/workflow/Workflow.ts +1 -9
- package/src/workflows/feature/criteria.test.ts +1 -14
- package/src/workflows/feature/criteria.ts +6 -38
- package/src/workflows/feature/feature.ts +34 -118
- package/src/workflows/feature/hands-on-skip.feature +2 -70
- package/src/workflows/feature/index.ts +0 -1
- package/src/workflows/feature/one-step-land.feature +0 -4
- package/src/workflows/feature/prototype-rounds.feature +1 -1
- package/src/workflows/feature/prototype.feature +3 -3
- package/src/workflows/feature/prototype.ts +18 -29
- package/src/workflows/feature/refactor-absorbs.feature +3 -8
- package/src/workflows/feature/refactor-first.feature +0 -5
- package/src/workflows/feature/refactor-rebuild.feature +0 -6
- package/src/workflows/feature/refactor-review.feature +11 -11
- package/src/workflows/feature/refactor.ts +30 -64
- package/src/workflows/feature/review-gate.feature +1 -1
- package/src/workflows/feature/seam-rejection.feature +0 -4
- package/src/workflows/feature/seam.test.ts +7 -26
- package/src/workflows/feature/seam.ts +52 -165
- package/src/workflows/index.ts +0 -1
- package/src/workflows/shared/lenses.ts +0 -13
- package/src/workflows/shared/options.test.ts +2 -11
- package/src/workflows/shared/options.ts +4 -21
- package/src/workflows/shared/review.test.ts +3 -3
- package/src/workflows/shared/steps.ts +0 -2
- package/src/workflows/shared/text.test.ts +10 -80
- package/src/workflows/shared/text.ts +35 -211
- package/src/workflows/shared/vars.ts +2 -8
- package/src/workflows/shared/workflows.steps.ts +0 -13
- package/claude/hooks/context.ts +0 -66
- package/src/replay/Slices.test.ts +0 -73
- package/src/replay/Slices.ts +0 -45
- package/src/wire/ContextLimit.fixture.ts +0 -30
- package/src/wire/ContextLimit.test.ts +0 -96
- package/src/wire/ContextLimit.ts +0 -169
- package/src/wire/Slices.ts +0 -25
- package/src/wire/golden/1/slices.json +0 -11
- package/src/workflows/feature/context-limit.feature +0 -37
- package/src/workflows/feature/continuation-cap.feature +0 -69
- package/src/workflows/feature/continuation-rebuild.feature +0 -92
- package/src/workflows/feature/continuation-slices.feature +0 -68
- package/src/workflows/feature/continuation.feature +0 -209
- package/src/workflows/feature/slice-findings.feature +0 -256
- package/src/workflows/feature/slicing-cap.feature +0 -23
- package/src/workflows/feature/slicing.feature +0 -297
- package/src/workflows/feature/slicing.test.ts +0 -49
- package/src/workflows/feature/slicing.ts +0 -105
- package/src/workflows/shared/continuation.ts +0 -92
package/dist/testkit.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { answered, applyScript, checkScript, configProblems, configure, installContext, isFactoryWorkflow, moveScript, numeric, protocolMismatch, removeScript, restoreScript, revertScript, showScript, t as flows_exports, writeScript } from "./flows.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { E as ACCESS_REFUSAL, _ as demandOf, a as landFields, c as renderLandJson, d as renderBeatPlain, f as beatDocument, g as beatKindOf, i as judgeJson, k as renderFormat, l as renderLandPlain, m as statusOf, n as resolveSession, p as renderBeatJson, r as selectPath, s as noopText, t as globMatches } from "./chunks/wire-C12dahrH.mjs";
|
|
3
3
|
import { A as parseThreadsWithFindings, M as headingSectionBodies, N as headingSections, P as formatFinding, a as unansweredQuestions, n as openThreads, r as steeringFormatFor, s as parseCodeThreads, t as BUILT_IN_MODE_NAMES } from "./chunks/steering-CLhu8C7q.mjs";
|
|
4
4
|
import { $ as logWarning, $i as BaseProto, $n as map$5, $r as make$19, $t as provide$1, A as catchIf, Ai as add$1, An as scopeMake, Ar as flatMap$3, At as suspend$5, B as flatMap$4, Bi as getOrElse, Bn as get$3, Br as sync$1, Bt as unsafeMakeLatch, C as all$1, Ci as toArray, Cn as isInterrupted, Cr as exitFail, Ct as sandbox, D as async, Di as GenericTag, Dn as squash, Dr as exitZipRight, Dt as scopedWith$1, E as asVoid$1, Ei as unsafeHead, En as reduceWithContext, Er as exitVoid, Et as scoped$4, F as ensuring$2, Fi as fromIterable, Fn as join$3, Fr as map$3, Ft as tryMap, G as forkIn, Gi as match$4, Gn as TypeId$10, Gr as whenEffect, Gt as zipLeft, H as fnUntraced, Hi as isNone, Hn as modify$1, Hr as uninterruptible, Ht as when, I as exit, Ii as splitAt, In as sequential, Ir as onInterrupt, It as tryPromise, J as ignore, Ji as isLeft, Jn as all, Jr as DeferredTypeId$1, Jt as spaced$1, K as forkScoped, Ki as none, Kn as Class, Kr as withFiberRuntime, Kt as zipRight$3, L as fail$5, Li as filter$1, Ln as bind, Lr as scopeClose, Lt as try_, M as die$2, Mi as getOption, Mn as _await$2, Mr as interruptAsFiber, Mt as tap, N as dieMessage, Ni as omit, Nn as inheritAll$1, Nr as interruptFiber, Nt as tapErrorCause, O as catchAll$1, Oi as Reference, On as ensuring$3, Or as fiberId, Ot as serviceOption, P as either, Pi as empty$5, Pn as interruptAllAs$1, Pr as interruptible$1, Pt as timeout, Q as isEffect, Qi as pipeArguments, Qn as isFailure, Qr as ids, Qt as mergeAll$2, R as failCause$4, Ri as flatMap$5, Rn as bindTo, Rr as succeed$4, Rt as uninterruptible$1, S as addFinalizer$1, Si as takeRight, Sn as isDieType, Sr as exitAs, St as runtime, T as as, Ti as unsafeGet, Tn as map$6, Tr as exitSucceed, Tt as scopeWith, U as forEach$2, Ui as isSome, Un as set$1, Ur as uninterruptibleMask$1, Ut as withFiberRuntime$1, V as flatten$4, Vi as getOrUndefined, Vn as make$16, Vr as tap$1, Vt as unsafeMakeSemaphore$1, W as forkDaemon, Wi as map$8, Wn as update$1, Wr as void_$2, Wt as zip$1, X as interruptible, Xi as match$2, Xn as fail$4, Xr as combine, Xt as tagged, Y as interrupt$4, Yi as left, Yn as die, Yr as empty$6, Yt as TaggedError$1, Z as intoDeferred, Zi as right, Zn as failCause$3, Zr as combineAll, Zt as fromEffect$5, _ as decodeSync, _i as of, _n as fail$3, _r as deferredIsDone, _t as raceWith, a as Literal, aa as constFalse, ai as size$2, an as updateContext$1, ar as zipRight$2, at as matchCause, b as acquireRelease, bi as reverse, bn as interrupt$3, br as deferredUnsafeDone, bt as runPromise, c as String$, ca as constVoid, ci as append$1, cn as Scope, cr as asyncInterrupt, ct as negate, d as Union, da as pipe, di as empty$4, dn as close, dr as deferredAwait, dt as orElse, ea as NodeInspectSymbol, ei as none$1, en as provideMerge$1, er as mapBoth, et as makeSemaphore, f as between, fi as filter, fn as extend, fr as deferredComplete, ft as orElseSucceed, gi as map$7, gn as die$1, gr as deferredInterruptWith, gt as race, hi as isEmpty$2, hn as NoSuchElementException, hr as deferredInterrupt, ht as provideService, i as Defect, ia as hasProperty, ii as has, in as unsafeRunPromiseExit, ir as zip$2, it as match$5, j as catchTag, ji as get$5, jn as scopeTag, jr as interrupt$2, jt as sync$2, k as catchAllCause, ki as Tag, kn as forEachConcurrentDiscard, kr as fiberRefLocally, kt as succeed$6, l as Struct, la as dual, li as appendAll, ln as addFinalizer, lr as catchAll, lt as onError, m as optional$1, mi as isChunk, mn as make$18, mr as deferredFailCause, mt as provide$2, n as Boolean$, na as toJSON, ni as make$17, nn as succeed$3, nr as succeed$5, nt as mapBoth$1, o as Never, oa as constTrue, oi as decode, on as forkIn$1, or as as$1, ot as matchCauseEffect, p as decodeUnknown, pi as fromIterable$1, pn as fork, pr as deferredFail, pt as promise, q as gen, qi as some, qn as spanTag, qr as zipRight$1, qt as zipWith, r as Cause, ra as equals, ri as set$2, rn as unsafeFork, rr as void_$3, rt as mapError, s as Number$, sa as constUndefined, si as isFinite, sn as unsafeMakeSemaphore, sr as asVoid, st as matchEffect, t as Array$, ta as format, ti as get$4, tn as scoped$3, tr as match$3, tt as map$4, u as TaggedError, ua as identity, ui as drop, un as addFinalizerExit, ur as currentContext$1, ut as orDie, v as encodeSync, vi as prepend, vn as failureOrCause, vr as deferredMake, vt as repeat, w as annotateLogs, wi as unsafeFromArray, wn as isInterruptedOnly, wr as exitFailCause, wt as scope, x as acquireUseRelease$2, xi as take$2, xn as interruptors, xr as deferredUnsafeMake, xt as runPromiseExit$1, y as _void, yi as reduce, yn as flipCauseOption, yr as deferredSucceed, yt as retry, z as fiberIdWith, zi as fromNullable, zn as sleep, zr as suspend$4, zt as uninterruptibleMask } from "./chunks/Schema-C6fLlQzx.mjs";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
@@ -6224,10 +6224,7 @@ while :; do
|
|
|
6224
6224
|
model="$(gtd next --json=model)"
|
|
6225
6225
|
system="$(gtd next --json=system)"
|
|
6226
6226
|
validate="$(gtd next --json=validate)"
|
|
6227
|
-
context="$(gtd next --json=context)"
|
|
6228
6227
|
log="$(gtd next --json=log)"
|
|
6229
|
-
# Absolute, since the hook runs wherever the agent last cd'ed to.
|
|
6230
|
-
case "$log" in /*) ;; *) log="$(git rev-parse --show-toplevel)/$log" ;; esac
|
|
6231
6228
|
# \`model\` is a hint, not a model name — the bundled workflow ships
|
|
6232
6229
|
# \`smart\` and \`base\`, which \`claude --model\` rejects. Resolve them
|
|
6233
6230
|
# from your environment; unset, the hint is dropped and the agent runs
|
|
@@ -6236,18 +6233,6 @@ while :; do
|
|
|
6236
6233
|
smart) model="\${GTD_LOOP_MODEL_SMART:-}" ;;
|
|
6237
6234
|
base) model="\${GTD_LOOP_MODEL_BASE:-}" ;;
|
|
6238
6235
|
esac
|
|
6239
|
-
# The step's context limit, as a Claude Code hook after every tool
|
|
6240
|
-
# call and before compacting. Past \`hard\` it stops the turn and
|
|
6241
|
-
# appends why to the stop file, so we land nothing and stop too.
|
|
6242
|
-
soft="$(printf '%s' "$context" | sed -n 's/.*"soft":\\([0-9.]*\\).*/\\1/p')"
|
|
6243
|
-
hard="$(printf '%s' "$context" | sed -n 's/.*"hard":\\([0-9.]*\\).*/\\1/p')"
|
|
6244
|
-
window="\${GTD_LOOP_CONTEXT_WINDOW:-}"
|
|
6245
|
-
case "$model" in *'[1m]'* | *'[1M]'*) window="\${window:-1000000}" ;; esac
|
|
6246
|
-
hook="gtd context-hook\${soft:+ --soft $soft}\${hard:+ --hard $hard}\${window:+ --window $window}"
|
|
6247
|
-
hooks="[{\\"hooks\\":[{\\"type\\":\\"command\\",\\"command\\":\\"$hook\\"}]}]"
|
|
6248
|
-
settings="{\\"hooks\\":{\\"PostToolUse\\":$hooks,\\"PreCompact\\":$hooks}}"
|
|
6249
|
-
stop="$log.context-stop"
|
|
6250
|
-
rm -f "$stop"
|
|
6251
6236
|
# One turn, its prompt on stdin. The prompt embeds a full diff, so it
|
|
6252
6237
|
# never goes on argv — argv is capped (~1 MB on macOS, and POSIX
|
|
6253
6238
|
# guarantees only 4 KB, ARG_MAX), and a diff crosses that far sooner
|
|
@@ -6255,15 +6240,15 @@ while :; do
|
|
|
6255
6240
|
# whether \`claude --resume\` re-applies the original session's is a
|
|
6256
6241
|
# harness detail gtd cannot verify from outside. Its JSON envelope
|
|
6257
6242
|
# goes to the log, and its \`total_cost_usd\` adds up into $cost. An
|
|
6258
|
-
# \`is_error\` envelope (max turns, an error mid-turn) or one
|
|
6259
|
-
#
|
|
6260
|
-
#
|
|
6243
|
+
# \`is_error\` envelope (max turns, an error mid-turn) or one a hook
|
|
6244
|
+
# stopped fails the turn even at exit 0: landing it would land the
|
|
6245
|
+
# failure as work. Only a turn that never ran (no envelope: the
|
|
6261
6246
|
# session flag was refused) is worth retrying under the other flag.
|
|
6262
6247
|
turn() {
|
|
6263
|
-
out="$(
|
|
6248
|
+
out="$(claude -p "$@" \\
|
|
6264
6249
|
\${model:+--model "$model"} \\
|
|
6265
6250
|
\${system:+--system-prompt "$system"} \\
|
|
6266
|
-
--
|
|
6251
|
+
--output-format json \\
|
|
6267
6252
|
--dangerously-skip-permissions 2>>"$log")" && ok=0 || ok=$?
|
|
6268
6253
|
printf '%s\\n' "$out" >>"$log"
|
|
6269
6254
|
case "$out" in *'"type":"result"'*) ran=true ;; *) ran=false ;; esac
|
|
@@ -6273,10 +6258,6 @@ while :; do
|
|
|
6273
6258
|
usd="$(printf '%s\\n' "$out" | sed -n 's/.*"total_cost_usd":\\([0-9.eE+-]*\\).*/\\1/p' | tail -n 1)"
|
|
6274
6259
|
[ -z "$usd" ] || cost="$(awk -v a="\${cost:-0}" -v b="$usd" 'BEGIN { printf "%.6f", a + b }')"
|
|
6275
6260
|
[ "\${cost:-}" != 0.000000 ] || cost=
|
|
6276
|
-
if [ -s "$stop" ]; then
|
|
6277
|
-
cat "$stop" >&2
|
|
6278
|
-
exit 1
|
|
6279
|
-
fi
|
|
6280
6261
|
return "$ok"
|
|
6281
6262
|
}
|
|
6282
6263
|
# Plain \`gtd next\` (not --json=content) renders the diff once.
|
|
@@ -6745,6 +6726,16 @@ way — an existing \`gtd-loop\` survives untouched beside the new
|
|
|
6745
6726
|
\`gtd-door review [base]\`). When either is still present beside the suite,
|
|
6746
6727
|
name it to the user as superseded and ask before removing it — never remove
|
|
6747
6728
|
one unasked.
|
|
6729
|
+
|
|
6730
|
+
An older \`gtd-build\` passed a \`gtd context-hook\` hook to every turn, and
|
|
6731
|
+
an older setup guide had it registered in Claude Code's own settings
|
|
6732
|
+
(\`PostToolUse\`/\`PreCompact\`). That command no longer exists: a hook
|
|
6733
|
+
still calling it fails on every tool call. Replace such a \`gtd-build\` as a
|
|
6734
|
+
differing file above, and look for a \`gtd context-hook\` entry in the user's
|
|
6735
|
+
Claude Code settings files (\`~/.claude/settings.json\`, the repository's
|
|
6736
|
+
\`.claude/settings.json\` and \`.claude/settings.local.json\`): name each file
|
|
6737
|
+
to the user, then remove that entry alone, leaving every other key
|
|
6738
|
+
byte-identical.
|
|
6748
6739
|
`;
|
|
6749
6740
|
const EDITOR_INTEGRATION = `
|
|
6750
6741
|
## Editor integration: offer it, don't bury it
|
|
@@ -6872,32 +6863,6 @@ const refNameFault = (name) => {
|
|
|
6872
6863
|
}
|
|
6873
6864
|
};
|
|
6874
6865
|
//#endregion
|
|
6875
|
-
//#region src/replay/Slices.ts
|
|
6876
|
-
const IMPLICIT_SLICE = "main";
|
|
6877
|
-
/** Same shape as mark()'s name rule; kept local so @pmelab/gtd/flows publishes no regex. */
|
|
6878
|
-
const SLICE_NAME = /^[a-z][a-z0-9-]*$/;
|
|
6879
|
-
/**
|
|
6880
|
-
* Folds boundaries into ranges: each kept span's base is the previous kept
|
|
6881
|
-
* span's `leftAt`, so a gap before a span belongs to it, and the last span
|
|
6882
|
-
* runs to the episode tip. A closed span that landed nothing is dropped.
|
|
6883
|
-
*/
|
|
6884
|
-
const sliceRanges = (spans, episode) => {
|
|
6885
|
-
if (spans.some((s) => s.enteredAt === "" || s.leftAt === "")) throw new Error("sliceRanges: a pending turn's \"\" hash is not a commit");
|
|
6886
|
-
const kept = spans.filter((s) => s.leftAt === void 0 || s.enteredAt !== s.leftAt);
|
|
6887
|
-
if (kept.length === 0) return [{
|
|
6888
|
-
name: IMPLICIT_SLICE,
|
|
6889
|
-
base: episode.base,
|
|
6890
|
-
tip: episode.tip,
|
|
6891
|
-
open: true
|
|
6892
|
-
}];
|
|
6893
|
-
return kept.map((s, i) => ({
|
|
6894
|
-
name: s.name,
|
|
6895
|
-
base: i === 0 ? episode.base : kept[i - 1].leftAt ?? episode.base,
|
|
6896
|
-
tip: i === kept.length - 1 ? episode.tip : s.leftAt ?? episode.tip,
|
|
6897
|
-
open: i === kept.length - 1
|
|
6898
|
-
}));
|
|
6899
|
-
};
|
|
6900
|
-
//#endregion
|
|
6901
6866
|
//#region src/replay/Tree.ts
|
|
6902
6867
|
const treeFromRecord = (files) => ({
|
|
6903
6868
|
paths: () => Object.keys(files).sort(),
|
|
@@ -7204,15 +7169,6 @@ const budgeted = (evidence, budgetBytes) => {
|
|
|
7204
7169
|
truncated
|
|
7205
7170
|
};
|
|
7206
7171
|
};
|
|
7207
|
-
const scopeLabel = (chain) => {
|
|
7208
|
-
const path = chain.flatMap((o) => o.name === void 0 ? [] : [o.name]).join(".");
|
|
7209
|
-
if (chain.at(-1)?.name !== void 0) return `scope "${path}"`;
|
|
7210
|
-
return path === "" ? "an unnamed scope" : `an unnamed scope inside "${path}"`;
|
|
7211
|
-
};
|
|
7212
|
-
const inheritedContext = (layers) => layers.reduce((merged, layer) => layer === void 0 ? merged : {
|
|
7213
|
-
...merged,
|
|
7214
|
-
...Object.fromEntries(Object.entries(layer).filter(([, value]) => value !== void 0))
|
|
7215
|
-
}, void 0);
|
|
7216
7172
|
const sameSkills = (a, b) => (a?.length ?? 0) === (b?.length ?? 0) && (a ?? []).every((skill, i) => skill === b?.[i]);
|
|
7217
7173
|
const samePersona = (a, b) => a.model === b.model && a.system === b.system && sameSkills(a.skills, b.skills) && sameAccess(a.access, b.access);
|
|
7218
7174
|
/**
|
|
@@ -7232,7 +7188,6 @@ const KNOWN_OPTIONS = {
|
|
|
7232
7188
|
...STEERING_OPTIONS,
|
|
7233
7189
|
"model",
|
|
7234
7190
|
"system",
|
|
7235
|
-
"context",
|
|
7236
7191
|
"allowEmpty",
|
|
7237
7192
|
"keep"
|
|
7238
7193
|
]),
|
|
@@ -7277,7 +7232,6 @@ const replay = async (input) => {
|
|
|
7277
7232
|
const keptTips = /* @__PURE__ */ new Map();
|
|
7278
7233
|
let landed;
|
|
7279
7234
|
const marks = [];
|
|
7280
|
-
const slices = [];
|
|
7281
7235
|
let outcome;
|
|
7282
7236
|
let signal;
|
|
7283
7237
|
const settled = new Promise((resolve) => {
|
|
@@ -7347,13 +7301,11 @@ const replay = async (input) => {
|
|
|
7347
7301
|
if (s.model !== void 0) persona.model = s.model;
|
|
7348
7302
|
if (s.system !== void 0) persona.system = s.system;
|
|
7349
7303
|
}
|
|
7350
|
-
const context = inheritedContext([...scopes.map((s) => s.context), request.options.context]);
|
|
7351
7304
|
return {
|
|
7352
7305
|
...request,
|
|
7353
7306
|
options: {
|
|
7354
7307
|
...persona,
|
|
7355
|
-
...request.options
|
|
7356
|
-
...context === void 0 ? {} : { context }
|
|
7308
|
+
...request.options
|
|
7357
7309
|
}
|
|
7358
7310
|
};
|
|
7359
7311
|
}
|
|
@@ -7487,31 +7439,14 @@ const replay = async (input) => {
|
|
|
7487
7439
|
rest: step,
|
|
7488
7440
|
trace,
|
|
7489
7441
|
landed,
|
|
7490
|
-
marks
|
|
7491
|
-
slices: slices.map((s) => ({ ...s }))
|
|
7442
|
+
marks
|
|
7492
7443
|
});
|
|
7493
7444
|
return landPending(step, input.pending);
|
|
7494
7445
|
};
|
|
7495
|
-
const scopeContextFault = () => {
|
|
7496
|
-
for (const [i, s] of scopes.entries()) {
|
|
7497
|
-
const fault = s.context === void 0 ? void 0 : contextShapeFault(s.context);
|
|
7498
|
-
if (fault !== void 0) return `the context of ${scopeLabel(scopes.slice(0, i + 1))}: ${fault}`;
|
|
7499
|
-
}
|
|
7500
|
-
};
|
|
7501
|
-
const resolvedContextFault = (request) => {
|
|
7502
|
-
const own = request.options.context;
|
|
7503
|
-
const layered = scopeContextFault() ?? (own === void 0 ? void 0 : contextShapeFault(own));
|
|
7504
|
-
if (layered !== void 0) return layered;
|
|
7505
|
-
const resolved = resolve(request);
|
|
7506
|
-
const context = resolved.kind === "agent" ? resolved.options.context : void 0;
|
|
7507
|
-
return context === void 0 ? void 0 : contextLimitFault(context);
|
|
7508
|
-
};
|
|
7509
7446
|
const requestFault = (request) => {
|
|
7510
7447
|
if (request.kind === "run" && typeof request.body !== "string") return `gtd: step "${scoped(request.name)}": a run() body is a shell script string — decide in flow code, then render the script (see checkScript and friends)`;
|
|
7511
7448
|
if (request.kind !== "agent") return void 0;
|
|
7512
7449
|
const name = scoped(request.name);
|
|
7513
|
-
const contextFault = resolvedContextFault(request);
|
|
7514
|
-
if (contextFault !== void 0) return `gtd: step "${name}": ${contextFault}`;
|
|
7515
7450
|
if (request.options.keep === true) {
|
|
7516
7451
|
if (request.options.allowEmpty === true) return `gtd: step "${name}": keep: true cannot be combined with allowEmpty — a turn that changes nothing has nothing to keep`;
|
|
7517
7452
|
const refFault = name.includes("/") ? "it contains \"/\"" : refNameFault(name);
|
|
@@ -7543,8 +7478,7 @@ const replay = async (input) => {
|
|
|
7543
7478
|
via,
|
|
7544
7479
|
trace,
|
|
7545
7480
|
landed,
|
|
7546
|
-
marks
|
|
7547
|
-
slices: slices.map((s) => ({ ...s }))
|
|
7481
|
+
marks
|
|
7548
7482
|
};
|
|
7549
7483
|
const changesSince = (hash) => {
|
|
7550
7484
|
const tree = treeAt(hash);
|
|
@@ -7569,31 +7503,6 @@ const replay = async (input) => {
|
|
|
7569
7503
|
scopes.pop();
|
|
7570
7504
|
scopeCallIds.pop();
|
|
7571
7505
|
},
|
|
7572
|
-
enterSlice: (name) => {
|
|
7573
|
-
if (outcome !== void 0) return;
|
|
7574
|
-
const fail = (message) => {
|
|
7575
|
-
finish({
|
|
7576
|
-
kind: "failed",
|
|
7577
|
-
message
|
|
7578
|
-
});
|
|
7579
|
-
throw new Stop();
|
|
7580
|
-
};
|
|
7581
|
-
if (typeof name !== "string" || !SLICE_NAME.test(name)) fail(`gtd: slice "${name}": name must match ${SLICE_NAME}`);
|
|
7582
|
-
if (name === "main") fail(`gtd: slice "${IMPLICIT_SLICE}" is reserved for the implicit slice`);
|
|
7583
|
-
const open = slices.find((s) => s.leftAt === void 0);
|
|
7584
|
-
if (open !== void 0) fail(`gtd: slice "${name}" is entered inside slice "${open.name}" — slices do not nest`);
|
|
7585
|
-
if (slices.some((s) => s.name === name)) fail(`gtd: slice "${name}" is entered twice in this episode — a loop must sit inside its slice`);
|
|
7586
|
-
slices.push({
|
|
7587
|
-
name,
|
|
7588
|
-
enteredAt: position.hash,
|
|
7589
|
-
leftAt: void 0
|
|
7590
|
-
});
|
|
7591
|
-
},
|
|
7592
|
-
leaveSlice: () => {
|
|
7593
|
-
if (outcome !== void 0) return;
|
|
7594
|
-
const open = slices.find((s) => s.leftAt === void 0);
|
|
7595
|
-
if (open !== void 0) open.leftAt = position.hash;
|
|
7596
|
-
},
|
|
7597
7506
|
read: (path) => position.tree.read(path),
|
|
7598
7507
|
glob: (pattern) => position.tree.paths().filter((path) => globMatches(path, pattern)),
|
|
7599
7508
|
changes: () => changesBetween(previousPosition.tree, position.tree),
|
|
@@ -9121,10 +9030,6 @@ const commonOf = (step) => {
|
|
|
9121
9030
|
...optional("mode", options.mode)
|
|
9122
9031
|
};
|
|
9123
9032
|
};
|
|
9124
|
-
const stepContext = (context) => context === void 0 ? void 0 : {
|
|
9125
|
-
...optional("soft", context.soft),
|
|
9126
|
-
...optional("hard", context.hard)
|
|
9127
|
-
};
|
|
9128
9033
|
const promptDef = (common, request, skills, access) => ({
|
|
9129
9034
|
...common,
|
|
9130
9035
|
kind: "prompt",
|
|
@@ -9133,7 +9038,6 @@ const promptDef = (common, request, skills, access) => ({
|
|
|
9133
9038
|
...optional("system", request.options.system),
|
|
9134
9039
|
...optional("allowEmpty", request.options.allowEmpty),
|
|
9135
9040
|
...optional("keep", request.options.keep),
|
|
9136
|
-
...optional("context", stepContext(request.options.context)),
|
|
9137
9041
|
...optional("skills", skills),
|
|
9138
9042
|
...optional("access", access)
|
|
9139
9043
|
});
|
|
@@ -9197,8 +9101,7 @@ const hintsOf = (def) => ({
|
|
|
9197
9101
|
...optional("system", def.system),
|
|
9198
9102
|
...optional("mode", def.mode),
|
|
9199
9103
|
...optional("skills", def.skills),
|
|
9200
|
-
...optional("access", def.access)
|
|
9201
|
-
...optional("context", def.context)
|
|
9104
|
+
...optional("access", def.access)
|
|
9202
9105
|
});
|
|
9203
9106
|
const normalizeStatus = (raw) => raw === "A" ? "A" : raw === "D" ? "D" : "M";
|
|
9204
9107
|
/** Per-model token totals, highest-cost first (ties broken by model name). */
|
|
@@ -9275,10 +9178,6 @@ const pendingChanges = (git, withIndex) => gen(function* () {
|
|
|
9275
9178
|
const seen = new Set(worktree.map((e) => e.path));
|
|
9276
9179
|
return [...worktree, ...(yield* git.stagedPaths()).filter((e) => !seen.has(e.path))];
|
|
9277
9180
|
});
|
|
9278
|
-
const sliceRangesOf = (slices, run) => sliceRanges(slices, {
|
|
9279
|
-
base: run.startParentHash,
|
|
9280
|
-
tip: run.trace.at(-1)?.hash ?? run.startParentHash
|
|
9281
|
-
});
|
|
9282
9181
|
/** Resolve the rest at `ref`, or at HEAD when `ref` is `undefined`. */
|
|
9283
9182
|
const restAt = (ref, startOverrides = {}) => gen(function* () {
|
|
9284
9183
|
const git = yield* GitService;
|
|
@@ -9322,64 +9221,6 @@ const restAt = (ref, startOverrides = {}) => gen(function* () {
|
|
|
9322
9221
|
};
|
|
9323
9222
|
});
|
|
9324
9223
|
const currentRest = restAt(void 0);
|
|
9325
|
-
/** Every slice refusal reads `<command>: refused — <reason>`; replay's own `gtd: ` lead is dropped. */
|
|
9326
|
-
const slicesDivergence = (command, run, outcome) => {
|
|
9327
|
-
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";
|
|
9328
|
-
return /* @__PURE__ */ new Error(`${command}: refused — ${reason}`);
|
|
9329
|
-
};
|
|
9330
|
-
/**
|
|
9331
|
-
* The replay `gtd slices` and `gtd summary --slice` share: the slice ranges of
|
|
9332
|
-
* `run`, plus whether HEAD closes it. Reads like `historyTrail`, never
|
|
9333
|
-
* `restAt`: it narrates and writes nothing.
|
|
9334
|
-
*/
|
|
9335
|
-
const replayedSlices = (config, run, how) => gen(function* () {
|
|
9336
|
-
const workspace = yield* Workspace;
|
|
9337
|
-
const host = yield* Host;
|
|
9338
|
-
if (run.trace.length === 0) return yield* fail$5(/* @__PURE__ */ new Error(`${how.command}: refused — HEAD neither closes nor sits inside a process`));
|
|
9339
|
-
const closing = run.closingHash;
|
|
9340
|
-
let outcome;
|
|
9341
|
-
let versionWarning;
|
|
9342
|
-
if (closing !== void 0) {
|
|
9343
|
-
const replayed = yield* replayFinishedProcess(config, run, closing, {
|
|
9344
|
-
command: how.command,
|
|
9345
|
-
configReason: how.configReason,
|
|
9346
|
-
notEnded: (o) => slicesDivergence(how.command, run, o)
|
|
9347
|
-
});
|
|
9348
|
-
outcome = replayed.outcome;
|
|
9349
|
-
versionWarning = replayed.versionWarning;
|
|
9350
|
-
} else {
|
|
9351
|
-
const { setup } = yield* setupFor(config, run, workspace, host);
|
|
9352
|
-
outcome = yield* promise(() => replayFor(setup));
|
|
9353
|
-
if (outcome.kind !== "rest") return yield* fail$5(slicesDivergence(how.command, run, outcome));
|
|
9354
|
-
}
|
|
9355
|
-
return {
|
|
9356
|
-
ranges: sliceRangesOf(outcome.slices, run),
|
|
9357
|
-
closed: closing !== void 0,
|
|
9358
|
-
...versionWarning !== void 0 ? { versionWarning } : {}
|
|
9359
|
-
};
|
|
9360
|
-
});
|
|
9361
|
-
/**
|
|
9362
|
-
* The slices of the process HEAD closes or sits inside, current episode only,
|
|
9363
|
-
* stack order. Returns a version warning for a closed run.
|
|
9364
|
-
*/
|
|
9365
|
-
const processSlices = gen(function* () {
|
|
9366
|
-
const git = yield* GitService;
|
|
9367
|
-
const config = yield* (yield* ConfigService).load;
|
|
9368
|
-
const run = yield* runAtHead(git, config.workflow);
|
|
9369
|
-
const { ranges, closed, versionWarning } = yield* replayedSlices(config, run, {
|
|
9370
|
-
command: "gtd slices",
|
|
9371
|
-
configReason: "HEAD's process replays under today's config; revert or stash the edit to list its slices"
|
|
9372
|
-
});
|
|
9373
|
-
return {
|
|
9374
|
-
slices: ranges.map(({ name, base, tip, open }) => ({
|
|
9375
|
-
name,
|
|
9376
|
-
base,
|
|
9377
|
-
tip,
|
|
9378
|
-
closed: closed || !open
|
|
9379
|
-
})),
|
|
9380
|
-
...versionWarning !== void 0 ? { versionWarning } : {}
|
|
9381
|
-
};
|
|
9382
|
-
});
|
|
9383
9224
|
/**
|
|
9384
9225
|
* Whether `gtd --workflow <name>` can start a process here: the workflow must
|
|
9385
9226
|
* reach a step from the tree the opening commit would capture. That step is
|
|
@@ -9586,85 +9427,31 @@ const snapshotFromRest = (rest, verdicts) => gen(function* () {
|
|
|
9586
9427
|
landing
|
|
9587
9428
|
};
|
|
9588
9429
|
});
|
|
9589
|
-
const narrowToSlice = (run, slice) => {
|
|
9590
|
-
if (slice === void 0) return {
|
|
9591
|
-
trace: run.trace,
|
|
9592
|
-
costEntries: run.costEntries
|
|
9593
|
-
};
|
|
9594
|
-
const from = slice.base === run.startParentHash ? -1 : run.trace.findIndex((e) => e.hash === slice.base);
|
|
9595
|
-
const to = run.trace.findIndex((e) => e.hash === slice.tip);
|
|
9596
|
-
const trace = run.trace.slice(from + 1, to + 1);
|
|
9597
|
-
const hashes = new Set(trace.map((e) => e.hash));
|
|
9598
|
-
return {
|
|
9599
|
-
trace,
|
|
9600
|
-
costEntries: run.costEntries.filter((c) => hashes.has(c.hash))
|
|
9601
|
-
};
|
|
9602
|
-
};
|
|
9603
9430
|
const humanCommitsOf = (trace, entryCommit) => trace.filter((entry) => entry.actor === "human" && entry.hash !== entryCommit).map((entry) => ({
|
|
9604
9431
|
hash: entry.hash,
|
|
9605
9432
|
state: entry.state
|
|
9606
9433
|
}));
|
|
9607
|
-
const summaryInput = (run,
|
|
9434
|
+
const summaryInput = (run, settings) => {
|
|
9608
9435
|
const entryCommit = run.trace[0].hash;
|
|
9609
|
-
const {
|
|
9610
|
-
const { total, byModel } = costTotals(costEntries);
|
|
9436
|
+
const { total, byModel } = costTotals(run.costEntries);
|
|
9611
9437
|
return {
|
|
9612
9438
|
entryCommit,
|
|
9613
|
-
processBase:
|
|
9614
|
-
processTip:
|
|
9615
|
-
humanCommits: humanCommitsOf(trace, entryCommit),
|
|
9439
|
+
processBase: run.startParentHash,
|
|
9440
|
+
processTip: run.trace[run.trace.length - 1].hash,
|
|
9441
|
+
humanCommits: humanCommitsOf(run.trace, entryCommit),
|
|
9616
9442
|
processCost: total,
|
|
9617
9443
|
processCostByModel: byModel,
|
|
9618
9444
|
marks: run.marks,
|
|
9619
|
-
...settings
|
|
9620
|
-
...slice !== void 0 ? { slice } : {}
|
|
9445
|
+
...settings
|
|
9621
9446
|
};
|
|
9622
9447
|
};
|
|
9623
|
-
const
|
|
9624
|
-
if (sliceName === void 0) return {};
|
|
9625
|
-
const { ranges, versionWarning } = yield* replayedSlices(config, run, {
|
|
9626
|
-
command: "gtd summary",
|
|
9627
|
-
configReason: "HEAD's process replays under today's config; revert or stash the edit to summarize its slices"
|
|
9628
|
-
});
|
|
9629
|
-
const warn = versionWarning !== void 0 ? { versionWarning } : {};
|
|
9630
|
-
const at = ranges.findIndex((r) => r.name === sliceName);
|
|
9631
|
-
if (at < 0) return {
|
|
9632
|
-
unknownSlice: `gtd summary: refused — no slice named "${sliceName}"; the slices, in stack order: ${ranges.map((r) => r.name).join(", ")}`,
|
|
9633
|
-
...warn
|
|
9634
|
-
};
|
|
9635
|
-
const { base, tip } = ranges[at];
|
|
9636
|
-
return {
|
|
9637
|
-
slice: {
|
|
9638
|
-
name: sliceName,
|
|
9639
|
-
base,
|
|
9640
|
-
tip,
|
|
9641
|
-
index: at + 1,
|
|
9642
|
-
count: ranges.length
|
|
9643
|
-
},
|
|
9644
|
-
...warn
|
|
9645
|
-
};
|
|
9646
|
-
});
|
|
9647
|
-
/**
|
|
9648
|
-
* A plain call never replays; under a slice, `entryCommit` and `marks` stay
|
|
9649
|
-
* whole-process.
|
|
9650
|
-
*/
|
|
9651
|
-
const summaryFor = (run, sliceName) => gen(function* () {
|
|
9448
|
+
const summaryFor = (run) => gen(function* () {
|
|
9652
9449
|
const config = yield* (yield* ConfigService).load;
|
|
9653
9450
|
const host = yield* Host;
|
|
9654
9451
|
const resolved = yield* workflowOf(config, run);
|
|
9655
9452
|
const summary = resolved.def.summary;
|
|
9656
|
-
if (summary === void 0) return { prompt: void 0 };
|
|
9657
|
-
|
|
9658
|
-
const located = yield* locateSlice(config, run, sliceName);
|
|
9659
|
-
if ("unknownSlice" in located) return {
|
|
9660
|
-
prompt: void 0,
|
|
9661
|
-
...located
|
|
9662
|
-
};
|
|
9663
|
-
const { slice, versionWarning } = located;
|
|
9664
|
-
return {
|
|
9665
|
-
prompt: summary(summaryInput(run, slice, settingsFor(resolved, run.pinnedVars, host.env, {}, {}))),
|
|
9666
|
-
...versionWarning !== void 0 ? { versionWarning } : {}
|
|
9667
|
-
};
|
|
9453
|
+
if (summary === void 0 || run.trace.length === 0) return { prompt: void 0 };
|
|
9454
|
+
return { prompt: summary(summaryInput(run, settingsFor(resolved, run.pinnedVars, host.env, {}, {}))) };
|
|
9668
9455
|
});
|
|
9669
9456
|
/** 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. */
|
|
9670
9457
|
const inRepoConfigPaths = (root, files, recorded) => {
|
|
@@ -10352,7 +10139,6 @@ const runHistoryFetchCommand = (remote, out) => sync$2(() => {
|
|
|
10352
10139
|
//#region src/cli/Metrics.ts
|
|
10353
10140
|
const QUALITY$1 = ".gtd/QUALITY.md";
|
|
10354
10141
|
const LENS = /^((?:.*\.)?quality\.[^.]+)\.reviewing$/;
|
|
10355
|
-
const CONTINUE = /(^|\.)continue\./;
|
|
10356
10142
|
const inSeam = (step) => step.startsWith("seam.");
|
|
10357
10143
|
const phaseOf = (step) => {
|
|
10358
10144
|
const scope = step.split(".")[0];
|
|
@@ -10371,13 +10157,12 @@ const add = (into, key, n) => {
|
|
|
10371
10157
|
};
|
|
10372
10158
|
const roundAll = (record) => Object.fromEntries(Object.entries(record).map(([k, v]) => [k, round(v)]));
|
|
10373
10159
|
const emptyTally = () => ({
|
|
10374
|
-
|
|
10160
|
+
builds: {
|
|
10375
10161
|
decided: 0,
|
|
10376
10162
|
landed: 0,
|
|
10377
10163
|
rebuilt: 0,
|
|
10378
10164
|
refactored: 0
|
|
10379
10165
|
},
|
|
10380
|
-
continuations: record(),
|
|
10381
10166
|
byModel: record(),
|
|
10382
10167
|
byPhase: {
|
|
10383
10168
|
naive: 0,
|
|
@@ -10394,9 +10179,8 @@ const emptyTally = () => ({
|
|
|
10394
10179
|
});
|
|
10395
10180
|
const sum = (record) => Object.values(record).reduce((a, b) => a + b, 0);
|
|
10396
10181
|
const finish = (t) => ({
|
|
10397
|
-
|
|
10398
|
-
refactorFirstRate: t.
|
|
10399
|
-
continuations: t.continuations,
|
|
10182
|
+
builds: t.builds,
|
|
10183
|
+
refactorFirstRate: t.builds.decided === 0 ? null : round(t.builds.rebuilt / t.builds.decided, 3),
|
|
10400
10184
|
cost: {
|
|
10401
10185
|
total: round(sum(t.byModel)),
|
|
10402
10186
|
byModel: roundAll(t.byModel),
|
|
@@ -10424,33 +10208,33 @@ const tallyCost = (t, open, costs, phase) => {
|
|
|
10424
10208
|
if (phase === "implement" && open.rebuilding) t.doubleWork.rebuild += cost;
|
|
10425
10209
|
}
|
|
10426
10210
|
};
|
|
10427
|
-
/** A
|
|
10211
|
+
/** A build is decided where its turns leave the seam: for `implement.applying` the naive pass landed — unless it fails to apply and is built afresh — anywhere else it is rebuilt. */
|
|
10428
10212
|
const tallyDecision = (t, open, at, to) => {
|
|
10429
10213
|
if (at === "implement.closing") open.rebuilding = false;
|
|
10430
10214
|
if (open.applying !== void 0 && at === "implement.applying") {
|
|
10431
10215
|
const naive = open.applying;
|
|
10432
10216
|
open.applying = void 0;
|
|
10433
10217
|
if (to !== "implement.building") return;
|
|
10434
|
-
t.
|
|
10218
|
+
t.builds.landed--;
|
|
10435
10219
|
rebuild(t, open, naive);
|
|
10436
10220
|
return;
|
|
10437
10221
|
}
|
|
10438
10222
|
if (!inSeam(at) || inSeam(to)) return;
|
|
10439
|
-
t.
|
|
10223
|
+
t.builds.decided++;
|
|
10440
10224
|
const naive = open.naive;
|
|
10441
10225
|
open.naive = 0;
|
|
10442
10226
|
if (to === "implement.applying") {
|
|
10443
10227
|
open.applying = naive;
|
|
10444
10228
|
open.rebuilding = false;
|
|
10445
|
-
t.
|
|
10229
|
+
t.builds.landed++;
|
|
10446
10230
|
return;
|
|
10447
10231
|
}
|
|
10448
10232
|
rebuild(t, open, naive);
|
|
10449
|
-
if (at === "seam.note") t.
|
|
10233
|
+
if (at === "seam.note") t.builds.refactored++;
|
|
10450
10234
|
};
|
|
10451
10235
|
const rebuild = (t, open, naive) => {
|
|
10452
10236
|
open.rebuilding = true;
|
|
10453
|
-
t.
|
|
10237
|
+
t.builds.rebuilt++;
|
|
10454
10238
|
t.doubleWork.naive += naive;
|
|
10455
10239
|
};
|
|
10456
10240
|
/** Oldest first; a commit gtd did not write is skipped. */
|
|
@@ -10467,7 +10251,6 @@ const processMetrics = (commits) => {
|
|
|
10467
10251
|
if (subject === void 0) continue;
|
|
10468
10252
|
const at = subject.from ?? subject.to;
|
|
10469
10253
|
tallyCost(t, open, message.cost, phaseOf(at));
|
|
10470
|
-
if (subject.actor === "agent" && CONTINUE.test(at)) add(t.continuations, at.replace(CONTINUE, "$1"), 1);
|
|
10471
10254
|
const lens = LENS.exec(at)?.[1];
|
|
10472
10255
|
if (lens !== void 0 && commit.findings > 0) add(t.byLens, lens, commit.findings);
|
|
10473
10256
|
if (message.marks.some((m) => m.name === "flaky")) add(t.flaky, at, 1);
|
|
@@ -10478,8 +10261,7 @@ const processMetrics = (commits) => {
|
|
|
10478
10261
|
const totalMetrics = (all) => {
|
|
10479
10262
|
const t = emptyTally();
|
|
10480
10263
|
for (const m of all) {
|
|
10481
|
-
for (const key of Object.keys(t.
|
|
10482
|
-
for (const [k, v] of Object.entries(m.continuations)) add(t.continuations, k, v);
|
|
10264
|
+
for (const key of Object.keys(t.builds)) t.builds[key] += m.builds[key];
|
|
10483
10265
|
for (const [k, v] of Object.entries(m.cost.byModel)) add(t.byModel, k, v);
|
|
10484
10266
|
for (const key of Object.keys(t.byPhase)) t.byPhase[key] += m.cost.byPhase[key];
|
|
10485
10267
|
t.doubleWork.naive += m.cost.doubleWork.naive;
|
|
@@ -10516,12 +10298,10 @@ const readProcessMetrics = (entry) => gen(function* () {
|
|
|
10516
10298
|
const printable = (s) => s.replace(/[\x00-\x1f\x7f-\x9f\u2028\u2029]/g, "?");
|
|
10517
10299
|
const listed = (record) => Object.entries(record).map(([k, v]) => `${printable(k)} ${v}`).join(", ");
|
|
10518
10300
|
const line = (label, m) => {
|
|
10519
|
-
const {
|
|
10301
|
+
const { builds, cost } = m;
|
|
10520
10302
|
const percent = m.refactorFirstRate === null ? "-" : `${Math.round(m.refactorFirstRate * 100)}%`;
|
|
10521
|
-
const continued = sum(m.continuations);
|
|
10522
10303
|
return [
|
|
10523
|
-
`${label}: ${
|
|
10524
|
-
`${continued} continuations${continued > 0 ? ` (${listed(m.continuations)})` : ""}`,
|
|
10304
|
+
`${label}: ${builds.decided} builds, ${builds.rebuilt} rebuilt (refactor-first ${percent}), ${builds.refactored} refactored`,
|
|
10525
10305
|
`cost ${cost.total} (${listed(cost.byPhase)}; by model ${listed(cost.byModel) || "-"}), double work ${cost.doubleWork.total}`,
|
|
10526
10306
|
`${m.findings.total} findings${m.findings.total > 0 ? ` (${listed(m.findings.byLens)})` : ""}`,
|
|
10527
10307
|
`${m.flaky.total} flaky${m.flaky.total > 0 ? ` (${listed(m.flaky.byStep)})` : ""}`
|
|
@@ -11005,16 +10785,10 @@ const planLanding = (opts = {}, current) => gen(function* () {
|
|
|
11005
10785
|
* closes or sits inside its own closing message. Writes nothing — no git, no
|
|
11006
10786
|
* state transition, no file. Refuses (throws, mapped to the runtime-error
|
|
11007
10787
|
* exit code) when the workflow declares no `summary` prompt, or the resolved
|
|
11008
|
-
* run has no commits to name.
|
|
11009
|
-
|
|
11010
|
-
|
|
11011
|
-
|
|
11012
|
-
* it in its message.
|
|
11013
|
-
*/
|
|
11014
|
-
const runSummaryCommand = (out, sliceName) => gen(function* () {
|
|
11015
|
-
const { prompt, versionWarning, unknownSlice } = yield* summaryFor(yield* summaryRun, sliceName);
|
|
11016
|
-
if (versionWarning !== void 0) yield* (yield* Narrator).warn(versionWarning);
|
|
11017
|
-
if (unknownSlice !== void 0) return yield* fail$5(new Error(unknownSlice));
|
|
10788
|
+
* run has no commits to name.
|
|
10789
|
+
*/
|
|
10790
|
+
const runSummaryCommand = (out) => gen(function* () {
|
|
10791
|
+
const { prompt } = yield* summaryFor(yield* summaryRun);
|
|
11018
10792
|
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"));
|
|
11019
10793
|
out.write(prompt.endsWith("\n") ? prompt : prompt + "\n");
|
|
11020
10794
|
});
|
|
@@ -11059,17 +10833,6 @@ const readStdin = () => tryPromise({
|
|
|
11059
10833
|
},
|
|
11060
10834
|
catch: (e) => e instanceof Error ? e : new Error(String(e))
|
|
11061
10835
|
});
|
|
11062
|
-
/** Stdout is written once: a hook reads one JSON document, or none. */
|
|
11063
|
-
const runContextHookCommand = (command, out) => gen(function* () {
|
|
11064
|
-
const tools = yield* ToolCommands;
|
|
11065
|
-
const input = yield* readStdin();
|
|
11066
|
-
out.write(yield* tools.contextHook({
|
|
11067
|
-
input,
|
|
11068
|
-
...command.soft !== void 0 ? { soft: command.soft } : {},
|
|
11069
|
-
...command.hard !== void 0 ? { hard: command.hard } : {},
|
|
11070
|
-
...command.window !== void 0 ? { window: command.window } : {}
|
|
11071
|
-
}));
|
|
11072
|
-
});
|
|
11073
10836
|
/** Stdout is written once, after the answerer resolves — a rejection leaves it empty. */
|
|
11074
10837
|
const runJudgeRunCommand = (command, out) => gen(function* () {
|
|
11075
10838
|
const host = yield* Host;
|
|
@@ -11208,17 +10971,6 @@ const runDoorsCommand = (json, out) => gen(function* () {
|
|
|
11208
10971
|
if (json.kind === "document") out.write(`${JSON.stringify(document)}\n`);
|
|
11209
10972
|
else yield* writeSelection(out, document, json.path);
|
|
11210
10973
|
});
|
|
11211
|
-
const runSlicesCommand = (json, out) => gen(function* () {
|
|
11212
|
-
const { slices, versionWarning } = yield* processSlices;
|
|
11213
|
-
if (versionWarning !== void 0) yield* (yield* Narrator).warn(versionWarning);
|
|
11214
|
-
if (json.kind === "off") {
|
|
11215
|
-
out.write(slices.map((s) => `${s.name} ${s.base}..${s.tip}\n`).join(""));
|
|
11216
|
-
return;
|
|
11217
|
-
}
|
|
11218
|
-
const document = slicesDocument(slices);
|
|
11219
|
-
if (json.kind === "document") out.write(renderSlicesJson(document));
|
|
11220
|
-
else yield* writeSelection(out, document, json.path);
|
|
11221
|
-
});
|
|
11222
10974
|
/**
|
|
11223
10975
|
* Ends the process underway without completing it. Nothing is discarded: a
|
|
11224
10976
|
* `git reset --mixed` to the process's start commit leaves every turn
|
|
@@ -11525,7 +11277,6 @@ const needsOf = (kind) => {
|
|
|
11525
11277
|
switch (kind) {
|
|
11526
11278
|
case "lsp":
|
|
11527
11279
|
case "install":
|
|
11528
|
-
case "contextHook":
|
|
11529
11280
|
case "judgeRun":
|
|
11530
11281
|
case "strays": return "none";
|
|
11531
11282
|
case "check":
|
|
@@ -11554,7 +11305,6 @@ const dispatchVoidCommand = (command, json, out) => {
|
|
|
11554
11305
|
case "start": return runStartCommand(command.actor, command.workflow, command.vars, out, command.label);
|
|
11555
11306
|
case "door": return runDoorCommand(command.name, command.args, out);
|
|
11556
11307
|
case "doors": return runDoorsCommand(json, out);
|
|
11557
|
-
case "slices": return runSlicesCommand(json, out);
|
|
11558
11308
|
case "abandon": return runAbandonCommand(out);
|
|
11559
11309
|
case "restore": return runRestoreCommand(out);
|
|
11560
11310
|
case "next": return runNextCommand(json, out);
|
|
@@ -11564,8 +11314,7 @@ const dispatchVoidCommand = (command, json, out) => {
|
|
|
11564
11314
|
return runCheckCommand(command.mode, command.file, command.openQuestions ?? false, command.openThreads ?? false);
|
|
11565
11315
|
case "uncheck": return runUncheckCommand(command.file);
|
|
11566
11316
|
case "install": return runInstallCommand(out);
|
|
11567
|
-
case "
|
|
11568
|
-
case "summary": return runSummaryCommand(out, command.slice);
|
|
11317
|
+
case "summary": return runSummaryCommand(out);
|
|
11569
11318
|
case "base": return runBaseCommand(out);
|
|
11570
11319
|
case "judge": return runJudgeCommand(json, out);
|
|
11571
11320
|
case "judgeAnswer": return runJudgeAnswerCommand(json, out);
|
|
@@ -11608,17 +11357,12 @@ const nonNegativeNumber = (raw, flag) => {
|
|
|
11608
11357
|
if (raw.trim() === "" || !Number.isFinite(n) || n < 0) return left(`gtd: ${flag} must be a non-negative number — got "${raw}"`);
|
|
11609
11358
|
return right(n);
|
|
11610
11359
|
};
|
|
11611
|
-
const share = (raw, flag) => {
|
|
11612
|
-
const n = Number(raw);
|
|
11613
|
-
return raw.trim() !== "" && n > 0 && n <= 1 ? right(n) : left(`gtd: ${flag} must be a share of the context window in (0, 1] — got "${raw}"`);
|
|
11614
|
-
};
|
|
11615
11360
|
const JSON_KINDS = /* @__PURE__ */ new Set([
|
|
11616
11361
|
"next",
|
|
11617
11362
|
"land",
|
|
11618
11363
|
"judge",
|
|
11619
11364
|
"judgeAnswer",
|
|
11620
11365
|
"doors",
|
|
11621
|
-
"slices",
|
|
11622
11366
|
"historyList",
|
|
11623
11367
|
"historyShow",
|
|
11624
11368
|
"historyMetrics",
|
|
@@ -11632,10 +11376,10 @@ const FLAGS = [
|
|
|
11632
11376
|
repeatable: false,
|
|
11633
11377
|
scope: (kind) => JSON_KINDS.has(kind),
|
|
11634
11378
|
decode: ([raw]) => right(raw ?? ""),
|
|
11635
|
-
scopeError: "gtd: --json is only valid for `gtd next`/`gtd land`/`gtd judge`/`gtd judge answer`/`gtd doors`/`gtd
|
|
11379
|
+
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 history metrics`/`gtd history judges`/`gtd strays` — every other command prints plain text; see `gtd install` for the driver protocol briefing",
|
|
11636
11380
|
valueHint: "<path>",
|
|
11637
11381
|
help: [
|
|
11638
|
-
"(gtd next/gtd land/gtd judge/gtd judge answer/gtd doors/gtd
|
|
11382
|
+
"(gtd next/gtd land/gtd judge/gtd judge answer/gtd doors/gtd history",
|
|
11639
11383
|
"list/gtd history show/gtd history metrics/gtd history judges/gtd strays only) output structured JSON. Bare --json prints the whole document;",
|
|
11640
11384
|
"--json=<path> (a dotted key path into that document, e.g.",
|
|
11641
11385
|
"kind, content, session.id) prints just that value: a",
|
|
@@ -11752,54 +11496,6 @@ const FLAGS = [
|
|
|
11752
11496
|
"GTD_JUDGE_MODEL, then judge.json, then sonnet"
|
|
11753
11497
|
]
|
|
11754
11498
|
},
|
|
11755
|
-
{
|
|
11756
|
-
name: "--soft",
|
|
11757
|
-
arity: 1,
|
|
11758
|
-
repeatable: false,
|
|
11759
|
-
scope: (kind) => kind === "contextHook",
|
|
11760
|
-
decode: ([raw]) => share(raw ?? "", "--soft"),
|
|
11761
|
-
scopeError: "gtd: --soft is only valid for `gtd context-hook`",
|
|
11762
|
-
valueHint: "<share>",
|
|
11763
|
-
help: ["(gtd context-hook only) the soft limit; default 0.6"]
|
|
11764
|
-
},
|
|
11765
|
-
{
|
|
11766
|
-
name: "--hard",
|
|
11767
|
-
arity: 1,
|
|
11768
|
-
repeatable: false,
|
|
11769
|
-
scope: (kind) => kind === "contextHook",
|
|
11770
|
-
decode: ([raw]) => share(raw ?? "", "--hard"),
|
|
11771
|
-
scopeError: "gtd: --hard is only valid for `gtd context-hook`",
|
|
11772
|
-
valueHint: "<share>",
|
|
11773
|
-
help: ["(gtd context-hook only) the hard limit; default 0.7"]
|
|
11774
|
-
},
|
|
11775
|
-
{
|
|
11776
|
-
name: "--window",
|
|
11777
|
-
arity: 1,
|
|
11778
|
-
repeatable: false,
|
|
11779
|
-
scope: (kind) => kind === "contextHook",
|
|
11780
|
-
decode: ([raw]) => {
|
|
11781
|
-
const window = parseContextWindow(raw);
|
|
11782
|
-
return window !== void 0 ? right(window) : left(`gtd: --window must be a positive integer — got "${raw ?? ""}"`);
|
|
11783
|
-
},
|
|
11784
|
-
scopeError: "gtd: --window is only valid for `gtd context-hook`",
|
|
11785
|
-
valueHint: "<tokens>",
|
|
11786
|
-
help: ["(gtd context-hook only) the model's context window in", "tokens; default 200000"]
|
|
11787
|
-
},
|
|
11788
|
-
{
|
|
11789
|
-
name: "--slice",
|
|
11790
|
-
arity: 1,
|
|
11791
|
-
repeatable: false,
|
|
11792
|
-
scope: (kind) => kind === "summary",
|
|
11793
|
-
decode: ([raw]) => right(raw),
|
|
11794
|
-
scopeError: "gtd: --slice is only valid for `gtd summary`",
|
|
11795
|
-
valueHint: "<name>",
|
|
11796
|
-
help: [
|
|
11797
|
-
"(gtd summary only) the closing-message prompt for one",
|
|
11798
|
-
"slice, named as `gtd slices` lists it: its own range, human",
|
|
11799
|
-
"commits and cost. An unknown name is refused (exit 1) with",
|
|
11800
|
-
"every valid slice listed"
|
|
11801
|
-
]
|
|
11802
|
-
},
|
|
11803
11499
|
{
|
|
11804
11500
|
name: "--workflow",
|
|
11805
11501
|
arity: 1,
|
|
@@ -12090,22 +11786,6 @@ const COMMAND_ROWS = [
|
|
|
12090
11786
|
"else it writes nothing."
|
|
12091
11787
|
]
|
|
12092
11788
|
},
|
|
12093
|
-
{
|
|
12094
|
-
token: "context-hook",
|
|
12095
|
-
kind: "contextHook",
|
|
12096
|
-
arity: "none",
|
|
12097
|
-
details: [
|
|
12098
|
-
"A Claude Code PostToolUse and PreCompact hook that stops an",
|
|
12099
|
-
"agent turn at a share of its context window. Reads the",
|
|
12100
|
-
"hook's input on stdin and the context the transcript's",
|
|
12101
|
-
"latest assistant message used. Past --soft it blocks with a",
|
|
12102
|
-
"request to end the turn with a handoff note; past --hard,",
|
|
12103
|
-
"or at PreCompact, it stops the turn with a stopReason",
|
|
12104
|
-
"starting \"gtd: context limit\". Prints nothing otherwise,",
|
|
12105
|
-
"and for a subagent's tool call. Always exits 0 on valid",
|
|
12106
|
-
"input; see docs/driver.md"
|
|
12107
|
-
]
|
|
12108
|
-
},
|
|
12109
11789
|
{
|
|
12110
11790
|
token: "summary",
|
|
12111
11791
|
kind: "summary",
|
|
@@ -12119,15 +11799,10 @@ const COMMAND_ROWS = [
|
|
|
12119
11799
|
"no state transition, no file, no session identity — the",
|
|
12120
11800
|
"driver pipes the output to a cold agent and does what it",
|
|
12121
11801
|
"wants with the result (a squash, an amend, a PR body).",
|
|
12122
|
-
"
|
|
12123
|
-
"
|
|
12124
|
-
"
|
|
12125
|
-
"
|
|
12126
|
-
"replay diverged; an open last slice is provisional. Refuses",
|
|
12127
|
-
"(exit 1) when the workflow declares no summary: template,",
|
|
12128
|
-
"or when the resolved run has no commits to name — plain",
|
|
12129
|
-
"gtd summary is runnable any time before the next thing",
|
|
12130
|
-
"lands on the branch"
|
|
11802
|
+
"Refuses (exit 1) when the workflow declares no summary:",
|
|
11803
|
+
"template, or when the resolved run has no commits to name —",
|
|
11804
|
+
"it is runnable any time before the next thing lands on the",
|
|
11805
|
+
"branch"
|
|
12131
11806
|
]
|
|
12132
11807
|
},
|
|
12133
11808
|
{
|
|
@@ -12173,25 +11848,6 @@ const COMMAND_ROWS = [
|
|
|
12173
11848
|
"value off it. Writes nothing"
|
|
12174
11849
|
]
|
|
12175
11850
|
},
|
|
12176
|
-
{
|
|
12177
|
-
token: "slices",
|
|
12178
|
-
kind: "slices",
|
|
12179
|
-
arity: "none",
|
|
12180
|
-
details: [
|
|
12181
|
-
"List the slices of the process HEAD closes or sits inside,",
|
|
12182
|
-
"first slice first: one `name base..tip` line each (full",
|
|
12183
|
-
"hashes; plain output carries no promise). --json prints",
|
|
12184
|
-
"{ schema: 1, slices: [{ name, base, tip, closed }] };",
|
|
12185
|
-
"--json=<path> reads one value off it. Writes nothing: no",
|
|
12186
|
-
"git, no state transition, no session identity. When HEAD",
|
|
12187
|
-
"closes a process, every slice is listed as closed; a gtd",
|
|
12188
|
-
"version mismatch only warns on stderr. Refuses (exit 1)",
|
|
12189
|
-
"when HEAD neither closes nor sits inside a process, or when",
|
|
12190
|
-
"today's config disagrees with history about the process:",
|
|
12191
|
-
"HEAD closes it and its config files differ from the working",
|
|
12192
|
-
"tree, or the replay diverged"
|
|
12193
|
-
]
|
|
12194
|
-
},
|
|
12195
11851
|
{
|
|
12196
11852
|
token: "judge",
|
|
12197
11853
|
kind: "judge",
|
|
@@ -12293,11 +11949,11 @@ const COMMAND_ROWS = [
|
|
|
12293
11949
|
many: true
|
|
12294
11950
|
},
|
|
12295
11951
|
details: [
|
|
12296
|
-
"Report how the kept processes went:
|
|
12297
|
-
"landed or was rebuilt (the refactor-first rate),
|
|
12298
|
-
"
|
|
12299
|
-
"
|
|
12300
|
-
"
|
|
11952
|
+
"Report how the kept processes went: builds whose naive pass",
|
|
11953
|
+
"landed or was rebuilt (the refactor-first rate), Gtd-Cost by",
|
|
11954
|
+
"model and phase with the double work of a rebuild, quality-lap",
|
|
11955
|
+
"findings per lens, and red checks that passed on their",
|
|
11956
|
+
"re-check — per process and in total. Each <ref>",
|
|
12301
11957
|
"is read as by history show, but a branch name means every",
|
|
12302
11958
|
"process kept from it; none means every ref.",
|
|
12303
11959
|
"Reads commit subjects and trailers, never replays, so a",
|
|
@@ -12626,23 +12282,9 @@ const parseArgv = (argv) => {
|
|
|
12626
12282
|
verbose
|
|
12627
12283
|
};
|
|
12628
12284
|
}
|
|
12629
|
-
if (kind === "contextHook") return {
|
|
12630
|
-
kind: "command",
|
|
12631
|
-
command: {
|
|
12632
|
-
kind: "contextHook",
|
|
12633
|
-
...bag["--soft"] !== void 0 ? { soft: bag["--soft"] } : {},
|
|
12634
|
-
...bag["--hard"] !== void 0 ? { hard: bag["--hard"] } : {},
|
|
12635
|
-
...bag["--window"] !== void 0 ? { window: bag["--window"] } : {}
|
|
12636
|
-
},
|
|
12637
|
-
json,
|
|
12638
|
-
verbose
|
|
12639
|
-
};
|
|
12640
12285
|
if (kind === "summary") return {
|
|
12641
12286
|
kind: "command",
|
|
12642
|
-
command: {
|
|
12643
|
-
kind: "summary",
|
|
12644
|
-
...bag["--slice"] !== void 0 ? { slice: bag["--slice"] } : {}
|
|
12645
|
-
},
|
|
12287
|
+
command: { kind: "summary" },
|
|
12646
12288
|
json,
|
|
12647
12289
|
verbose
|
|
12648
12290
|
};
|
|
@@ -13441,7 +13083,6 @@ const missingTool = (name) => () => fail$5(/* @__PURE__ */ new Error(`gtd ${name
|
|
|
13441
13083
|
const missingToolCommands = {
|
|
13442
13084
|
ui: missingTool("ui"),
|
|
13443
13085
|
lsp: missingTool("lsp"),
|
|
13444
|
-
contextHook: missingTool("context-hook"),
|
|
13445
13086
|
judge: () => Promise.reject(/* @__PURE__ */ new Error("gtd judge run: ToolCommands has no test double wired into testLayers()"))
|
|
13446
13087
|
};
|
|
13447
13088
|
/** The fine-grained `GitService` layer alone — for unit tests that need only git. */
|