@pmelab/gtd 38.1.0 → 38.2.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 +30 -3
- package/dist/chunks/{Schema-DO5ttJaI.mjs → Schema-C6fLlQzx.mjs} +1 -1
- package/dist/chunks/{reserved-CfQ8eurM.mjs → reserved-C3PgP7XB.mjs} +1 -1
- package/dist/chunks/{tail-C7yaWvaS.mjs → tail-Bkxki2nK.mjs} +1 -1
- package/dist/gtd.bundle.mjs +291 -52
- package/dist/testkit.mjs +256 -20
- package/dist/workflows/cleanup.mjs +2 -2
- 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 +1 -1
- package/package.json +1 -1
- package/src/cli/Cli.test.ts +15 -3
- package/src/cli/Cli.ts +61 -28
- package/src/cli/History.ts +1 -19
- package/src/cli/Metrics.test.ts +224 -0
- package/src/cli/Metrics.ts +310 -0
- package/src/cli/Output.ts +13 -0
- package/src/cli/Program.test.ts +3 -1
- package/src/cli/Program.ts +4 -0
package/dist/testkit.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { applyScript, checkScript, configProblems, configure, installContext, isFactoryWorkflow, moveScript, protocolMismatch, removeScript, restoreScript, revertScript, showScript, t as flows_exports } from "./flows.mjs";
|
|
2
2
|
import { A as beatKindOf, B as ACCESS_REFUSAL, C as renderLandPlain, D as renderBeatJson, E as beatDocument, O as statusOf, S as renderLandJson, T as renderBeatPlain, U as renderFormat, _ as selectPath, a as contextLimitFault, g as resolveSession, h as globMatches, j as demandOf, l as parseContextWindow, m as slicesDocument, o as contextShapeFault, p as renderSlicesJson, v as judgeJson, x as noopText, y as landFields } from "./chunks/wire-BJuZWR3C.mjs";
|
|
3
3
|
import { C as headingSections, S as headingSectionBodies, a as unansweredQuestions, b as parseThreadsWithFindings, n as openThreads, r as steeringFormatFor, s as parseCodeThreads, t as BUILT_IN_MODE_NAMES, w as formatFinding } from "./chunks/steering-DVILanVf.mjs";
|
|
4
|
-
import { $ as logWarning, $i as
|
|
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";
|
|
6
6
|
import * as Crypto from "node:crypto";
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
@@ -2686,7 +2686,7 @@ const get$1 = (self_) => {
|
|
|
2686
2686
|
case "Acquired":
|
|
2687
2687
|
self.state.refCount++;
|
|
2688
2688
|
return self.state.fiber ? as$1(interruptFiber(self.state.fiber), self.state) : succeed$4(self.state);
|
|
2689
|
-
case "Empty": return scopeMake().pipe(bindTo("scope"), bind("value", ({ scope }) => restore(fiberRefLocally(self.acquire, currentContext$1, add(self.context, scopeTag, scope)))), map$3(({ scope, value }) => {
|
|
2689
|
+
case "Empty": return scopeMake().pipe(bindTo("scope"), bind("value", ({ scope }) => restore(fiberRefLocally(self.acquire, currentContext$1, add$1(self.context, scopeTag, scope)))), map$3(({ scope, value }) => {
|
|
2690
2690
|
const state = {
|
|
2691
2691
|
_tag: "Acquired",
|
|
2692
2692
|
value,
|
|
@@ -10190,6 +10190,13 @@ const writeSelection = (out, fields, path) => gen(function* () {
|
|
|
10190
10190
|
if (selection.kind === "value") out.write(`${selection.text}\n`);
|
|
10191
10191
|
else if (selection.kind === "unknown") return yield* fail$5(new SelectorUsageError(`gtd: unknown --json selector "${selection.path}" — see \`gtd --help\``));
|
|
10192
10192
|
});
|
|
10193
|
+
/** Writes `document` per `json` (bare → whole, `=path` → one value); returns false for `off`. */
|
|
10194
|
+
const writeJson = (json, out, document) => gen(function* () {
|
|
10195
|
+
if (json.kind === "off") return false;
|
|
10196
|
+
if (json.kind === "document") out.write(`${JSON.stringify(document)}\n`);
|
|
10197
|
+
else yield* writeSelection(out, document, json.path);
|
|
10198
|
+
return true;
|
|
10199
|
+
});
|
|
10193
10200
|
//#endregion
|
|
10194
10201
|
//#region src/cli/Strays.ts
|
|
10195
10202
|
const resolveSide = (git, side) => git.resolveRef(`${side}^{commit}`).pipe(mapError(() => new GtdError(`gtd strays: unknown revision '${side}'`)));
|
|
@@ -10247,13 +10254,6 @@ const resolveHistoryRef = (arg, entries) => {
|
|
|
10247
10254
|
return fail$5(/* @__PURE__ */ new Error(`gtd history: no history ref matches "${arg}"`));
|
|
10248
10255
|
};
|
|
10249
10256
|
const seven = (hash) => hash.slice(0, 7);
|
|
10250
|
-
/** Writes `document` per `json` (bare → whole, `=path` → one value); returns false for `off`. */
|
|
10251
|
-
const writeJson = (json, out, document) => gen(function* () {
|
|
10252
|
-
if (json.kind === "off") return false;
|
|
10253
|
-
if (json.kind === "document") out.write(`${JSON.stringify(document)}\n`);
|
|
10254
|
-
else yield* writeSelection(out, document, json.path);
|
|
10255
|
-
return true;
|
|
10256
|
-
});
|
|
10257
10257
|
const runHistoryListCommand = (json, out) => gen(function* () {
|
|
10258
10258
|
const entries = yield* historyEntries;
|
|
10259
10259
|
if (yield* writeJson(json, out, entries)) return;
|
|
@@ -10266,13 +10266,12 @@ const runHistoryListCommand = (json, out) => gen(function* () {
|
|
|
10266
10266
|
const runHistoryShowCommand = (ref, json, out) => gen(function* () {
|
|
10267
10267
|
const entry = yield* resolveHistoryRef(ref, yield* historyEntries);
|
|
10268
10268
|
const trail = yield* historyTrail(entry.tip);
|
|
10269
|
-
|
|
10269
|
+
if (yield* writeJson(json, out, {
|
|
10270
10270
|
ref: entry.ref,
|
|
10271
10271
|
entry: entry.entry,
|
|
10272
10272
|
branch: entry.branch,
|
|
10273
10273
|
...trail
|
|
10274
|
-
};
|
|
10275
|
-
if (yield* writeJson(json, out, document)) return;
|
|
10274
|
+
})) return;
|
|
10276
10275
|
if (trail.versionWarning !== void 0) yield* (yield* Narrator).warn(trail.versionWarning);
|
|
10277
10276
|
const lines = [`${entry.ref} (branch ${entry.branch ?? "-"}, tip ${seven(entry.tip)})`];
|
|
10278
10277
|
for (const step of trail.steps) {
|
|
@@ -10288,6 +10287,200 @@ const runHistoryFetchCommand = (remote, out) => sync$2(() => {
|
|
|
10288
10287
|
out.write(`${combinedScript(fetchHistoryRefs(remote), "")}\n`);
|
|
10289
10288
|
});
|
|
10290
10289
|
//#endregion
|
|
10290
|
+
//#region src/cli/Metrics.ts
|
|
10291
|
+
const QUALITY = ".gtd/QUALITY.md";
|
|
10292
|
+
const LENS = /^((?:.*\.)?quality\.[^.]+)\.reviewing$/;
|
|
10293
|
+
const CONTINUE = /(^|\.)continue\./;
|
|
10294
|
+
const inSeam = (step) => step.startsWith("seam.");
|
|
10295
|
+
const phaseOf = (step) => {
|
|
10296
|
+
const scope = step.split(".")[0];
|
|
10297
|
+
if (inSeam(step) && step.endsWith(".naive")) return "naive";
|
|
10298
|
+
if (step === "seam.note" || scope === "refactor") return "refactor";
|
|
10299
|
+
if (scope === "implement") return "implement";
|
|
10300
|
+
return "other";
|
|
10301
|
+
};
|
|
10302
|
+
const round = (n, places = 6) => {
|
|
10303
|
+
const f = 10 ** places;
|
|
10304
|
+
return Math.round(n * f) / f;
|
|
10305
|
+
};
|
|
10306
|
+
const record = () => Object.create(null);
|
|
10307
|
+
const add = (into, key, n) => {
|
|
10308
|
+
into[key] = (into[key] ?? 0) + n;
|
|
10309
|
+
};
|
|
10310
|
+
const roundAll = (record) => Object.fromEntries(Object.entries(record).map(([k, v]) => [k, round(v)]));
|
|
10311
|
+
const emptyTally = () => ({
|
|
10312
|
+
slices: {
|
|
10313
|
+
decided: 0,
|
|
10314
|
+
landed: 0,
|
|
10315
|
+
rebuilt: 0,
|
|
10316
|
+
refactored: 0
|
|
10317
|
+
},
|
|
10318
|
+
continuations: record(),
|
|
10319
|
+
byModel: record(),
|
|
10320
|
+
byPhase: {
|
|
10321
|
+
naive: 0,
|
|
10322
|
+
refactor: 0,
|
|
10323
|
+
implement: 0,
|
|
10324
|
+
other: 0
|
|
10325
|
+
},
|
|
10326
|
+
doubleWork: {
|
|
10327
|
+
naive: 0,
|
|
10328
|
+
rebuild: 0
|
|
10329
|
+
},
|
|
10330
|
+
byLens: record()
|
|
10331
|
+
});
|
|
10332
|
+
const sum = (record) => Object.values(record).reduce((a, b) => a + b, 0);
|
|
10333
|
+
const finish = (t) => ({
|
|
10334
|
+
slices: t.slices,
|
|
10335
|
+
refactorFirstRate: t.slices.decided === 0 ? null : round(t.slices.rebuilt / t.slices.decided, 3),
|
|
10336
|
+
continuations: t.continuations,
|
|
10337
|
+
cost: {
|
|
10338
|
+
total: round(sum(t.byModel)),
|
|
10339
|
+
byModel: roundAll(t.byModel),
|
|
10340
|
+
byPhase: roundAll(t.byPhase),
|
|
10341
|
+
doubleWork: {
|
|
10342
|
+
naive: round(t.doubleWork.naive),
|
|
10343
|
+
rebuild: round(t.doubleWork.rebuild),
|
|
10344
|
+
total: round(t.doubleWork.naive + t.doubleWork.rebuild)
|
|
10345
|
+
}
|
|
10346
|
+
},
|
|
10347
|
+
findings: {
|
|
10348
|
+
total: sum(t.byLens),
|
|
10349
|
+
byLens: t.byLens
|
|
10350
|
+
}
|
|
10351
|
+
});
|
|
10352
|
+
const tallyCost = (t, open, costs, phase) => {
|
|
10353
|
+
for (const { cost, model } of costs) {
|
|
10354
|
+
add(t.byModel, model ?? "unspecified", cost);
|
|
10355
|
+
t.byPhase[phase] += cost;
|
|
10356
|
+
if (phase === "naive") open.naive += cost;
|
|
10357
|
+
if (phase === "implement" && open.rebuilding) t.doubleWork.rebuild += cost;
|
|
10358
|
+
}
|
|
10359
|
+
};
|
|
10360
|
+
/** A slice 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. */
|
|
10361
|
+
const tallyDecision = (t, open, at, to) => {
|
|
10362
|
+
if (at === "implement.closing") open.rebuilding = false;
|
|
10363
|
+
if (open.applying !== void 0 && at === "implement.applying") {
|
|
10364
|
+
const naive = open.applying;
|
|
10365
|
+
open.applying = void 0;
|
|
10366
|
+
if (to !== "implement.building") return;
|
|
10367
|
+
t.slices.landed--;
|
|
10368
|
+
rebuild(t, open, naive);
|
|
10369
|
+
return;
|
|
10370
|
+
}
|
|
10371
|
+
if (!inSeam(at) || inSeam(to)) return;
|
|
10372
|
+
t.slices.decided++;
|
|
10373
|
+
const naive = open.naive;
|
|
10374
|
+
open.naive = 0;
|
|
10375
|
+
if (to === "implement.applying") {
|
|
10376
|
+
open.applying = naive;
|
|
10377
|
+
open.rebuilding = false;
|
|
10378
|
+
t.slices.landed++;
|
|
10379
|
+
return;
|
|
10380
|
+
}
|
|
10381
|
+
rebuild(t, open, naive);
|
|
10382
|
+
if (at === "seam.note") t.slices.refactored++;
|
|
10383
|
+
};
|
|
10384
|
+
const rebuild = (t, open, naive) => {
|
|
10385
|
+
open.rebuilding = true;
|
|
10386
|
+
t.slices.rebuilt++;
|
|
10387
|
+
t.doubleWork.naive += naive;
|
|
10388
|
+
};
|
|
10389
|
+
/** Oldest first; a commit gtd did not write is skipped. */
|
|
10390
|
+
const processMetrics = (commits) => {
|
|
10391
|
+
const t = emptyTally();
|
|
10392
|
+
const open = {
|
|
10393
|
+
naive: 0,
|
|
10394
|
+
rebuilding: false,
|
|
10395
|
+
applying: void 0
|
|
10396
|
+
};
|
|
10397
|
+
for (const commit of commits) {
|
|
10398
|
+
const message = parseCommitMessage(commit.message);
|
|
10399
|
+
const subject = message.parsed;
|
|
10400
|
+
if (subject === void 0) continue;
|
|
10401
|
+
const at = subject.from ?? subject.to;
|
|
10402
|
+
tallyCost(t, open, message.cost, phaseOf(at));
|
|
10403
|
+
if (subject.actor === "agent" && CONTINUE.test(at)) add(t.continuations, at.replace(CONTINUE, "$1"), 1);
|
|
10404
|
+
const lens = LENS.exec(at)?.[1];
|
|
10405
|
+
if (lens !== void 0 && commit.findings > 0) add(t.byLens, lens, commit.findings);
|
|
10406
|
+
tallyDecision(t, open, at, subject.to);
|
|
10407
|
+
}
|
|
10408
|
+
return finish(t);
|
|
10409
|
+
};
|
|
10410
|
+
const totalMetrics = (all) => {
|
|
10411
|
+
const t = emptyTally();
|
|
10412
|
+
for (const m of all) {
|
|
10413
|
+
for (const key of Object.keys(t.slices)) t.slices[key] += m.slices[key];
|
|
10414
|
+
for (const [k, v] of Object.entries(m.continuations)) add(t.continuations, k, v);
|
|
10415
|
+
for (const [k, v] of Object.entries(m.cost.byModel)) add(t.byModel, k, v);
|
|
10416
|
+
for (const key of Object.keys(t.byPhase)) t.byPhase[key] += m.cost.byPhase[key];
|
|
10417
|
+
t.doubleWork.naive += m.cost.doubleWork.naive;
|
|
10418
|
+
t.doubleWork.rebuild += m.cost.doubleWork.rebuild;
|
|
10419
|
+
for (const [k, v] of Object.entries(m.findings.byLens)) add(t.byLens, k, v);
|
|
10420
|
+
}
|
|
10421
|
+
return {
|
|
10422
|
+
processes: all.length,
|
|
10423
|
+
...finish(t)
|
|
10424
|
+
};
|
|
10425
|
+
};
|
|
10426
|
+
const chunks = (content) => content === void 0 ? 0 : (content.match(/^## /gm) ?? []).length;
|
|
10427
|
+
const readProcessMetrics = (entry) => gen(function* () {
|
|
10428
|
+
const git = yield* GitService;
|
|
10429
|
+
const base = getOrUndefined(yield* git.readRefOption(`${entry.entry}^`));
|
|
10430
|
+
const commits = yield* git.commitHistory(base, entry.tip);
|
|
10431
|
+
const quality = (hash) => git.readFileAtRef(hash, QUALITY).pipe(map$4((content) => content), orElseSucceed(() => void 0));
|
|
10432
|
+
let count = chunks(base === void 0 ? void 0 : yield* quality(base));
|
|
10433
|
+
const read = [];
|
|
10434
|
+
for (const { hash, message, touched } of commits) {
|
|
10435
|
+
const before = count;
|
|
10436
|
+
if (touched.includes(QUALITY)) count = chunks(yield* quality(hash));
|
|
10437
|
+
read.push({
|
|
10438
|
+
message,
|
|
10439
|
+
findings: Math.max(0, count - before)
|
|
10440
|
+
});
|
|
10441
|
+
}
|
|
10442
|
+
return {
|
|
10443
|
+
workflow: (commits[0] === void 0 ? void 0 : parseCommitMessage(commits[0].message).workflow) ?? null,
|
|
10444
|
+
...processMetrics(read)
|
|
10445
|
+
};
|
|
10446
|
+
});
|
|
10447
|
+
const printable = (s) => s.replace(/[\x00-\x1f\x7f-\x9f\u2028\u2029]/g, "?");
|
|
10448
|
+
const listed = (record) => Object.entries(record).map(([k, v]) => `${printable(k)} ${v}`).join(", ");
|
|
10449
|
+
const line = (label, m) => {
|
|
10450
|
+
const { slices, cost } = m;
|
|
10451
|
+
const percent = m.refactorFirstRate === null ? "-" : `${Math.round(m.refactorFirstRate * 100)}%`;
|
|
10452
|
+
const continued = sum(m.continuations);
|
|
10453
|
+
return [
|
|
10454
|
+
`${label}: ${slices.decided} slices, ${slices.rebuilt} rebuilt (refactor-first ${percent}), ${slices.refactored} refactored`,
|
|
10455
|
+
`${continued} continuations${continued > 0 ? ` (${listed(m.continuations)})` : ""}`,
|
|
10456
|
+
`cost ${cost.total} (${listed(cost.byPhase)}; by model ${listed(cost.byModel) || "-"}), double work ${cost.doubleWork.total}`,
|
|
10457
|
+
`${m.findings.total} findings${m.findings.total > 0 ? ` (${listed(m.findings.byLens)})` : ""}`
|
|
10458
|
+
].join("; ");
|
|
10459
|
+
};
|
|
10460
|
+
const runHistoryMetricsCommand = (refs, json, out) => gen(function* () {
|
|
10461
|
+
const entries = yield* historyEntries;
|
|
10462
|
+
const chosen = refs.length === 0 ? entries : (yield* forEach$2(refs, (ref) => resolveHistoryRef(ref, entries).pipe(map$4((hit) => hit.branch === ref ? entries.filter((e) => e.branch === ref) : [hit])))).flat();
|
|
10463
|
+
const processes = yield* forEach$2(chosen.filter((e, i) => chosen.findIndex((o) => o.ref === e.ref) === i), (e) => readProcessMetrics(e).pipe(map$4((metrics) => ({
|
|
10464
|
+
ref: e.ref,
|
|
10465
|
+
entry: e.entry,
|
|
10466
|
+
branch: e.branch,
|
|
10467
|
+
tip: e.tip,
|
|
10468
|
+
...metrics
|
|
10469
|
+
}))), { concurrency: 4 });
|
|
10470
|
+
const document = {
|
|
10471
|
+
processes,
|
|
10472
|
+
total: totalMetrics(processes)
|
|
10473
|
+
};
|
|
10474
|
+
if (yield* writeJson(json, out, document)) return;
|
|
10475
|
+
if (processes.length === 0) {
|
|
10476
|
+
out.write("no history refs; run gtd history fetch | sh to pull them\n");
|
|
10477
|
+
return;
|
|
10478
|
+
}
|
|
10479
|
+
const lines = processes.map((p) => line(`${p.entry.slice(0, 7)} ${printable(p.branch ?? "-")}${p.workflow === null ? "" : ` (${printable(p.workflow)})`}`, p));
|
|
10480
|
+
lines.push(line(`total (${processes.length} processes)`, document.total));
|
|
10481
|
+
out.write(`${lines.join("\n")}\n`);
|
|
10482
|
+
});
|
|
10483
|
+
//#endregion
|
|
10291
10484
|
//#region src/cli/Program.ts
|
|
10292
10485
|
const selfGtd = () => {
|
|
10293
10486
|
const script = process.argv[1];
|
|
@@ -10939,6 +11132,7 @@ const dispatchVoidCommand = (command, json, out) => {
|
|
|
10939
11132
|
case "judgeRun": return runJudgeRunCommand(command, out);
|
|
10940
11133
|
case "historyList": return runHistoryListCommand(json, out);
|
|
10941
11134
|
case "historyShow": return runHistoryShowCommand(command.ref, json, out);
|
|
11135
|
+
case "historyMetrics": return runHistoryMetricsCommand(command.refs, json, out);
|
|
10942
11136
|
case "historyFetch": return runHistoryFetchCommand(command.remote, out);
|
|
10943
11137
|
}
|
|
10944
11138
|
};
|
|
@@ -10977,18 +11171,30 @@ const share = (raw, flag) => {
|
|
|
10977
11171
|
const n = Number(raw);
|
|
10978
11172
|
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}"`);
|
|
10979
11173
|
};
|
|
11174
|
+
const JSON_KINDS = /* @__PURE__ */ new Set([
|
|
11175
|
+
"next",
|
|
11176
|
+
"land",
|
|
11177
|
+
"judge",
|
|
11178
|
+
"judgeAnswer",
|
|
11179
|
+
"doors",
|
|
11180
|
+
"slices",
|
|
11181
|
+
"historyList",
|
|
11182
|
+
"historyShow",
|
|
11183
|
+
"historyMetrics",
|
|
11184
|
+
"strays"
|
|
11185
|
+
]);
|
|
10980
11186
|
const FLAGS = [
|
|
10981
11187
|
{
|
|
10982
11188
|
name: "--json",
|
|
10983
11189
|
arity: "optional",
|
|
10984
11190
|
repeatable: false,
|
|
10985
|
-
scope: (kind) => kind
|
|
11191
|
+
scope: (kind) => JSON_KINDS.has(kind),
|
|
10986
11192
|
decode: ([raw]) => right(raw ?? ""),
|
|
10987
|
-
scopeError: "gtd: --json is only valid for `gtd next`/`gtd land`/`gtd judge`/`gtd judge answer`/`gtd doors`/`gtd slices`/`gtd history list`/`gtd history show`/`gtd strays` — every other command prints plain text; see `gtd install` for the driver protocol briefing",
|
|
11193
|
+
scopeError: "gtd: --json is only valid for `gtd next`/`gtd land`/`gtd judge`/`gtd judge answer`/`gtd doors`/`gtd slices`/`gtd history list`/`gtd history show`/`gtd history metrics`/`gtd strays` — every other command prints plain text; see `gtd install` for the driver protocol briefing",
|
|
10988
11194
|
valueHint: "<path>",
|
|
10989
11195
|
help: [
|
|
10990
11196
|
"(gtd next/gtd land/gtd judge/gtd judge answer/gtd doors/gtd slices/gtd history",
|
|
10991
|
-
"list/gtd history show/gtd strays only) output structured JSON. Bare --json prints the whole document;",
|
|
11197
|
+
"list/gtd history show/gtd history metrics/gtd strays only) output structured JSON. Bare --json prints the whole document;",
|
|
10992
11198
|
"--json=<path> (a dotted key path into that document, e.g.",
|
|
10993
11199
|
"kind, content, session.id) prints just that value: a",
|
|
10994
11200
|
"scalar raw and unquoted, a boolean as true/false, a list",
|
|
@@ -11638,6 +11844,25 @@ const COMMAND_ROWS = [
|
|
|
11638
11844
|
"prints the trail as one document; writes nothing"
|
|
11639
11845
|
]
|
|
11640
11846
|
},
|
|
11847
|
+
{
|
|
11848
|
+
token: "history metrics",
|
|
11849
|
+
kind: "historyMetrics",
|
|
11850
|
+
arity: {
|
|
11851
|
+
name: "ref",
|
|
11852
|
+
many: true
|
|
11853
|
+
},
|
|
11854
|
+
details: [
|
|
11855
|
+
"Report how the kept processes went: slices whose naive pass",
|
|
11856
|
+
"landed or was rebuilt (the refactor-first rate), continuations",
|
|
11857
|
+
"per phase, Gtd-Cost by model and phase with the double work of",
|
|
11858
|
+
"a rebuild, and quality-lap findings per lens — per process and",
|
|
11859
|
+
"in total. Each <ref> is read as by history show, but a branch",
|
|
11860
|
+
"name means every process kept from it; none means every ref.",
|
|
11861
|
+
"Reads commit subjects and trailers, never replays, so a",
|
|
11862
|
+
"changed config is no refusal. --json prints { processes, total",
|
|
11863
|
+
"} as one document; writes nothing"
|
|
11864
|
+
]
|
|
11865
|
+
},
|
|
11641
11866
|
{
|
|
11642
11867
|
token: "history fetch",
|
|
11643
11868
|
kind: "historyFetch",
|
|
@@ -11669,7 +11894,7 @@ const flagHeader = (row) => {
|
|
|
11669
11894
|
};
|
|
11670
11895
|
const renderHelp = () => {
|
|
11671
11896
|
const commandBlocks = COMMAND_ROWS.map((row) => {
|
|
11672
|
-
const header = row.arity === "none" ? row.token : "names" in row.arity ? `${row.token} <${row.arity.names[0]}> <${row.arity.names[1]}>` : "variadic" in row.arity ? `${row.token} <${row.arity.name}> [${row.arity.variadic}...]` : "optional" in row.arity ? `${row.token} [${row.arity.name}]` : `${row.token} <${row.arity.name}>`;
|
|
11897
|
+
const header = row.arity === "none" ? row.token : "names" in row.arity ? `${row.token} <${row.arity.names[0]}> <${row.arity.names[1]}>` : "variadic" in row.arity ? `${row.token} <${row.arity.name}> [${row.arity.variadic}...]` : "optional" in row.arity ? `${row.token} [${row.arity.name}]` : "many" in row.arity ? `${row.token} [${row.arity.name}...]` : `${row.token} <${row.arity.name}>`;
|
|
11673
11898
|
return renderBlock(header, row.details);
|
|
11674
11899
|
});
|
|
11675
11900
|
return `Usage: gtd [command] [options]\n\nCommands:\n${[
|
|
@@ -11777,12 +12002,14 @@ const conflictViolation = (present) => {
|
|
|
11777
12002
|
for (const other of row.conflicts ?? []) if (present.has(other)) return `gtd: ${row.name} and ${other} are mutually exclusive`;
|
|
11778
12003
|
}
|
|
11779
12004
|
};
|
|
12005
|
+
const tooMany = (cmd, rest, expected, max) => rest.length > max ? `gtd ${cmd}: too many arguments — expected ${expected}, got: ${rest.join(", ")}` : void 0;
|
|
11780
12006
|
const arityError = (cmd, rest, arity) => {
|
|
11781
|
-
if (arity === "none") return
|
|
12007
|
+
if (arity === "none") return tooMany(cmd, rest, "none", 0);
|
|
11782
12008
|
if ("names" in arity) return pairArityError(cmd, rest, arity.names);
|
|
11783
|
-
if ("
|
|
12009
|
+
if ("many" in arity) return void 0;
|
|
12010
|
+
if ("optional" in arity) return tooMany(cmd, rest, `at most one ${arity.name}`, 1);
|
|
11784
12011
|
if (rest.length === 0) return `gtd ${cmd}: missing ${arity.name} argument`;
|
|
11785
|
-
|
|
12012
|
+
return "variadic" in arity ? void 0 : tooMany(cmd, rest, `one ${arity.name}`, 1);
|
|
11786
12013
|
};
|
|
11787
12014
|
const pairArityError = (cmd, rest, [first, second]) => {
|
|
11788
12015
|
if (rest.length === 0) return `gtd ${cmd}: missing ${first} and ${second} arguments`;
|
|
@@ -11833,7 +12060,7 @@ const parseArgv = (argv) => {
|
|
|
11833
12060
|
};
|
|
11834
12061
|
const isHistory = first === "history";
|
|
11835
12062
|
const row = first === void 0 ? void 0 : commandByToken(isHistory ? `history ${positionals[1] ?? ""}` : first);
|
|
11836
|
-
if (isHistory && row === void 0) return usagePlan("gtd history: expected a subcommand — list, show <ref
|
|
12063
|
+
if (isHistory && row === void 0) return usagePlan("gtd history: expected a subcommand — list, show <ref>, metrics [ref...] or fetch [remote]", jsonSeen);
|
|
11837
12064
|
const judgeAnswer = row?.kind === "judge" && positionals[1] === "answer";
|
|
11838
12065
|
const selectsStart = workflowPresent && first === void 0;
|
|
11839
12066
|
const judgeRun = row?.kind === "judge" && positionals[1] === "run";
|
|
@@ -11966,6 +12193,15 @@ const parseArgv = (argv) => {
|
|
|
11966
12193
|
json,
|
|
11967
12194
|
verbose
|
|
11968
12195
|
};
|
|
12196
|
+
if (kind === "historyMetrics") return {
|
|
12197
|
+
kind: "command",
|
|
12198
|
+
command: {
|
|
12199
|
+
kind: "historyMetrics",
|
|
12200
|
+
refs: restPositionals
|
|
12201
|
+
},
|
|
12202
|
+
json,
|
|
12203
|
+
verbose
|
|
12204
|
+
};
|
|
11969
12205
|
if (kind === "historyFetch") return {
|
|
11970
12206
|
kind: "command",
|
|
11971
12207
|
command: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { agent, changes, glob, read, refuse, scope, start, vars } from "../flows.mjs";
|
|
2
|
-
import { Dt as registryEntry, Ot as requireFlag, S as coder, St as envDefaults, Tt as flagReferences, i as skills, n as summary, o as flagShipped, r as access, t as steering, xt as defaults } from "../chunks/reserved-
|
|
3
|
-
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-
|
|
2
|
+
import { Dt as registryEntry, Ot as requireFlag, S as coder, St as envDefaults, Tt as flagReferences, i as skills, n as summary, o as flagShipped, r as access, t as steering, xt as defaults } from "../chunks/reserved-C3PgP7XB.mjs";
|
|
3
|
+
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-Bkxki2nK.mjs";
|
|
4
4
|
//#region src/workflows/cleanup/cleanup.ts
|
|
5
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.`;
|
|
6
6
|
const cleanupFlow = async () => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { agent, answered, applyScript, changes, changesSince, check, codeChanges, env, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, matches, numeric, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restoreScript, revertScript, run, scope, slice, start, vars, wrote } from "../flows.mjs";
|
|
2
2
|
import { lensEntriesOf, review as review$1 } from "../workflow-kit.mjs";
|
|
3
|
-
import { $ as naiveApplyFailure, A as agentWithSkills, B as coverageQuestion, C as fixCheck, Ct as testFileGlobs, D as reviewMissing, E as planner, F as buildReviewTriageMessage, G as designSystem, H as criterionLine, I as builderSystem, K as designTriagePrompt, L as clearCriteriaMessage, M as buildReviewAwaitAfterDeferMessage, N as buildReviewCollectingPrompt, P as buildReviewFixNitsPrompt, Q as implementFixPrompt, R as clearCriteriaQuestion, S as coder, St as envDefaults, T as fixRisks, U as deferNotesScript, V as coveringPrompt, W as designGateAnswerMessage, X as idleMessage, Z as implementBuildingPrompt, _ as REFACTOR_NOTE, _t as startGateBlockedMessage, a as flagModeOn, at as refactorDroppedRefusal, b as SLICES, bt as unwindFailure, ct as refactorRules, d as guarded, et as naivePassPrompt, f as healthy, ft as reviewNotesCapture, g as REFACTOR, gt as slicingPrompt, h as QUALITY, ht as seamSteerMessage, i as skills, it as refactorBuildingPrompt, j as buildReviewAnswerQuestionsPrompt, k as reviewing, lt as reviewEditNotesCapture, m as HANDOFF, mt as seamByHandMessage, n as summary, nt as oversizedMessage, ot as refactorFixPrompt, p as FEEDBACK, pt as reviewerSystem, r as access, rt as qualityUnresolvedMessage, s as shipped, st as refactorNotePrompt, t as steering, tt as normaliseCriteriaScript, u as gate, ut as reviewEditsCapture, v as REQUIREMENTS, vt as uncoveredMessage, x as awaitReview, xt as defaults, y as REVIEW, yt as unwindFailedMessage, z as coverageMessage } from "../chunks/reserved-
|
|
4
|
-
import { a as refusePendingHandOff, i as isOptionalObject, n as closeReview, o as tail } from "../chunks/tail-
|
|
3
|
+
import { $ as naiveApplyFailure, A as agentWithSkills, B as coverageQuestion, C as fixCheck, Ct as testFileGlobs, D as reviewMissing, E as planner, F as buildReviewTriageMessage, G as designSystem, H as criterionLine, I as builderSystem, K as designTriagePrompt, L as clearCriteriaMessage, M as buildReviewAwaitAfterDeferMessage, N as buildReviewCollectingPrompt, P as buildReviewFixNitsPrompt, Q as implementFixPrompt, R as clearCriteriaQuestion, S as coder, St as envDefaults, T as fixRisks, U as deferNotesScript, V as coveringPrompt, W as designGateAnswerMessage, X as idleMessage, Z as implementBuildingPrompt, _ as REFACTOR_NOTE, _t as startGateBlockedMessage, a as flagModeOn, at as refactorDroppedRefusal, b as SLICES, bt as unwindFailure, ct as refactorRules, d as guarded, et as naivePassPrompt, f as healthy, ft as reviewNotesCapture, g as REFACTOR, gt as slicingPrompt, h as QUALITY, ht as seamSteerMessage, i as skills, it as refactorBuildingPrompt, j as buildReviewAnswerQuestionsPrompt, k as reviewing, lt as reviewEditNotesCapture, m as HANDOFF, mt as seamByHandMessage, n as summary, nt as oversizedMessage, ot as refactorFixPrompt, p as FEEDBACK, pt as reviewerSystem, r as access, rt as qualityUnresolvedMessage, s as shipped, st as refactorNotePrompt, t as steering, tt as normaliseCriteriaScript, u as gate, ut as reviewEditsCapture, v as REQUIREMENTS, vt as uncoveredMessage, x as awaitReview, xt as defaults, y as REVIEW, yt as unwindFailedMessage, z as coverageMessage } from "../chunks/reserved-C3PgP7XB.mjs";
|
|
4
|
+
import { a as refusePendingHandOff, i as isOptionalObject, n as closeReview, o as tail } from "../chunks/tail-Bkxki2nK.mjs";
|
|
5
5
|
import { posix } from "node:path";
|
|
6
6
|
//#region src/workflows/shared/fences.ts
|
|
7
7
|
const markFenced = (text) => {
|
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 { A as agentWithSkills, J as fixReviewNotesPrompt, S as coder, St as envDefaults, c as baseline, i as skills, n as summary, q as finisherSystem, r as access, s as shipped, t as steering, xt as defaults, y as REVIEW } from "../chunks/reserved-
|
|
4
|
-
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-
|
|
3
|
+
import { A as agentWithSkills, J as fixReviewNotesPrompt, S as coder, St as envDefaults, c as baseline, i as skills, n as summary, q as finisherSystem, r as access, s as shipped, t as steering, xt as defaults, y as REVIEW } from "../chunks/reserved-C3PgP7XB.mjs";
|
|
4
|
+
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-Bkxki2nK.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 { St as envDefaults, dt as reviewGateBlockedMessage, i as skills, n as summary, r as access, s as shipped, t as steering, u as gate, xt as defaults } from "../chunks/reserved-
|
|
3
|
-
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-
|
|
2
|
+
import { St as envDefaults, dt as reviewGateBlockedMessage, i as skills, n as summary, r as access, s as shipped, t as steering, u as gate, xt as defaults } from "../chunks/reserved-C3PgP7XB.mjs";
|
|
3
|
+
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-Bkxki2nK.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,5 +1,5 @@
|
|
|
1
1
|
import { agent, changesSince, glob, head, mark, read, refuse, removeScript, run, scope, vars } from "../flows.mjs";
|
|
2
|
-
import { Et as flipProblems, Ot as requireFlag, S as coder, St as envDefaults, i as skills, n as summary, o as flagShipped, p as FEEDBACK, r as access, t as steering, u as gate, wt as flagMentions, xt as defaults } from "../chunks/reserved-
|
|
2
|
+
import { Et as flipProblems, Ot as requireFlag, S as coder, St as envDefaults, i as skills, n as summary, o as flagShipped, p as FEEDBACK, r as access, t as steering, u as gate, wt as flagMentions, xt as defaults } from "../chunks/reserved-C3PgP7XB.mjs";
|
|
3
3
|
//#region src/workflows/rollout/rollout.ts
|
|
4
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.`;
|
|
5
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.";
|
package/package.json
CHANGED
package/src/cli/Cli.test.ts
CHANGED
|
@@ -1021,14 +1021,21 @@ describe("parseArgv — gtd history", () => {
|
|
|
1021
1021
|
expect(parse("history", "fetch", "upstream")).toMatchObject({
|
|
1022
1022
|
command: { kind: "historyFetch", remote: "upstream" },
|
|
1023
1023
|
})
|
|
1024
|
+
expect(parse("history", "metrics")).toMatchObject({
|
|
1025
|
+
command: { kind: "historyMetrics", refs: [] },
|
|
1026
|
+
})
|
|
1027
|
+
expect(parse("history", "metrics", "a", "feat/b")).toMatchObject({
|
|
1028
|
+
command: { kind: "historyMetrics", refs: ["a", "feat/b"] },
|
|
1029
|
+
})
|
|
1024
1030
|
})
|
|
1025
1031
|
|
|
1026
|
-
it("bare history and an unknown subcommand are usage errors naming list, show, fetch", () => {
|
|
1032
|
+
it("bare history and an unknown subcommand are usage errors naming list, show, metrics, fetch", () => {
|
|
1027
1033
|
for (const args of [["history"], ["history", "nope"]]) {
|
|
1028
1034
|
const message = usage(parse(...args))
|
|
1029
1035
|
expect(message).toContain("list")
|
|
1030
1036
|
expect(message).toContain("show")
|
|
1031
1037
|
expect(message).toContain("fetch")
|
|
1038
|
+
expect(message).toContain("metrics")
|
|
1032
1039
|
}
|
|
1033
1040
|
})
|
|
1034
1041
|
|
|
@@ -1039,19 +1046,23 @@ describe("parseArgv — gtd history", () => {
|
|
|
1039
1046
|
expect(usage(parse("history", "fetch", "a", "b"))).toContain("too many arguments")
|
|
1040
1047
|
})
|
|
1041
1048
|
|
|
1042
|
-
it("--json is accepted for list and
|
|
1049
|
+
it("--json is accepted for list, show and metrics, rejected for fetch", () => {
|
|
1043
1050
|
expect(parse("history", "list", "--json")).toMatchObject({ json: { kind: "document" } })
|
|
1044
1051
|
expect(parse("history", "show", "x", "--json=ended")).toMatchObject({
|
|
1045
1052
|
json: { kind: "select", path: "ended" },
|
|
1046
1053
|
})
|
|
1054
|
+
expect(parse("history", "metrics", "--json=total.refactorFirstRate")).toMatchObject({
|
|
1055
|
+
json: { kind: "select", path: "total.refactorFirstRate" },
|
|
1056
|
+
})
|
|
1047
1057
|
expect(parse("history", "fetch", "--json").kind).toBe("usage")
|
|
1048
1058
|
})
|
|
1049
1059
|
|
|
1050
|
-
it("help lists the
|
|
1060
|
+
it("help lists the four history rows", () => {
|
|
1051
1061
|
const help = renderHelp()
|
|
1052
1062
|
expect(help).toMatch(/^ {2}history list\b/m)
|
|
1053
1063
|
expect(help).toContain("history show <ref>")
|
|
1054
1064
|
expect(help).toContain("history fetch [remote]")
|
|
1065
|
+
expect(help).toContain("history metrics [ref...]")
|
|
1055
1066
|
})
|
|
1056
1067
|
})
|
|
1057
1068
|
|
|
@@ -1136,6 +1147,7 @@ describe("standaloneKinds / needsOf", () => {
|
|
|
1136
1147
|
"slices",
|
|
1137
1148
|
"historyList",
|
|
1138
1149
|
"historyShow",
|
|
1150
|
+
"historyMetrics",
|
|
1139
1151
|
"historyFetch",
|
|
1140
1152
|
] as const) {
|
|
1141
1153
|
expect(needsOf(kind)).toBe("state")
|