@pmelab/gtd 41.0.0 → 43.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 +107 -53
- package/claude/hooks/sketch.ts +1 -1
- package/dist/chunks/{reserved-Ch4LLrEj.mjs → reserved-COEMFEpy.mjs} +84 -51
- package/dist/chunks/{steering-DVILanVf.mjs → steering-D4LUQ-av.mjs} +95 -12
- package/dist/chunks/tail-C_xn8U7U.mjs +246 -0
- package/dist/flows.mjs +11 -1
- package/dist/gtd.bundle.mjs +498 -30
- package/dist/steering.mjs +2 -2
- package/dist/testkit.mjs +460 -28
- package/dist/web/main.js +42 -13
- package/dist/workflow-kit.mjs +61 -25
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +66 -57
- package/dist/workflows/fix.mjs +3 -3
- package/dist/workflows/review.mjs +2 -2
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +446 -141
- package/package.json +1 -1
- package/src/cli/Cli.test.ts +33 -1
- package/src/cli/Cli.ts +70 -6
- package/src/cli/History.ts +15 -0
- package/src/cli/Judges.test.ts +373 -0
- package/src/cli/Judges.ts +624 -0
- package/src/cli/Metrics.ts +3 -16
- package/src/cli/Program.test.ts +3 -1
- package/src/cli/Program.ts +9 -0
- package/src/flows/scripts.ts +15 -0
- package/src/steering/index.ts +12 -1
- package/src/steering/review.test.ts +109 -20
- package/src/steering/review.ts +160 -9
- package/src/testkit/EmittedScriptRecognizer.ts +0 -14
- package/src/testkit/FlowScripts.test.ts +17 -0
- package/src/testkit/FlowScripts.ts +15 -0
- package/src/testkit/steps.ts +31 -0
- package/src/workflow-kit/review.test.ts +170 -26
- package/src/workflow-kit/review.ts +95 -19
- package/src/workflows/feature/coverage.feature +3 -3
- package/src/workflows/feature/fast-path.feature +2 -2
- package/src/workflows/feature/feature.test.ts +2 -37
- package/src/workflows/feature/feature.ts +12 -14
- package/src/workflows/feature/flag-mode.feature +2 -2
- package/src/workflows/feature/lens-review.feature +246 -0
- package/src/workflows/feature/one-step-land.feature +2 -2
- package/src/workflows/feature/quality-unresolved.feature +47 -32
- package/src/workflows/feature/refactor-absorbs.feature +3 -3
- package/src/workflows/feature/refactor-first.feature +5 -5
- package/src/workflows/feature/refactor-rebuild.feature +7 -7
- package/src/workflows/feature/refactor-review.feature +19 -100
- package/src/workflows/feature/refactor.test.ts +12 -27
- package/src/workflows/feature/refactor.ts +0 -1
- package/src/workflows/feature/review-answers.feature +179 -0
- package/src/workflows/feature/review-gate.feature +6 -6
- package/src/workflows/feature/review.test.ts +33 -77
- package/src/workflows/feature/review.ts +79 -47
- package/src/workflows/feature/seam-rejection.feature +2 -2
- package/src/workflows/feature/seam.feature +2 -2
- package/src/workflows/feature/seam.test.ts +1 -1
- package/src/workflows/feature/slicing.feature +6 -6
- package/src/workflows/feature/steps.test.ts +0 -12
- package/src/workflows/feature/steps.ts +3 -10
- package/src/workflows/index.ts +1 -0
- package/src/workflows/shared/__fixtures__/feature-collecting-prompt.txt +6 -5
- package/src/workflows/shared/access.test.ts +2 -11
- package/src/workflows/shared/access.ts +0 -2
- package/src/workflows/shared/lenses.test.ts +39 -0
- package/src/workflows/shared/lenses.ts +176 -0
- package/src/workflows/shared/options.ts +1 -0
- package/src/workflows/shared/tail.ts +19 -4
- package/src/workflows/shared/text.test.ts +106 -88
- package/src/workflows/shared/text.ts +164 -53
- package/src/workflows/shared/vars.ts +1 -0
- package/dist/chunks/tail-lY1UmJrU.mjs +0 -112
package/dist/steering.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as headingSections, S as
|
|
2
|
-
export { BUILT_IN_MODE_NAMES, FOOTNOTE_ACTION_TITLE, FREE_TEXT_PLACEHOLDER, THREAD_REPLY_ACTION_TITLE, applySteeringEdits, formatFinding, freeFormFormat, getParseCount, headingSectionBodies, headingSections, isAnswered, openThreadFindings, openThreads, parseCodeThreads, parseFootnotes, parseThreads, parseThreadsWithFindings, reviewNotes, reviewRisks, steeringFormatFor, steeringFormatOrFreeForm, stripCodeThreads, unansweredQuestions, viewOf };
|
|
1
|
+
import { C as parseFootnotes, D as headingSectionBodies, E as getParseCount, O as headingSections, S as openThreadFindings, T as parseThreadsWithFindings, _ as unansweredNotes, a as unansweredQuestions, b as FOOTNOTE_ACTION_TITLE, c as stripCodeThreads, d as ANSWER_FIX, f as ANSWER_LEAD, g as reviewRisks, h as reviewNotes, i as steeringFormatOrFreeForm, k as formatFinding, l as freeFormFormat, m as reviewDecisions, n as openThreads, o as viewOf, p as ANSWER_WONT_FIX, r as steeringFormatFor, s as parseCodeThreads, t as BUILT_IN_MODE_NAMES, u as applySteeringEdits, v as FREE_TEXT_PLACEHOLDER, w as parseThreads, x as THREAD_REPLY_ACTION_TITLE, y as isAnswered } from "./chunks/steering-D4LUQ-av.mjs";
|
|
2
|
+
export { ANSWER_FIX, ANSWER_LEAD, ANSWER_WONT_FIX, BUILT_IN_MODE_NAMES, FOOTNOTE_ACTION_TITLE, FREE_TEXT_PLACEHOLDER, THREAD_REPLY_ACTION_TITLE, applySteeringEdits, formatFinding, freeFormFormat, getParseCount, headingSectionBodies, headingSections, isAnswered, openThreadFindings, openThreads, parseCodeThreads, parseFootnotes, parseThreads, parseThreadsWithFindings, reviewDecisions, reviewNotes, reviewRisks, steeringFormatFor, steeringFormatOrFreeForm, stripCodeThreads, unansweredNotes, unansweredQuestions, viewOf };
|
package/dist/testkit.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { applyScript, checkScript, configProblems, configure, installContext, isFactoryWorkflow, moveScript, protocolMismatch, removeScript, restoreScript, revertScript, showScript, t as flows_exports } from "./flows.mjs";
|
|
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
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
|
-
import {
|
|
3
|
+
import { D as headingSectionBodies, O as headingSections, T as parseThreadsWithFindings, a as unansweredQuestions, k as formatFinding, n as openThreads, r as steeringFormatFor, s as parseCodeThreads, t as BUILT_IN_MODE_NAMES } from "./chunks/steering-D4LUQ-av.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";
|
|
6
6
|
import * as Crypto from "node:crypto";
|
|
@@ -10253,6 +10253,13 @@ const resolveHistoryRef = (arg, entries) => {
|
|
|
10253
10253
|
if (byBranch !== void 0) return succeed$6(byBranch);
|
|
10254
10254
|
return fail$5(/* @__PURE__ */ new Error(`gtd history: no history ref matches "${arg}"`));
|
|
10255
10255
|
};
|
|
10256
|
+
/** Every kept process, or those `refs` name — a branch name meaning every process kept from it, not just its newest. */
|
|
10257
|
+
const selectHistoryEntries = (refs) => gen(function* () {
|
|
10258
|
+
const entries = yield* historyEntries;
|
|
10259
|
+
if (refs.length === 0) return entries;
|
|
10260
|
+
const chosen = (yield* forEach$2(refs, (ref) => resolveHistoryRef(ref, entries).pipe(map$4((hit) => hit.branch === ref ? entries.filter((e) => e.branch === ref) : [hit])))).flat();
|
|
10261
|
+
return chosen.filter((e, i) => chosen.findIndex((o) => o.ref === e.ref) === i);
|
|
10262
|
+
});
|
|
10256
10263
|
const seven = (hash) => hash.slice(0, 7);
|
|
10257
10264
|
const runHistoryListCommand = (json, out) => gen(function* () {
|
|
10258
10265
|
const entries = yield* historyEntries;
|
|
@@ -10288,7 +10295,7 @@ const runHistoryFetchCommand = (remote, out) => sync$2(() => {
|
|
|
10288
10295
|
});
|
|
10289
10296
|
//#endregion
|
|
10290
10297
|
//#region src/cli/Metrics.ts
|
|
10291
|
-
const QUALITY = ".gtd/QUALITY.md";
|
|
10298
|
+
const QUALITY$1 = ".gtd/QUALITY.md";
|
|
10292
10299
|
const LENS = /^((?:.*\.)?quality\.[^.]+)\.reviewing$/;
|
|
10293
10300
|
const CONTINUE = /(^|\.)continue\./;
|
|
10294
10301
|
const inSeam = (step) => step.startsWith("seam.");
|
|
@@ -10435,12 +10442,12 @@ const readProcessMetrics = (entry) => gen(function* () {
|
|
|
10435
10442
|
const git = yield* GitService;
|
|
10436
10443
|
const base = getOrUndefined(yield* git.readRefOption(`${entry.entry}^`));
|
|
10437
10444
|
const commits = yield* git.commitHistory(base, entry.tip);
|
|
10438
|
-
const quality = (hash) => git.readFileAtRef(hash, QUALITY).pipe(map$4((content) => content), orElseSucceed(() => void 0));
|
|
10445
|
+
const quality = (hash) => git.readFileAtRef(hash, QUALITY$1).pipe(map$4((content) => content), orElseSucceed(() => void 0));
|
|
10439
10446
|
let count = chunks(base === void 0 ? void 0 : yield* quality(base));
|
|
10440
10447
|
const read = [];
|
|
10441
10448
|
for (const { hash, message, touched } of commits) {
|
|
10442
10449
|
const before = count;
|
|
10443
|
-
if (touched.includes(QUALITY)) count = chunks(yield* quality(hash));
|
|
10450
|
+
if (touched.includes(QUALITY$1)) count = chunks(yield* quality(hash));
|
|
10444
10451
|
read.push({
|
|
10445
10452
|
message,
|
|
10446
10453
|
findings: Math.max(0, count - before)
|
|
@@ -10466,9 +10473,7 @@ const line = (label, m) => {
|
|
|
10466
10473
|
].join("; ");
|
|
10467
10474
|
};
|
|
10468
10475
|
const runHistoryMetricsCommand = (refs, json, out) => gen(function* () {
|
|
10469
|
-
const
|
|
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) => ({
|
|
10476
|
+
const processes = yield* forEach$2(yield* selectHistoryEntries(refs), (e) => readProcessMetrics(e).pipe(map$4((metrics) => ({
|
|
10472
10477
|
ref: e.ref,
|
|
10473
10478
|
entry: e.entry,
|
|
10474
10479
|
branch: e.branch,
|
|
@@ -10489,6 +10494,381 @@ const runHistoryMetricsCommand = (refs, json, out) => gen(function* () {
|
|
|
10489
10494
|
out.write(`${lines.join("\n")}\n`);
|
|
10490
10495
|
});
|
|
10491
10496
|
//#endregion
|
|
10497
|
+
//#region src/cli/Judges.ts
|
|
10498
|
+
const FEEDBACK = ".gtd/FEEDBACK.md";
|
|
10499
|
+
const QUALITY = ".gtd/QUALITY.md";
|
|
10500
|
+
const REVIEW = ".gtd/REVIEW.md";
|
|
10501
|
+
const REQUIREMENTS = ".gtd/REQUIREMENTS.md";
|
|
10502
|
+
const JUDGE_STEPS = [
|
|
10503
|
+
["retry", /(^|\.)health\.judge$/],
|
|
10504
|
+
["triage", /(^|\.)quality\.triage$/],
|
|
10505
|
+
["clear", /(^|\.)design\.clear$/]
|
|
10506
|
+
];
|
|
10507
|
+
const answerOf = (answer) => typeof answer === "boolean" ? answer ? "yes" : "no" : String(answer);
|
|
10508
|
+
const verdictOf = (answer, p) => ({
|
|
10509
|
+
answer: answerOf(answer),
|
|
10510
|
+
p
|
|
10511
|
+
});
|
|
10512
|
+
const THRESHOLDS = {
|
|
10513
|
+
judgeIdenticalMinP: {
|
|
10514
|
+
judge: "retry",
|
|
10515
|
+
decide: (answer, p, minP) => answered(verdictOf(answer, p), "identical", minP) ? "identical" : "retry",
|
|
10516
|
+
labels: {
|
|
10517
|
+
identical: [
|
|
10518
|
+
"escalation",
|
|
10519
|
+
"escalations",
|
|
10520
|
+
"an escalation"
|
|
10521
|
+
],
|
|
10522
|
+
retry: [
|
|
10523
|
+
"plain retry",
|
|
10524
|
+
"plain retries",
|
|
10525
|
+
"a plain retry"
|
|
10526
|
+
]
|
|
10527
|
+
}
|
|
10528
|
+
},
|
|
10529
|
+
qualityTriageMinP: {
|
|
10530
|
+
judge: "triage",
|
|
10531
|
+
decide: (answer, p, minP) => ["mechanical", "noise"].find((k) => answered(verdictOf(answer, p), k, minP)) ?? "design",
|
|
10532
|
+
labels: {
|
|
10533
|
+
mechanical: [
|
|
10534
|
+
"auto-fix",
|
|
10535
|
+
"auto-fixes",
|
|
10536
|
+
"an auto-fix"
|
|
10537
|
+
],
|
|
10538
|
+
noise: [
|
|
10539
|
+
"noise drop",
|
|
10540
|
+
"noise drops",
|
|
10541
|
+
"a noise drop"
|
|
10542
|
+
],
|
|
10543
|
+
design: [
|
|
10544
|
+
"note",
|
|
10545
|
+
"notes",
|
|
10546
|
+
"a note"
|
|
10547
|
+
]
|
|
10548
|
+
}
|
|
10549
|
+
}
|
|
10550
|
+
};
|
|
10551
|
+
const UNTITLED = "Quality findings";
|
|
10552
|
+
/** The quality lap's own numbering: prose outside every `## ` section is finding Q1. */
|
|
10553
|
+
const findingsOf = (text) => {
|
|
10554
|
+
const sections = headingSectionBodies(text);
|
|
10555
|
+
const stray = sections.reduce((rest, s) => rest.replace(s.body, "\n"), text).split("\n").filter((line) => !/^#\s/.test(line)).join("\n").trim();
|
|
10556
|
+
return [...stray === "" ? [] : [{
|
|
10557
|
+
title: UNTITLED,
|
|
10558
|
+
body: stray
|
|
10559
|
+
}], ...sections.map((s) => ({
|
|
10560
|
+
title: s.title,
|
|
10561
|
+
body: s.body.split("\n").slice(1).join("\n").trim()
|
|
10562
|
+
}))].map((f, i) => ({
|
|
10563
|
+
id: `Q${i + 1}`,
|
|
10564
|
+
...f
|
|
10565
|
+
}));
|
|
10566
|
+
};
|
|
10567
|
+
/** The criteria list, one item per line, wrapping undone: prose around it is no criterion. */
|
|
10568
|
+
const criteriaOf = (text) => {
|
|
10569
|
+
if (text === void 0) return void 0;
|
|
10570
|
+
const section = headingSectionBodies(text).find((s) => /^acceptance criteria$/i.test(s.title.trim()));
|
|
10571
|
+
if (section === void 0) return void 0;
|
|
10572
|
+
const items = [];
|
|
10573
|
+
let open = false;
|
|
10574
|
+
for (const line of section.body.split("\n").slice(1)) {
|
|
10575
|
+
const item = /^\s*[-*+]\s+(.*)$/.exec(line);
|
|
10576
|
+
if (item !== null) items.push(item[1].trim());
|
|
10577
|
+
else if (open && line.trim() !== "") items.push(`${items.pop()} ${line.trim()}`);
|
|
10578
|
+
open = item !== null || open && line.trim() !== "";
|
|
10579
|
+
}
|
|
10580
|
+
return items.map((i) => i.replace(/\s+/g, " ")).join("\n");
|
|
10581
|
+
};
|
|
10582
|
+
const criteriaChanged = (before, after) => criteriaOf(before) !== criteriaOf(after);
|
|
10583
|
+
const chunkLines = (doc, title) => headingSectionBodies(doc).find((s) => s.title === title)?.body.split("\n").slice(1).map((l) => l.trim()).filter((l) => l !== "") ?? [];
|
|
10584
|
+
const retryOutcome = (reads, i, scope) => {
|
|
10585
|
+
const isCheck = (r) => r.at === `${scope}health.check` || r.at === `${scope}health.recheck`;
|
|
10586
|
+
const after = reads.slice(i + 1);
|
|
10587
|
+
const fix = after.findIndex((r) => isCheck(r) && r.commit.feedback === void 0 || r.actor === "agent" && !r.at.startsWith(`${scope}health.`));
|
|
10588
|
+
if (fix === -1 || after[fix].actor !== "agent") return null;
|
|
10589
|
+
const check = after.findIndex((r, k) => k > fix && r.at === `${scope}health.check`);
|
|
10590
|
+
if (check === -1) return null;
|
|
10591
|
+
if (after.slice(0, check).some((r) => r.actor === "human" && r.commit.code === true)) return null;
|
|
10592
|
+
return after[check].commit.feedback === void 0 ? "overturned" : "held";
|
|
10593
|
+
};
|
|
10594
|
+
const RESHOWN = {
|
|
10595
|
+
mechanical: "Fixed automatically",
|
|
10596
|
+
noise: "Dropped as noise"
|
|
10597
|
+
};
|
|
10598
|
+
const isItem = (line) => line.startsWith("- ");
|
|
10599
|
+
/** The line at `at` and the note lines under it, up to the next list item. */
|
|
10600
|
+
const block = (lines, at) => {
|
|
10601
|
+
const next = lines.findIndex((l, k) => k > at && isItem(l));
|
|
10602
|
+
return lines.slice(at, next === -1 ? void 0 : next).join("\n");
|
|
10603
|
+
};
|
|
10604
|
+
/** Whether the review edited `title`'s line in `chunk`, or wrote under it; null when the review did not list it there. */
|
|
10605
|
+
const reviewedLine = (review, chunk, title) => {
|
|
10606
|
+
const before = chunkLines(review.before, chunk);
|
|
10607
|
+
const at = before.findIndex((l) => l.startsWith(`- ${title} (`));
|
|
10608
|
+
if (at === -1) return null;
|
|
10609
|
+
const after = chunkLines(review.after, chunk);
|
|
10610
|
+
const kept = after.indexOf(before[at]);
|
|
10611
|
+
return (kept === -1 ? after.some((l) => !before.includes(l) && l.startsWith(`- ${title}`) && !before.some((o, k) => k !== at && o.startsWith(`- ${title}`) && l.startsWith(o.split(" (")[0]))) : block(after, kept) !== block(before, at)) ? "overturned" : "held";
|
|
10612
|
+
};
|
|
10613
|
+
const triageOutcome = (reads, i, answer, finding) => {
|
|
10614
|
+
const chunk = RESHOWN[answer];
|
|
10615
|
+
if (chunk === void 0 || finding === void 0) return null;
|
|
10616
|
+
const next = reads.slice(i + 1).find((r) => judgeOf(r.at) === "triage" || r.actor === "human" && r.commit.review !== void 0);
|
|
10617
|
+
const review = next?.actor === "human" ? next.commit.review : void 0;
|
|
10618
|
+
return review === void 0 ? null : reviewedLine(review, chunk, finding.title);
|
|
10619
|
+
};
|
|
10620
|
+
const CLEAR_MIN_P = .8;
|
|
10621
|
+
const clearOutcome = (reads, i, v) => {
|
|
10622
|
+
if (!answered(verdictOf(v.answer, v.p), "yes", CLEAR_MIN_P)) return null;
|
|
10623
|
+
if (!reads[i].to.endsWith("design.normalise")) return null;
|
|
10624
|
+
return reads.slice(i + 1).some((r) => r.actor === "human" && /(^|\.)design\./.test(r.at) && r.commit.criteriaChanged === true) ? "overturned" : "held";
|
|
10625
|
+
};
|
|
10626
|
+
const judgeOf = (step) => JUDGE_STEPS.find(([, pattern]) => pattern.test(step))?.[0];
|
|
10627
|
+
const reportsBefore = (reads, i, scope) => reads.slice(0, i).filter((r) => (r.at === `${scope}health.check` || r.at === `${scope}health.recheck`) && r.commit.feedback !== void 0).map((r) => r.commit.feedback);
|
|
10628
|
+
/** Oldest first: every verdict of a calibrated judge, with its outcome. */
|
|
10629
|
+
const processJudgments = (commits) => {
|
|
10630
|
+
const reads = commits.map((commit) => {
|
|
10631
|
+
const message = parseCommitMessage(commit.message);
|
|
10632
|
+
const subject = message.parsed;
|
|
10633
|
+
return {
|
|
10634
|
+
commit,
|
|
10635
|
+
actor: subject?.actor,
|
|
10636
|
+
at: subject === void 0 ? "" : subject.from ?? subject.to,
|
|
10637
|
+
to: subject?.to ?? "",
|
|
10638
|
+
judge: message.judge
|
|
10639
|
+
};
|
|
10640
|
+
});
|
|
10641
|
+
const sketch = commits[0]?.sketch;
|
|
10642
|
+
return reads.flatMap((r, i) => {
|
|
10643
|
+
const judge = judgeOf(r.at);
|
|
10644
|
+
if (judge === void 0) return [];
|
|
10645
|
+
const scope = r.at.slice(0, r.at.length - r.at.split(".").slice(-2).join(".").length);
|
|
10646
|
+
const at = {
|
|
10647
|
+
reads,
|
|
10648
|
+
i,
|
|
10649
|
+
scope,
|
|
10650
|
+
sketch
|
|
10651
|
+
};
|
|
10652
|
+
return r.judge.map((v) => ({
|
|
10653
|
+
judge,
|
|
10654
|
+
commit: r.commit.hash,
|
|
10655
|
+
step: r.at,
|
|
10656
|
+
question: v.id,
|
|
10657
|
+
...JUDGES[judge](at, v),
|
|
10658
|
+
answer: v.answer,
|
|
10659
|
+
p: v.p
|
|
10660
|
+
}));
|
|
10661
|
+
});
|
|
10662
|
+
};
|
|
10663
|
+
const NOT_COMPARABLE = { tailsNotComparable: "true" };
|
|
10664
|
+
const field = (key, value) => value === void 0 ? {} : { [key]: value };
|
|
10665
|
+
const JUDGES = {
|
|
10666
|
+
retry: ({ reads, i, scope }, v) => {
|
|
10667
|
+
const reports = reportsBefore(reads, i, scope);
|
|
10668
|
+
const previous = reports.at(-2);
|
|
10669
|
+
const current = reports.at(-1);
|
|
10670
|
+
const comparable = previous?.trim() !== "" && current?.trim() !== "";
|
|
10671
|
+
const known = previous !== void 0 && current !== void 0;
|
|
10672
|
+
return {
|
|
10673
|
+
state: comparable ? {
|
|
10674
|
+
...field("previous", previous),
|
|
10675
|
+
...field("current", current)
|
|
10676
|
+
} : NOT_COMPARABLE,
|
|
10677
|
+
outcome: known && comparable && answerOf(v.answer) === "identical" ? retryOutcome(reads, i, scope) : null
|
|
10678
|
+
};
|
|
10679
|
+
},
|
|
10680
|
+
triage: ({ reads, i }, v) => {
|
|
10681
|
+
const finding = reads[i].commit.findings?.find((f) => f.id === v.id);
|
|
10682
|
+
return {
|
|
10683
|
+
state: field(v.id, finding && `## ${finding.title}\n\n${finding.body}`),
|
|
10684
|
+
outcome: triageOutcome(reads, i, answerOf(v.answer), finding)
|
|
10685
|
+
};
|
|
10686
|
+
},
|
|
10687
|
+
clear: ({ reads, i, sketch }, v) => ({
|
|
10688
|
+
state: field("sketch", sketch),
|
|
10689
|
+
outcome: clearOutcome(reads, i, v)
|
|
10690
|
+
})
|
|
10691
|
+
};
|
|
10692
|
+
const rate = (overturned, scored) => Math.round(overturned / scored * 1e3) / 1e3;
|
|
10693
|
+
const bucketOf = (p) => Math.min(9, Math.max(0, Math.floor(p * 10))) / 10;
|
|
10694
|
+
const calibrate = (judgments) => {
|
|
10695
|
+
const of = (judge) => {
|
|
10696
|
+
const all = judgments.filter((j) => j.judge === judge);
|
|
10697
|
+
const scored = all.filter((j) => j.outcome !== null);
|
|
10698
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
10699
|
+
for (const j of scored) {
|
|
10700
|
+
const b = buckets.get(bucketOf(j.p)) ?? {
|
|
10701
|
+
scored: 0,
|
|
10702
|
+
overturned: 0
|
|
10703
|
+
};
|
|
10704
|
+
b.scored++;
|
|
10705
|
+
if (j.outcome === "overturned") b.overturned++;
|
|
10706
|
+
buckets.set(bucketOf(j.p), b);
|
|
10707
|
+
}
|
|
10708
|
+
const overturned = scored.filter((j) => j.outcome === "overturned").length;
|
|
10709
|
+
return {
|
|
10710
|
+
verdicts: all.length,
|
|
10711
|
+
scored: scored.length,
|
|
10712
|
+
overturned,
|
|
10713
|
+
buckets: [...buckets.entries()].sort(([a], [b]) => a - b).map(([p, b]) => ({
|
|
10714
|
+
p,
|
|
10715
|
+
...b,
|
|
10716
|
+
rate: rate(b.overturned, b.scored)
|
|
10717
|
+
}))
|
|
10718
|
+
};
|
|
10719
|
+
};
|
|
10720
|
+
return {
|
|
10721
|
+
retry: of("retry"),
|
|
10722
|
+
triage: of("triage"),
|
|
10723
|
+
clear: of("clear")
|
|
10724
|
+
};
|
|
10725
|
+
};
|
|
10726
|
+
/** Every verdict of `threshold`'s judge as `[decided then, decided under value]`. */
|
|
10727
|
+
const redecided = (processes, name, value) => {
|
|
10728
|
+
const threshold = THRESHOLDS[name];
|
|
10729
|
+
const now = numeric(value, Infinity);
|
|
10730
|
+
return processes.filter(({ vars }) => Object.hasOwn(vars, name)).flatMap(({ vars, judgments }) => judgments.filter((j) => j.judge === threshold.judge && j.state !== NOT_COMPARABLE).map((j) => {
|
|
10731
|
+
const was = numeric(vars[name], Infinity);
|
|
10732
|
+
return [
|
|
10733
|
+
threshold.decide(j.answer, j.p, was),
|
|
10734
|
+
threshold.decide(j.answer, j.p, now),
|
|
10735
|
+
j
|
|
10736
|
+
];
|
|
10737
|
+
}));
|
|
10738
|
+
};
|
|
10739
|
+
const whatIf = (processes, name, value) => {
|
|
10740
|
+
const changes = /* @__PURE__ */ new Map();
|
|
10741
|
+
for (const [from, to, j] of redecided(processes, name, value)) {
|
|
10742
|
+
if (from === to) continue;
|
|
10743
|
+
const c = changes.get(`${from} ${to}`) ?? {
|
|
10744
|
+
from,
|
|
10745
|
+
to,
|
|
10746
|
+
count: 0,
|
|
10747
|
+
overturned: 0
|
|
10748
|
+
};
|
|
10749
|
+
changes.set(`${from} ${to}`, {
|
|
10750
|
+
...c,
|
|
10751
|
+
count: c.count + 1,
|
|
10752
|
+
overturned: c.overturned + (j.outcome === "overturned" ? 1 : 0)
|
|
10753
|
+
});
|
|
10754
|
+
}
|
|
10755
|
+
return {
|
|
10756
|
+
var: name,
|
|
10757
|
+
value,
|
|
10758
|
+
judge: THRESHOLDS[name].judge,
|
|
10759
|
+
changes: [...changes.values()]
|
|
10760
|
+
};
|
|
10761
|
+
};
|
|
10762
|
+
const changeLine = (labels, c) => {
|
|
10763
|
+
const [one, many] = labels[c.from];
|
|
10764
|
+
const [, toMany, toOne] = labels[c.to];
|
|
10765
|
+
const what = c.count === 1 ? `1 ${one} becomes ${toOne}` : `${c.count} ${many} become ${toMany}`;
|
|
10766
|
+
return c.overturned > 0 ? `${what} (${c.overturned} overturned)` : what;
|
|
10767
|
+
};
|
|
10768
|
+
const reportFact = (at, c, from) => /(^|\.)health\.(re)?check$/.test(from) && c.touched.includes(FEEDBACK) ? at(c.hash, FEEDBACK).pipe(map$4((r) => r === void 0 ? {} : { feedback: stripStamp(r) })) : succeed$6({});
|
|
10769
|
+
const findingsFact = (at, parent, from) => judgeOf(from) === "triage" ? at(parent, QUALITY).pipe(map$4((q) => ({ findings: findingsOf(q ?? "") }))) : succeed$6({});
|
|
10770
|
+
const reviewFact = (at, c, parent, human) => human && c.touched.includes(REVIEW) ? all$1([at(parent, REVIEW), at(c.hash, REVIEW)]).pipe(map$4(([before, after]) => ({ review: {
|
|
10771
|
+
before: before ?? "",
|
|
10772
|
+
after: after ?? ""
|
|
10773
|
+
} }))) : succeed$6({});
|
|
10774
|
+
const criteriaFact = (at, c, parent, human) => human && c.touched.includes(REQUIREMENTS) ? all$1([at(parent, REQUIREMENTS), at(c.hash, REQUIREMENTS)]).pipe(map$4(([before, after]) => ({ criteriaChanged: criteriaChanged(before, after) }))) : succeed$6({});
|
|
10775
|
+
const sketchFact = (at, c, opening) => {
|
|
10776
|
+
const todo = c.touched.filter((p) => /(^|\/)TODO\.md$/.test(p));
|
|
10777
|
+
return opening && todo.length === 1 ? at(c.hash, todo[0]).pipe(map$4((s) => s === void 0 ? {} : { sketch: s })) : succeed$6({});
|
|
10778
|
+
};
|
|
10779
|
+
/** Only the files a verdict's outcome or state reads, and only at the commits that can matter. */
|
|
10780
|
+
const commitFacts = (at, c, parent, opening) => {
|
|
10781
|
+
const step = parseCommitMessage(c.message).parsed;
|
|
10782
|
+
const from = step === void 0 ? "" : step.from ?? step.to;
|
|
10783
|
+
return all$1([
|
|
10784
|
+
reportFact(at, c, from),
|
|
10785
|
+
findingsFact(at, parent, from),
|
|
10786
|
+
reviewFact(at, c, parent, step?.actor === "human"),
|
|
10787
|
+
criteriaFact(at, c, parent, step?.actor === "human"),
|
|
10788
|
+
sketchFact(at, c, opening)
|
|
10789
|
+
]).pipe(map$4((facts) => Object.assign({
|
|
10790
|
+
hash: c.hash,
|
|
10791
|
+
message: c.message
|
|
10792
|
+
}, c.touched.some((p) => p !== ".gtd" && !p.startsWith(".gtd/")) ? { code: true } : {}, ...facts)));
|
|
10793
|
+
};
|
|
10794
|
+
const readProcessJudgments = (entry) => gen(function* () {
|
|
10795
|
+
const git = yield* GitService;
|
|
10796
|
+
const base = getOrUndefined(yield* git.readRefOption(`${entry.entry}^`));
|
|
10797
|
+
const commits = yield* git.commitHistory(base, entry.tip);
|
|
10798
|
+
const vars = commits[0] === void 0 ? {} : parseCommitMessage(commits[0].message).vars;
|
|
10799
|
+
if (!commits.some((c) => {
|
|
10800
|
+
const m = parseCommitMessage(c.message);
|
|
10801
|
+
return m.judge.length > 0 && judgeOf(m.parsed?.from ?? m.parsed?.to ?? "") !== void 0;
|
|
10802
|
+
})) return {
|
|
10803
|
+
vars,
|
|
10804
|
+
judgments: []
|
|
10805
|
+
};
|
|
10806
|
+
const at = (ref, path) => ref === void 0 ? succeed$6(void 0) : git.readFileAtRef(ref, path).pipe(map$4((content) => content), orElseSucceed(() => void 0));
|
|
10807
|
+
const read = yield* forEach$2(commits, (commit, i) => commitFacts(at, commit, i === 0 ? base : commits[i - 1].hash, i === 0));
|
|
10808
|
+
return {
|
|
10809
|
+
vars,
|
|
10810
|
+
judgments: processJudgments(read)
|
|
10811
|
+
};
|
|
10812
|
+
});
|
|
10813
|
+
const stripStamp = (text) => text.replace(/\n<!-- gtd check [0-9a-f]+ -->\n?$/, "\n");
|
|
10814
|
+
const NAMES = [
|
|
10815
|
+
"retry",
|
|
10816
|
+
"triage",
|
|
10817
|
+
"clear"
|
|
10818
|
+
];
|
|
10819
|
+
const calibrationLine = (name, c) => {
|
|
10820
|
+
const buckets = c.buckets.map((b) => `${b.p.toFixed(1)}-${(b.p + .1).toFixed(1)}: ${b.overturned}/${b.scored}`).join(", ");
|
|
10821
|
+
return `${name}: ${c.verdicts} verdicts, ${c.scored} scored, ${c.overturned} overturned${buckets === "" ? "" : ` (${buckets})`}`;
|
|
10822
|
+
};
|
|
10823
|
+
const runHistoryJudgesCommand = (refs, vars, jsonl, json, out) => gen(function* () {
|
|
10824
|
+
const processes = yield* forEach$2(yield* selectHistoryEntries(refs), (e) => readProcessJudgments(e).pipe(map$4((p) => ({
|
|
10825
|
+
...p,
|
|
10826
|
+
entry: e.entry,
|
|
10827
|
+
ref: e.ref,
|
|
10828
|
+
branch: e.branch
|
|
10829
|
+
}))), { concurrency: 4 });
|
|
10830
|
+
if (jsonl) {
|
|
10831
|
+
for (const p of processes) for (const { judge, commit, step, question, state, answer, p: pr, outcome } of p.judgments) out.write(`${JSON.stringify({
|
|
10832
|
+
process: p.entry,
|
|
10833
|
+
judge,
|
|
10834
|
+
commit,
|
|
10835
|
+
step,
|
|
10836
|
+
question,
|
|
10837
|
+
state,
|
|
10838
|
+
answer,
|
|
10839
|
+
p: pr,
|
|
10840
|
+
outcome
|
|
10841
|
+
})}\n`);
|
|
10842
|
+
return;
|
|
10843
|
+
}
|
|
10844
|
+
const judges = calibrate(processes.flatMap((p) => p.judgments));
|
|
10845
|
+
const whatIfs = Object.entries(vars).map(([name, value]) => whatIf(processes, name, value));
|
|
10846
|
+
if (yield* writeJson(json, out, {
|
|
10847
|
+
processes: processes.map((p) => ({
|
|
10848
|
+
ref: p.ref,
|
|
10849
|
+
entry: p.entry,
|
|
10850
|
+
branch: p.branch,
|
|
10851
|
+
judges: calibrate(p.judgments)
|
|
10852
|
+
})),
|
|
10853
|
+
total: {
|
|
10854
|
+
processes: processes.length,
|
|
10855
|
+
judges
|
|
10856
|
+
},
|
|
10857
|
+
whatIf: whatIfs
|
|
10858
|
+
})) return;
|
|
10859
|
+
if (processes.length === 0) {
|
|
10860
|
+
out.write("no history refs; run gtd history fetch | sh to pull them\n");
|
|
10861
|
+
return;
|
|
10862
|
+
}
|
|
10863
|
+
const lines = NAMES.map((name) => calibrationLine(name, judges[name]));
|
|
10864
|
+
for (const w of whatIfs) {
|
|
10865
|
+
const labels = THRESHOLDS[w.var].labels;
|
|
10866
|
+
const changes = w.changes.map((c) => changeLine(labels, c)).join(", ");
|
|
10867
|
+
lines.push(`what-if ${w.var}=${printable(w.value)}: ${changes || "no decision changes"}`);
|
|
10868
|
+
}
|
|
10869
|
+
out.write(`${lines.join("\n")}\n`);
|
|
10870
|
+
});
|
|
10871
|
+
//#endregion
|
|
10492
10872
|
//#region src/cli/Program.ts
|
|
10493
10873
|
const selfGtd = () => {
|
|
10494
10874
|
const script = process.argv[1];
|
|
@@ -11141,6 +11521,7 @@ const dispatchVoidCommand = (command, json, out) => {
|
|
|
11141
11521
|
case "historyList": return runHistoryListCommand(json, out);
|
|
11142
11522
|
case "historyShow": return runHistoryShowCommand(command.ref, json, out);
|
|
11143
11523
|
case "historyMetrics": return runHistoryMetricsCommand(command.refs, json, out);
|
|
11524
|
+
case "historyJudges": return runHistoryJudgesCommand(command.refs, command.vars, command.jsonl, json, out);
|
|
11144
11525
|
case "historyFetch": return runHistoryFetchCommand(command.remote, out);
|
|
11145
11526
|
}
|
|
11146
11527
|
};
|
|
@@ -11189,6 +11570,7 @@ const JSON_KINDS = /* @__PURE__ */ new Set([
|
|
|
11189
11570
|
"historyList",
|
|
11190
11571
|
"historyShow",
|
|
11191
11572
|
"historyMetrics",
|
|
11573
|
+
"historyJudges",
|
|
11192
11574
|
"strays"
|
|
11193
11575
|
]);
|
|
11194
11576
|
const FLAGS = [
|
|
@@ -11198,11 +11580,11 @@ const FLAGS = [
|
|
|
11198
11580
|
repeatable: false,
|
|
11199
11581
|
scope: (kind) => JSON_KINDS.has(kind),
|
|
11200
11582
|
decode: ([raw]) => right(raw ?? ""),
|
|
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",
|
|
11583
|
+
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 history judges`/`gtd strays` — every other command prints plain text; see `gtd install` for the driver protocol briefing",
|
|
11202
11584
|
valueHint: "<path>",
|
|
11203
11585
|
help: [
|
|
11204
11586
|
"(gtd next/gtd land/gtd judge/gtd judge answer/gtd doors/gtd slices/gtd history",
|
|
11205
|
-
"list/gtd history show/gtd history metrics/gtd strays only) output structured JSON. Bare --json prints the whole document;",
|
|
11587
|
+
"list/gtd history show/gtd history metrics/gtd history judges/gtd strays only) output structured JSON. Bare --json prints the whole document;",
|
|
11206
11588
|
"--json=<path> (a dotted key path into that document, e.g.",
|
|
11207
11589
|
"kind, content, session.id) prints just that value: a",
|
|
11208
11590
|
"scalar raw and unquoted, a boolean as true/false, a list",
|
|
@@ -11380,7 +11762,7 @@ const FLAGS = [
|
|
|
11380
11762
|
name: "--var",
|
|
11381
11763
|
arity: 1,
|
|
11382
11764
|
repeatable: true,
|
|
11383
|
-
scope: (kind) => kind === "start",
|
|
11765
|
+
scope: (kind) => kind === "start" || kind === "historyJudges",
|
|
11384
11766
|
decode: (raws) => {
|
|
11385
11767
|
const vars = {};
|
|
11386
11768
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -11403,15 +11785,28 @@ const FLAGS = [
|
|
|
11403
11785
|
}
|
|
11404
11786
|
return right(vars);
|
|
11405
11787
|
},
|
|
11406
|
-
scopeError: "gtd: --var requires --workflow",
|
|
11788
|
+
scopeError: "gtd: --var requires --workflow (or `gtd history judges`)",
|
|
11407
11789
|
valueHint: "<name>=<value>",
|
|
11408
11790
|
help: [
|
|
11409
11791
|
"(with --workflow; repeatable) pin a process setting for the",
|
|
11410
11792
|
"new process; the name must already be declared by the",
|
|
11411
11793
|
"workflow's defaults (environment settings are not",
|
|
11412
|
-
"pinnable)"
|
|
11794
|
+
"pinnable). (gtd history judges) re-decide past verdicts",
|
|
11795
|
+
"under this threshold: judgeIdenticalMinP or",
|
|
11796
|
+
"qualityTriageMinP"
|
|
11413
11797
|
]
|
|
11414
11798
|
},
|
|
11799
|
+
{
|
|
11800
|
+
name: "--jsonl",
|
|
11801
|
+
arity: 0,
|
|
11802
|
+
repeatable: false,
|
|
11803
|
+
scope: (kind) => kind === "historyJudges",
|
|
11804
|
+
decode: () => right(true),
|
|
11805
|
+
scopeError: "gtd: --jsonl is only valid for `gtd history judges`",
|
|
11806
|
+
valueHint: "",
|
|
11807
|
+
help: ["(gtd history judges only) print one JSON line per verdict", "instead of the report"],
|
|
11808
|
+
conflicts: ["--json", "--var"]
|
|
11809
|
+
},
|
|
11415
11810
|
{
|
|
11416
11811
|
name: "--open-questions",
|
|
11417
11812
|
arity: 0,
|
|
@@ -11872,6 +12267,26 @@ const COMMAND_ROWS = [
|
|
|
11872
12267
|
"} as one document; writes nothing"
|
|
11873
12268
|
]
|
|
11874
12269
|
},
|
|
12270
|
+
{
|
|
12271
|
+
token: "history judges",
|
|
12272
|
+
kind: "historyJudges",
|
|
12273
|
+
arity: {
|
|
12274
|
+
name: "ref",
|
|
12275
|
+
many: true
|
|
12276
|
+
},
|
|
12277
|
+
details: [
|
|
12278
|
+
"Calibrate the judges of the kept processes from their",
|
|
12279
|
+
"recorded Gtd-Judge verdicts: per judge (retry, triage, clear)",
|
|
12280
|
+
"and p bucket, how many verdicts later events overturned.",
|
|
12281
|
+
"--var <name>=<value> re-decides every past verdict under",
|
|
12282
|
+
"another threshold and prints what would have changed;",
|
|
12283
|
+
"--jsonl prints one { process, judge, commit, step, question,",
|
|
12284
|
+
"state, answer, p, outcome } line per verdict instead. <ref>",
|
|
12285
|
+
"is read as by history metrics. Never replays and asks no",
|
|
12286
|
+
"model. --json prints { processes, total, whatIf }; writes",
|
|
12287
|
+
"nothing"
|
|
12288
|
+
]
|
|
12289
|
+
},
|
|
11875
12290
|
{
|
|
11876
12291
|
token: "history fetch",
|
|
11877
12292
|
kind: "historyFetch",
|
|
@@ -12069,7 +12484,7 @@ const parseArgv = (argv) => {
|
|
|
12069
12484
|
};
|
|
12070
12485
|
const isHistory = first === "history";
|
|
12071
12486
|
const row = first === void 0 ? void 0 : commandByToken(isHistory ? `history ${positionals[1] ?? ""}` : first);
|
|
12072
|
-
if (isHistory && row === void 0) return usagePlan("gtd history: expected a subcommand — list, show <ref>, metrics [ref...] or fetch [remote]", jsonSeen);
|
|
12487
|
+
if (isHistory && row === void 0) return usagePlan("gtd history: expected a subcommand — list, show <ref>, metrics [ref...], judges [ref...] or fetch [remote]", jsonSeen);
|
|
12073
12488
|
const judgeAnswer = row?.kind === "judge" && positionals[1] === "answer";
|
|
12074
12489
|
const selectsStart = workflowPresent && first === void 0;
|
|
12075
12490
|
const judgeRun = row?.kind === "judge" && positionals[1] === "run";
|
|
@@ -12211,6 +12626,24 @@ const parseArgv = (argv) => {
|
|
|
12211
12626
|
json,
|
|
12212
12627
|
verbose
|
|
12213
12628
|
};
|
|
12629
|
+
if (kind === "historyJudges") {
|
|
12630
|
+
const vars = bag["--var"] ?? {};
|
|
12631
|
+
const unknown = Object.keys(vars).find((name) => !Object.hasOwn(THRESHOLDS, name));
|
|
12632
|
+
if (unknown !== void 0) return usagePlan(`gtd history judges: --var ${unknown} is no judge threshold — use ${Object.keys(THRESHOLDS).join(" or ")}`, jsonSeen);
|
|
12633
|
+
const nan = Object.entries(vars).find(([, value]) => value.trim() === "" || !Number.isFinite(Number(value)));
|
|
12634
|
+
if (nan !== void 0) return usagePlan(`gtd history judges: --var ${nan[0]} must be a number — got "${nan[1]}"`, jsonSeen);
|
|
12635
|
+
return {
|
|
12636
|
+
kind: "command",
|
|
12637
|
+
command: {
|
|
12638
|
+
kind: "historyJudges",
|
|
12639
|
+
refs: restPositionals,
|
|
12640
|
+
vars,
|
|
12641
|
+
jsonl: bag["--jsonl"] ?? false
|
|
12642
|
+
},
|
|
12643
|
+
json,
|
|
12644
|
+
verbose
|
|
12645
|
+
};
|
|
12646
|
+
}
|
|
12214
12647
|
if (kind === "historyFetch") return {
|
|
12215
12648
|
kind: "command",
|
|
12216
12649
|
command: {
|
|
@@ -13068,6 +13501,18 @@ const recognizeMove = (repo, script) => {
|
|
|
13068
13501
|
repo.writeFile(to, content);
|
|
13069
13502
|
return ok();
|
|
13070
13503
|
};
|
|
13504
|
+
const recognizeWrite = (repo, script) => {
|
|
13505
|
+
const lines = shellLines(script.split("\n"));
|
|
13506
|
+
if (!lines[3]?.startsWith("printf '%s' ")) return void 0;
|
|
13507
|
+
const [path] = words$1(lines[2] ?? "");
|
|
13508
|
+
const [, content, to] = words$1(lines[3]);
|
|
13509
|
+
const remove = words$1(lines.find((l) => l.startsWith("rm -rf -- ")) ?? "");
|
|
13510
|
+
if (path === void 0 || content === void 0 || to !== path) return void 0;
|
|
13511
|
+
if (writeScript(path, content, remove) !== script) return void 0;
|
|
13512
|
+
repo.writeFile(path, content);
|
|
13513
|
+
removeAll(repo, remove);
|
|
13514
|
+
return ok();
|
|
13515
|
+
};
|
|
13071
13516
|
const recognizeShow = (repo, script) => {
|
|
13072
13517
|
const line = script.split("\n").find((l) => l.startsWith("git show ")) ?? "";
|
|
13073
13518
|
const [source, to] = words$1(line);
|
|
@@ -13312,7 +13757,7 @@ const recognizeCheck = (repo, commands, script) => {
|
|
|
13312
13757
|
*/
|
|
13313
13758
|
const applyFlowScript = (repo, commands, script) => {
|
|
13314
13759
|
if (!script.startsWith(`${SHEBANG}\n`)) return void 0;
|
|
13315
|
-
return recognizeRemove(repo, script) ?? recognizeMove(repo, script) ?? recognizeShow(repo, script) ?? recognizePatch(repo, script) ?? recognizeRestore(repo, script) ?? recognizeCheck(repo, commands, script);
|
|
13760
|
+
return recognizeRemove(repo, script) ?? recognizeMove(repo, script) ?? recognizeShow(repo, script) ?? recognizeWrite(repo, script) ?? recognizePatch(repo, script) ?? recognizeRestore(repo, script) ?? recognizeCheck(repo, commands, script);
|
|
13316
13761
|
};
|
|
13317
13762
|
//#endregion
|
|
13318
13763
|
//#region src/testkit/EmittedScriptRecognizer.ts
|
|
@@ -13824,18 +14269,6 @@ const splitBlocks = (script) => {
|
|
|
13824
14269
|
if (current.trim().length > 0) blocks.push(current.trim());
|
|
13825
14270
|
return blocks;
|
|
13826
14271
|
};
|
|
13827
|
-
/** `deferNotesScript`: append its one section to `.gtd/TODO.md`, reading the section's lines off the final `printf`. */
|
|
13828
|
-
const DEFER_HEAD = "mkdir -p .gtd\nif [ -s .gtd/TODO.md ]; then";
|
|
13829
|
-
const recognizeDeferNotes = (repo, block) => {
|
|
13830
|
-
if (!block.startsWith(DEFER_HEAD)) return void 0;
|
|
13831
|
-
const at = block.lastIndexOf("\nprintf '%s\\n' ");
|
|
13832
|
-
if (at === -1) return void 0;
|
|
13833
|
-
const lines = extractQuotedTokens(block.slice(at + 15));
|
|
13834
|
-
const old = repo.readFile(".gtd/TODO.md") ?? "";
|
|
13835
|
-
const sep = old === "" ? "" : `${old.endsWith("\n") ? "" : "\n"}\n`;
|
|
13836
|
-
repo.writeFile(".gtd/TODO.md", `${old}${sep}${lines.join("\n")}\n`);
|
|
13837
|
-
return { kind: "applied" };
|
|
13838
|
-
};
|
|
13839
14272
|
/** `mkdir -p <dir>` then `printf '%s\n' <quoted lines> > <path>`: a flow's one-file write. Re-rendered from what was extracted, so any other shape stays unrecognized. */
|
|
13840
14273
|
const recognizeWriteLines = (repo, block) => {
|
|
13841
14274
|
const [mkdir, printf, ...rest] = block.split("\n");
|
|
@@ -13855,7 +14288,6 @@ const recognizersFor = (repo, commands, git) => [
|
|
|
13855
14288
|
(block) => recognizeFileExistsGuard(repo, block),
|
|
13856
14289
|
(block) => recognizeBinaryGuard(commands, block),
|
|
13857
14290
|
(block) => git ? recognizeGitBuilders(repo, block) : void 0,
|
|
13858
|
-
(block) => recognizeDeferNotes(repo, block),
|
|
13859
14291
|
(block) => recognizeWriteLines(repo, block),
|
|
13860
14292
|
(block) => recognizeFailurePromptWrapper(repo, commands, block),
|
|
13861
14293
|
(block) => recognizePresentationSubshell(repo, commands, block, git),
|