@pmelab/gtd 38.1.0 → 39.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 +41 -7
- package/dist/chunks/{Schema-DO5ttJaI.mjs → Schema-C6fLlQzx.mjs} +1 -1
- package/dist/chunks/{reserved-CfQ8eurM.mjs → reserved-B6tTYjeb.mjs} +23 -6
- package/dist/chunks/{tail-C7yaWvaS.mjs → tail-_CZTP7Hu.mjs} +1 -1
- package/dist/gtd.bundle.mjs +300 -52
- package/dist/testkit.mjs +265 -20
- package/dist/workflow-kit.mjs +12 -1
- 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/dist/workflows.mjs +34 -6
- package/package.json +1 -1
- package/src/cli/Cli.test.ts +15 -3
- package/src/cli/Cli.ts +62 -28
- package/src/cli/History.ts +1 -19
- package/src/cli/Metrics.test.ts +255 -0
- package/src/cli/Metrics.ts +318 -0
- package/src/cli/Output.ts +13 -0
- package/src/cli/Program.test.ts +3 -1
- package/src/cli/Program.ts +4 -0
- package/src/workflow-kit/health.ts +15 -1
- package/src/workflows/cleanup/cleanup.test.ts +2 -0
- package/src/workflows/feature/refactor.test.ts +4 -0
- package/src/workflows/rollout/rollout.feature +16 -0
- package/src/workflows/rollout/rollout.test.ts +2 -0
- package/src/workflows/shared/health.test.ts +2 -0
- package/src/workflows/shared/health.ts +34 -5
- package/src/workflows/shared/recheck.feature +60 -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,208 @@ 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
|
+
flaky: record()
|
|
10332
|
+
});
|
|
10333
|
+
const sum = (record) => Object.values(record).reduce((a, b) => a + b, 0);
|
|
10334
|
+
const finish = (t) => ({
|
|
10335
|
+
slices: t.slices,
|
|
10336
|
+
refactorFirstRate: t.slices.decided === 0 ? null : round(t.slices.rebuilt / t.slices.decided, 3),
|
|
10337
|
+
continuations: t.continuations,
|
|
10338
|
+
cost: {
|
|
10339
|
+
total: round(sum(t.byModel)),
|
|
10340
|
+
byModel: roundAll(t.byModel),
|
|
10341
|
+
byPhase: roundAll(t.byPhase),
|
|
10342
|
+
doubleWork: {
|
|
10343
|
+
naive: round(t.doubleWork.naive),
|
|
10344
|
+
rebuild: round(t.doubleWork.rebuild),
|
|
10345
|
+
total: round(t.doubleWork.naive + t.doubleWork.rebuild)
|
|
10346
|
+
}
|
|
10347
|
+
},
|
|
10348
|
+
findings: {
|
|
10349
|
+
total: sum(t.byLens),
|
|
10350
|
+
byLens: t.byLens
|
|
10351
|
+
},
|
|
10352
|
+
flaky: {
|
|
10353
|
+
total: sum(t.flaky),
|
|
10354
|
+
byStep: t.flaky
|
|
10355
|
+
}
|
|
10356
|
+
});
|
|
10357
|
+
const tallyCost = (t, open, costs, phase) => {
|
|
10358
|
+
for (const { cost, model } of costs) {
|
|
10359
|
+
add(t.byModel, model ?? "unspecified", cost);
|
|
10360
|
+
t.byPhase[phase] += cost;
|
|
10361
|
+
if (phase === "naive") open.naive += cost;
|
|
10362
|
+
if (phase === "implement" && open.rebuilding) t.doubleWork.rebuild += cost;
|
|
10363
|
+
}
|
|
10364
|
+
};
|
|
10365
|
+
/** 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. */
|
|
10366
|
+
const tallyDecision = (t, open, at, to) => {
|
|
10367
|
+
if (at === "implement.closing") open.rebuilding = false;
|
|
10368
|
+
if (open.applying !== void 0 && at === "implement.applying") {
|
|
10369
|
+
const naive = open.applying;
|
|
10370
|
+
open.applying = void 0;
|
|
10371
|
+
if (to !== "implement.building") return;
|
|
10372
|
+
t.slices.landed--;
|
|
10373
|
+
rebuild(t, open, naive);
|
|
10374
|
+
return;
|
|
10375
|
+
}
|
|
10376
|
+
if (!inSeam(at) || inSeam(to)) return;
|
|
10377
|
+
t.slices.decided++;
|
|
10378
|
+
const naive = open.naive;
|
|
10379
|
+
open.naive = 0;
|
|
10380
|
+
if (to === "implement.applying") {
|
|
10381
|
+
open.applying = naive;
|
|
10382
|
+
open.rebuilding = false;
|
|
10383
|
+
t.slices.landed++;
|
|
10384
|
+
return;
|
|
10385
|
+
}
|
|
10386
|
+
rebuild(t, open, naive);
|
|
10387
|
+
if (at === "seam.note") t.slices.refactored++;
|
|
10388
|
+
};
|
|
10389
|
+
const rebuild = (t, open, naive) => {
|
|
10390
|
+
open.rebuilding = true;
|
|
10391
|
+
t.slices.rebuilt++;
|
|
10392
|
+
t.doubleWork.naive += naive;
|
|
10393
|
+
};
|
|
10394
|
+
/** Oldest first; a commit gtd did not write is skipped. */
|
|
10395
|
+
const processMetrics = (commits) => {
|
|
10396
|
+
const t = emptyTally();
|
|
10397
|
+
const open = {
|
|
10398
|
+
naive: 0,
|
|
10399
|
+
rebuilding: false,
|
|
10400
|
+
applying: void 0
|
|
10401
|
+
};
|
|
10402
|
+
for (const commit of commits) {
|
|
10403
|
+
const message = parseCommitMessage(commit.message);
|
|
10404
|
+
const subject = message.parsed;
|
|
10405
|
+
if (subject === void 0) continue;
|
|
10406
|
+
const at = subject.from ?? subject.to;
|
|
10407
|
+
tallyCost(t, open, message.cost, phaseOf(at));
|
|
10408
|
+
if (subject.actor === "agent" && CONTINUE.test(at)) add(t.continuations, at.replace(CONTINUE, "$1"), 1);
|
|
10409
|
+
const lens = LENS.exec(at)?.[1];
|
|
10410
|
+
if (lens !== void 0 && commit.findings > 0) add(t.byLens, lens, commit.findings);
|
|
10411
|
+
if (message.marks.some((m) => m.name === "flaky")) add(t.flaky, at, 1);
|
|
10412
|
+
tallyDecision(t, open, at, subject.to);
|
|
10413
|
+
}
|
|
10414
|
+
return finish(t);
|
|
10415
|
+
};
|
|
10416
|
+
const totalMetrics = (all) => {
|
|
10417
|
+
const t = emptyTally();
|
|
10418
|
+
for (const m of all) {
|
|
10419
|
+
for (const key of Object.keys(t.slices)) t.slices[key] += m.slices[key];
|
|
10420
|
+
for (const [k, v] of Object.entries(m.continuations)) add(t.continuations, k, v);
|
|
10421
|
+
for (const [k, v] of Object.entries(m.cost.byModel)) add(t.byModel, k, v);
|
|
10422
|
+
for (const key of Object.keys(t.byPhase)) t.byPhase[key] += m.cost.byPhase[key];
|
|
10423
|
+
t.doubleWork.naive += m.cost.doubleWork.naive;
|
|
10424
|
+
t.doubleWork.rebuild += m.cost.doubleWork.rebuild;
|
|
10425
|
+
for (const [k, v] of Object.entries(m.findings.byLens)) add(t.byLens, k, v);
|
|
10426
|
+
for (const [k, v] of Object.entries(m.flaky.byStep)) add(t.flaky, k, v);
|
|
10427
|
+
}
|
|
10428
|
+
return {
|
|
10429
|
+
processes: all.length,
|
|
10430
|
+
...finish(t)
|
|
10431
|
+
};
|
|
10432
|
+
};
|
|
10433
|
+
const chunks = (content) => content === void 0 ? 0 : (content.match(/^## /gm) ?? []).length;
|
|
10434
|
+
const readProcessMetrics = (entry) => gen(function* () {
|
|
10435
|
+
const git = yield* GitService;
|
|
10436
|
+
const base = getOrUndefined(yield* git.readRefOption(`${entry.entry}^`));
|
|
10437
|
+
const commits = yield* git.commitHistory(base, entry.tip);
|
|
10438
|
+
const quality = (hash) => git.readFileAtRef(hash, QUALITY).pipe(map$4((content) => content), orElseSucceed(() => void 0));
|
|
10439
|
+
let count = chunks(base === void 0 ? void 0 : yield* quality(base));
|
|
10440
|
+
const read = [];
|
|
10441
|
+
for (const { hash, message, touched } of commits) {
|
|
10442
|
+
const before = count;
|
|
10443
|
+
if (touched.includes(QUALITY)) count = chunks(yield* quality(hash));
|
|
10444
|
+
read.push({
|
|
10445
|
+
message,
|
|
10446
|
+
findings: Math.max(0, count - before)
|
|
10447
|
+
});
|
|
10448
|
+
}
|
|
10449
|
+
return {
|
|
10450
|
+
workflow: (commits[0] === void 0 ? void 0 : parseCommitMessage(commits[0].message).workflow) ?? null,
|
|
10451
|
+
...processMetrics(read)
|
|
10452
|
+
};
|
|
10453
|
+
});
|
|
10454
|
+
const printable = (s) => s.replace(/[\x00-\x1f\x7f-\x9f\u2028\u2029]/g, "?");
|
|
10455
|
+
const listed = (record) => Object.entries(record).map(([k, v]) => `${printable(k)} ${v}`).join(", ");
|
|
10456
|
+
const line = (label, m) => {
|
|
10457
|
+
const { slices, cost } = m;
|
|
10458
|
+
const percent = m.refactorFirstRate === null ? "-" : `${Math.round(m.refactorFirstRate * 100)}%`;
|
|
10459
|
+
const continued = sum(m.continuations);
|
|
10460
|
+
return [
|
|
10461
|
+
`${label}: ${slices.decided} slices, ${slices.rebuilt} rebuilt (refactor-first ${percent}), ${slices.refactored} refactored`,
|
|
10462
|
+
`${continued} continuations${continued > 0 ? ` (${listed(m.continuations)})` : ""}`,
|
|
10463
|
+
`cost ${cost.total} (${listed(cost.byPhase)}; by model ${listed(cost.byModel) || "-"}), double work ${cost.doubleWork.total}`,
|
|
10464
|
+
`${m.findings.total} findings${m.findings.total > 0 ? ` (${listed(m.findings.byLens)})` : ""}`,
|
|
10465
|
+
`${m.flaky.total} flaky${m.flaky.total > 0 ? ` (${listed(m.flaky.byStep)})` : ""}`
|
|
10466
|
+
].join("; ");
|
|
10467
|
+
};
|
|
10468
|
+
const runHistoryMetricsCommand = (refs, json, out) => gen(function* () {
|
|
10469
|
+
const entries = yield* historyEntries;
|
|
10470
|
+
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();
|
|
10471
|
+
const processes = yield* forEach$2(chosen.filter((e, i) => chosen.findIndex((o) => o.ref === e.ref) === i), (e) => readProcessMetrics(e).pipe(map$4((metrics) => ({
|
|
10472
|
+
ref: e.ref,
|
|
10473
|
+
entry: e.entry,
|
|
10474
|
+
branch: e.branch,
|
|
10475
|
+
tip: e.tip,
|
|
10476
|
+
...metrics
|
|
10477
|
+
}))), { concurrency: 4 });
|
|
10478
|
+
const document = {
|
|
10479
|
+
processes,
|
|
10480
|
+
total: totalMetrics(processes)
|
|
10481
|
+
};
|
|
10482
|
+
if (yield* writeJson(json, out, document)) return;
|
|
10483
|
+
if (processes.length === 0) {
|
|
10484
|
+
out.write("no history refs; run gtd history fetch | sh to pull them\n");
|
|
10485
|
+
return;
|
|
10486
|
+
}
|
|
10487
|
+
const lines = processes.map((p) => line(`${p.entry.slice(0, 7)} ${printable(p.branch ?? "-")}${p.workflow === null ? "" : ` (${printable(p.workflow)})`}`, p));
|
|
10488
|
+
lines.push(line(`total (${processes.length} processes)`, document.total));
|
|
10489
|
+
out.write(`${lines.join("\n")}\n`);
|
|
10490
|
+
});
|
|
10491
|
+
//#endregion
|
|
10291
10492
|
//#region src/cli/Program.ts
|
|
10292
10493
|
const selfGtd = () => {
|
|
10293
10494
|
const script = process.argv[1];
|
|
@@ -10939,6 +11140,7 @@ const dispatchVoidCommand = (command, json, out) => {
|
|
|
10939
11140
|
case "judgeRun": return runJudgeRunCommand(command, out);
|
|
10940
11141
|
case "historyList": return runHistoryListCommand(json, out);
|
|
10941
11142
|
case "historyShow": return runHistoryShowCommand(command.ref, json, out);
|
|
11143
|
+
case "historyMetrics": return runHistoryMetricsCommand(command.refs, json, out);
|
|
10942
11144
|
case "historyFetch": return runHistoryFetchCommand(command.remote, out);
|
|
10943
11145
|
}
|
|
10944
11146
|
};
|
|
@@ -10977,18 +11179,30 @@ const share = (raw, flag) => {
|
|
|
10977
11179
|
const n = Number(raw);
|
|
10978
11180
|
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
11181
|
};
|
|
11182
|
+
const JSON_KINDS = /* @__PURE__ */ new Set([
|
|
11183
|
+
"next",
|
|
11184
|
+
"land",
|
|
11185
|
+
"judge",
|
|
11186
|
+
"judgeAnswer",
|
|
11187
|
+
"doors",
|
|
11188
|
+
"slices",
|
|
11189
|
+
"historyList",
|
|
11190
|
+
"historyShow",
|
|
11191
|
+
"historyMetrics",
|
|
11192
|
+
"strays"
|
|
11193
|
+
]);
|
|
10980
11194
|
const FLAGS = [
|
|
10981
11195
|
{
|
|
10982
11196
|
name: "--json",
|
|
10983
11197
|
arity: "optional",
|
|
10984
11198
|
repeatable: false,
|
|
10985
|
-
scope: (kind) => kind
|
|
11199
|
+
scope: (kind) => JSON_KINDS.has(kind),
|
|
10986
11200
|
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",
|
|
11201
|
+
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
11202
|
valueHint: "<path>",
|
|
10989
11203
|
help: [
|
|
10990
11204
|
"(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;",
|
|
11205
|
+
"list/gtd history show/gtd history metrics/gtd strays only) output structured JSON. Bare --json prints the whole document;",
|
|
10992
11206
|
"--json=<path> (a dotted key path into that document, e.g.",
|
|
10993
11207
|
"kind, content, session.id) prints just that value: a",
|
|
10994
11208
|
"scalar raw and unquoted, a boolean as true/false, a list",
|
|
@@ -11638,6 +11852,26 @@ const COMMAND_ROWS = [
|
|
|
11638
11852
|
"prints the trail as one document; writes nothing"
|
|
11639
11853
|
]
|
|
11640
11854
|
},
|
|
11855
|
+
{
|
|
11856
|
+
token: "history metrics",
|
|
11857
|
+
kind: "historyMetrics",
|
|
11858
|
+
arity: {
|
|
11859
|
+
name: "ref",
|
|
11860
|
+
many: true
|
|
11861
|
+
},
|
|
11862
|
+
details: [
|
|
11863
|
+
"Report how the kept processes went: slices whose naive pass",
|
|
11864
|
+
"landed or was rebuilt (the refactor-first rate), continuations",
|
|
11865
|
+
"per phase, Gtd-Cost by model and phase with the double work of",
|
|
11866
|
+
"a rebuild, quality-lap findings per lens, and red checks that",
|
|
11867
|
+
"passed on their re-check — per process and in total. Each <ref>",
|
|
11868
|
+
"is read as by history show, but a branch name means every",
|
|
11869
|
+
"process kept from it; none means every ref.",
|
|
11870
|
+
"Reads commit subjects and trailers, never replays, so a",
|
|
11871
|
+
"changed config is no refusal. --json prints { processes, total",
|
|
11872
|
+
"} as one document; writes nothing"
|
|
11873
|
+
]
|
|
11874
|
+
},
|
|
11641
11875
|
{
|
|
11642
11876
|
token: "history fetch",
|
|
11643
11877
|
kind: "historyFetch",
|
|
@@ -11669,7 +11903,7 @@ const flagHeader = (row) => {
|
|
|
11669
11903
|
};
|
|
11670
11904
|
const renderHelp = () => {
|
|
11671
11905
|
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}>`;
|
|
11906
|
+
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
11907
|
return renderBlock(header, row.details);
|
|
11674
11908
|
});
|
|
11675
11909
|
return `Usage: gtd [command] [options]\n\nCommands:\n${[
|
|
@@ -11777,12 +12011,14 @@ const conflictViolation = (present) => {
|
|
|
11777
12011
|
for (const other of row.conflicts ?? []) if (present.has(other)) return `gtd: ${row.name} and ${other} are mutually exclusive`;
|
|
11778
12012
|
}
|
|
11779
12013
|
};
|
|
12014
|
+
const tooMany = (cmd, rest, expected, max) => rest.length > max ? `gtd ${cmd}: too many arguments — expected ${expected}, got: ${rest.join(", ")}` : void 0;
|
|
11780
12015
|
const arityError = (cmd, rest, arity) => {
|
|
11781
|
-
if (arity === "none") return
|
|
12016
|
+
if (arity === "none") return tooMany(cmd, rest, "none", 0);
|
|
11782
12017
|
if ("names" in arity) return pairArityError(cmd, rest, arity.names);
|
|
11783
|
-
if ("
|
|
12018
|
+
if ("many" in arity) return void 0;
|
|
12019
|
+
if ("optional" in arity) return tooMany(cmd, rest, `at most one ${arity.name}`, 1);
|
|
11784
12020
|
if (rest.length === 0) return `gtd ${cmd}: missing ${arity.name} argument`;
|
|
11785
|
-
|
|
12021
|
+
return "variadic" in arity ? void 0 : tooMany(cmd, rest, `one ${arity.name}`, 1);
|
|
11786
12022
|
};
|
|
11787
12023
|
const pairArityError = (cmd, rest, [first, second]) => {
|
|
11788
12024
|
if (rest.length === 0) return `gtd ${cmd}: missing ${first} and ${second} arguments`;
|
|
@@ -11833,7 +12069,7 @@ const parseArgv = (argv) => {
|
|
|
11833
12069
|
};
|
|
11834
12070
|
const isHistory = first === "history";
|
|
11835
12071
|
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
|
|
12072
|
+
if (isHistory && row === void 0) return usagePlan("gtd history: expected a subcommand — list, show <ref>, metrics [ref...] or fetch [remote]", jsonSeen);
|
|
11837
12073
|
const judgeAnswer = row?.kind === "judge" && positionals[1] === "answer";
|
|
11838
12074
|
const selectsStart = workflowPresent && first === void 0;
|
|
11839
12075
|
const judgeRun = row?.kind === "judge" && positionals[1] === "run";
|
|
@@ -11966,6 +12202,15 @@ const parseArgv = (argv) => {
|
|
|
11966
12202
|
json,
|
|
11967
12203
|
verbose
|
|
11968
12204
|
};
|
|
12205
|
+
if (kind === "historyMetrics") return {
|
|
12206
|
+
kind: "command",
|
|
12207
|
+
command: {
|
|
12208
|
+
kind: "historyMetrics",
|
|
12209
|
+
refs: restPositionals
|
|
12210
|
+
},
|
|
12211
|
+
json,
|
|
12212
|
+
verbose
|
|
12213
|
+
};
|
|
11969
12214
|
if (kind === "historyFetch") return {
|
|
11970
12215
|
kind: "command",
|
|
11971
12216
|
command: {
|
package/dist/workflow-kit.mjs
CHANGED
|
@@ -17,10 +17,15 @@ const untilSettled = async (check, pending) => {
|
|
|
17
17
|
};
|
|
18
18
|
const STAMP = /\n<!-- gtd check [0-9a-f]+ -->\n?$/;
|
|
19
19
|
const stripStamp = (text) => text.replace(STAMP, "\n");
|
|
20
|
+
/** `true` when the re-check passed, else the red re-check's report — the one the escalation describes. */
|
|
21
|
+
const recheckReport = async (health) => {
|
|
22
|
+
if (health.recheck === void 0) return void 0;
|
|
23
|
+
return await health.recheck() || stripStamp(read(health.report) ?? "");
|
|
24
|
+
};
|
|
20
25
|
/**
|
|
21
26
|
* Loop until the suite is green: check, and on red run `fix` — escalating
|
|
22
27
|
* first once `fixCap` fixes are spent, or `sameFailure` calls the failure
|
|
23
|
-
* identical to the previous round's.
|
|
28
|
+
* identical to the previous round's, unless `recheck` passes.
|
|
24
29
|
*/
|
|
25
30
|
const healthy = async (fix, health) => {
|
|
26
31
|
let fixes = health.fixesSoFar ?? 0;
|
|
@@ -34,6 +39,12 @@ const healthy = async (fix, health) => {
|
|
|
34
39
|
const stuck = previous !== void 0 && await health.sameFailure(previous, current);
|
|
35
40
|
previous = current;
|
|
36
41
|
if (stuck || fixes >= health.fixCap) {
|
|
42
|
+
const rechecked = await recheckReport(health);
|
|
43
|
+
if (rechecked === true) {
|
|
44
|
+
health.escalations.rounds = 0;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
previous = rechecked ?? previous;
|
|
37
48
|
await escalation(health.escalations, health.escalation);
|
|
38
49
|
fixes = 0;
|
|
39
50
|
}
|
|
@@ -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-B6tTYjeb.mjs";
|
|
3
|
+
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-_CZTP7Hu.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-B6tTYjeb.mjs";
|
|
4
|
+
import { a as refusePendingHandOff, i as isOptionalObject, n as closeReview, o as tail } from "../chunks/tail-_CZTP7Hu.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-B6tTYjeb.mjs";
|
|
4
|
+
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-_CZTP7Hu.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-B6tTYjeb.mjs";
|
|
3
|
+
import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-_CZTP7Hu.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-B6tTYjeb.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/dist/workflows.mjs
CHANGED
|
@@ -451,10 +451,15 @@ const escalation$1 = async (count, steps) => {
|
|
|
451
451
|
};
|
|
452
452
|
const STAMP = /\n<!-- gtd check [0-9a-f]+ -->\n?$/;
|
|
453
453
|
const stripStamp = (text) => text.replace(STAMP, "\n");
|
|
454
|
+
/** `true` when the re-check passed, else the red re-check's report — the one the escalation describes. */
|
|
455
|
+
const recheckReport = async (health) => {
|
|
456
|
+
if (health.recheck === void 0) return void 0;
|
|
457
|
+
return await health.recheck() || stripStamp(read(health.report) ?? "");
|
|
458
|
+
};
|
|
454
459
|
/**
|
|
455
460
|
* Loop until the suite is green: check, and on red run `fix` — escalating
|
|
456
461
|
* first once `fixCap` fixes are spent, or `sameFailure` calls the failure
|
|
457
|
-
* identical to the previous round's.
|
|
462
|
+
* identical to the previous round's, unless `recheck` passes.
|
|
458
463
|
*/
|
|
459
464
|
const healthy$1 = async (fix, health) => {
|
|
460
465
|
let fixes = health.fixesSoFar ?? 0;
|
|
@@ -468,6 +473,12 @@ const healthy$1 = async (fix, health) => {
|
|
|
468
473
|
const stuck = previous !== void 0 && await health.sameFailure(previous, current);
|
|
469
474
|
previous = current;
|
|
470
475
|
if (stuck || fixes >= health.fixCap) {
|
|
476
|
+
const rechecked = await recheckReport(health);
|
|
477
|
+
if (rechecked === true) {
|
|
478
|
+
health.escalations.rounds = 0;
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
previous = rechecked ?? previous;
|
|
471
482
|
await escalation$1(health.escalations, health.escalation);
|
|
472
483
|
fixes = 0;
|
|
473
484
|
}
|
|
@@ -18385,6 +18396,19 @@ const baseline = (name, label = "Checking the baseline") => check(name, env.test
|
|
|
18385
18396
|
report: FEEDBACK,
|
|
18386
18397
|
label
|
|
18387
18398
|
});
|
|
18399
|
+
/**
|
|
18400
|
+
* Re-run a red check as step `name` before a person sees it. Green records
|
|
18401
|
+
* `Gtd-Mark: flaky=<red>` on the re-check's landing and resolves `true`.
|
|
18402
|
+
*/
|
|
18403
|
+
const recheck = async (name, red, options = {}) => {
|
|
18404
|
+
const green = await check(name, env.testCommand ?? "", {
|
|
18405
|
+
...options,
|
|
18406
|
+
report: FEEDBACK,
|
|
18407
|
+
label: "Re-running checks"
|
|
18408
|
+
});
|
|
18409
|
+
if (green) mark("flaky", red);
|
|
18410
|
+
return green;
|
|
18411
|
+
};
|
|
18388
18412
|
const escalationSteps = (rules) => ({
|
|
18389
18413
|
limit: 2,
|
|
18390
18414
|
describe: () => describeEscalation(rules),
|
|
@@ -18435,15 +18459,17 @@ const sameFailure = async (previous, current) => {
|
|
|
18435
18459
|
});
|
|
18436
18460
|
return comparable && truncated.length === 0 && answered(answers.verdict, "identical", numeric(vars.judgeIdenticalMinP, Infinity));
|
|
18437
18461
|
};
|
|
18462
|
+
const healthSweep = (options) => [ESCALATION, ...options.sweepOnGreen ?? []];
|
|
18438
18463
|
const runSuite = (options) => check("health.check", env.testCommand ?? "", {
|
|
18439
18464
|
report: FEEDBACK,
|
|
18440
18465
|
label: "Running checks",
|
|
18441
|
-
sweepOnGreen:
|
|
18466
|
+
sweepOnGreen: healthSweep(options)
|
|
18442
18467
|
});
|
|
18443
18468
|
/**
|
|
18444
18469
|
* Loop until the suite is green: check, and on red run `fix` — escalating
|
|
18445
18470
|
* first once `FIX_CAP` fixes are spent, or a judge calls the failure
|
|
18446
|
-
* identical to the previous round's.
|
|
18471
|
+
* identical to the previous round's. An escalation re-runs the suite once
|
|
18472
|
+
* first; a green re-run ends the loop.
|
|
18447
18473
|
*/
|
|
18448
18474
|
const healthy = (fix, options = {}) => healthy$1(guarded(fix, options.guard), {
|
|
18449
18475
|
check: () => runSuite(options),
|
|
@@ -18452,14 +18478,16 @@ const healthy = (fix, options = {}) => healthy$1(guarded(fix, options.guard), {
|
|
|
18452
18478
|
fixesSoFar: options.fixesSoFar ?? 0,
|
|
18453
18479
|
sameFailure,
|
|
18454
18480
|
escalations: options.escalations ?? { rounds: 0 },
|
|
18481
|
+
recheck: () => recheck("health.recheck", "health.check", { sweepOnGreen: healthSweep(options) }),
|
|
18455
18482
|
escalation: guardedSteps(options.guard, options.describeGuard, options.guardRests, options.rules)
|
|
18456
18483
|
});
|
|
18457
18484
|
/**
|
|
18458
|
-
* Hold the process at `<name>.blocked` until the suite is green
|
|
18485
|
+
* Hold the process at `<name>.blocked` until the suite is green, re-running a
|
|
18486
|
+
* red check once as `<name>.recheck` before resting. `guard` runs
|
|
18459
18487
|
* after each `blocked` landing only — never after a check, whose refusal would
|
|
18460
18488
|
* re-run the same script forever.
|
|
18461
18489
|
*/
|
|
18462
|
-
const gate = (name, message, guard) => gate$1(name, () => baseline("check"), guarded(() => human("blocked", {
|
|
18490
|
+
const gate = (name, message, guard) => gate$1(name, async () => await baseline("check") || recheck("recheck", `${name}.check`), guarded(() => human("blocked", {
|
|
18463
18491
|
message,
|
|
18464
18492
|
label: "Baseline is red",
|
|
18465
18493
|
file: FEEDBACK
|
|
@@ -39578,4 +39606,4 @@ var workflows_default = feature;
|
|
|
39578
39606
|
*/
|
|
39579
39607
|
const doors = {};
|
|
39580
39608
|
//#endregion
|
|
39581
|
-
export { COMMENT, ESCALATION, FEEDBACK, FIX_CAP, FlagOptions, HANDOFF, QUALITY, REFACTOR, REFACTOR_NOTE, REFACTOR_PACKAGE, REQUIREMENTS, REVIEW, REVIEW_ORIG, SLICES, ShippedOptions, acceptanceFault, access, afterTail, agentWithSkills, answerProductQuestions, answerReviewQuestions, awaitReview, base, baseline, buildFeature, buildTail, builtInLenses, cleanup, cleanupFlow, closeOut, closeReview, coder, collecting, criteriaOf, criterionIds, workflows_default as default, defaults, deferNotes, describeEscalation, design, doors, envDefaults, escalate, escalation, escalationExhausted, feature, fix, fixCheck, fixFlow, fixNits, fixQuality, fixQualityFindings, fixReviewNotes, fixRisks, gate, guarded, handOffReview, handOffTail, healthy, holdsCallSites, implement, isTestFile, judgeSeam, lensEntriesOf, lensOf, lensesOf, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, planner, qualityLap, reUnwind, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireCriteriaIfEdited, requireNoNewFlags, requireNoTestFiles, requireSlices, requireTestCallSitesOnly, requireWithin, retryQuestion, review, reviewFlow, reviewMissing, reviewQuality, reviewedRefactor, reviewing, rollout, rolloutFlow, seamBuild, skills, skipDesign, sliceCap, sliceCriteria, sliceIds, slicesOf, slicing, statedCriteria, steering, summary, tail, threads, triage, unwind };
|
|
39609
|
+
export { COMMENT, ESCALATION, FEEDBACK, FIX_CAP, FlagOptions, HANDOFF, QUALITY, REFACTOR, REFACTOR_NOTE, REFACTOR_PACKAGE, REQUIREMENTS, REVIEW, REVIEW_ORIG, SLICES, ShippedOptions, acceptanceFault, access, afterTail, agentWithSkills, answerProductQuestions, answerReviewQuestions, awaitReview, base, baseline, buildFeature, buildTail, builtInLenses, cleanup, cleanupFlow, closeOut, closeReview, coder, collecting, criteriaOf, criterionIds, workflows_default as default, defaults, deferNotes, describeEscalation, design, doors, envDefaults, escalate, escalation, escalationExhausted, feature, fix, fixCheck, fixFlow, fixNits, fixQuality, fixQualityFindings, fixReviewNotes, fixRisks, gate, guarded, handOffReview, handOffTail, healthy, holdsCallSites, implement, isTestFile, judgeSeam, lensEntriesOf, lensOf, lensesOf, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, planner, qualityLap, reUnwind, recheck, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireCriteriaIfEdited, requireNoNewFlags, requireNoTestFiles, requireSlices, requireTestCallSitesOnly, requireWithin, retryQuestion, review, reviewFlow, reviewMissing, reviewQuality, reviewedRefactor, reviewing, rollout, rolloutFlow, seamBuild, skills, skipDesign, sliceCap, sliceCriteria, sliceIds, slicesOf, slicing, statedCriteria, steering, summary, tail, threads, triage, unwind };
|