@pmelab/gtd 52.1.0 → 54.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +91 -129
- package/claude/hooks/drive.ts +0 -3
- package/claude/hooks/register.tsx +3 -68
- package/claude/hooks/ship.ts +85 -1595
- package/dist/chunks/{reserved-DxB5CROj.mjs → reserved-CQ7amcKG.mjs} +45 -132
- package/dist/chunks/{review-CKMSdvgW.mjs → review-CXzUXJHL.mjs} +12 -5
- package/dist/chunks/{tail-QZc7Uz2U.mjs → tail-JZ9PAOwp.mjs} +2 -14
- package/dist/chunks/{wire-BJuZWR3C.mjs → wire-C12dahrH.mjs} +2 -130
- package/dist/flows.mjs +1 -16
- package/dist/gtd.bundle.mjs +476 -982
- package/dist/testkit.mjs +58 -417
- package/dist/wire.mjs +2 -2
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +112 -363
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +3 -3
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +321 -677
- package/package.json +1 -1
- package/src/cli/Cli.test.ts +1 -86
- package/src/cli/Cli.ts +13 -133
- package/src/cli/Metrics.test.ts +21 -31
- package/src/cli/Metrics.ts +25 -36
- package/src/cli/Program.test.ts +1 -35
- package/src/cli/Program.ts +5 -64
- package/src/cli/ToolCommands.ts +0 -3
- package/src/edge/Edge.test.ts +20 -322
- package/src/edge/Edge.ts +10 -181
- package/src/edge/index.ts +0 -1
- package/src/flows/runtime.test.ts +0 -27
- package/src/flows/runtime.ts +1 -49
- package/src/install/Install.ts +15 -24
- package/src/replay/Replay.test.ts +0 -169
- package/src/replay/Replay.ts +4 -93
- package/src/replay/index.ts +0 -1
- package/src/testkit/Layers.ts +0 -1
- package/src/testkit/steps.ts +23 -0
- package/src/wire/BeatDocument.ts +2 -12
- package/src/wire/BeatStatus.ts +0 -3
- package/src/wire/WireSchema.test.ts +0 -18
- package/src/wire/index.ts +0 -2
- package/src/wire/types.ts +0 -7
- package/src/workflow/Workflow.ts +1 -9
- package/src/workflows/feature/criteria.test.ts +1 -14
- package/src/workflows/feature/criteria.ts +6 -38
- package/src/workflows/feature/feature.ts +34 -118
- package/src/workflows/feature/hands-on-skip.feature +2 -70
- package/src/workflows/feature/index.ts +0 -1
- package/src/workflows/feature/lens-review.feature +50 -0
- package/src/workflows/feature/one-step-land.feature +0 -4
- package/src/workflows/feature/prototype-rounds.feature +1 -1
- package/src/workflows/feature/prototype.feature +3 -3
- package/src/workflows/feature/prototype.ts +18 -29
- package/src/workflows/feature/refactor-absorbs.feature +3 -8
- package/src/workflows/feature/refactor-first.feature +0 -5
- package/src/workflows/feature/refactor-rebuild.feature +0 -6
- package/src/workflows/feature/refactor-review.feature +49 -11
- package/src/workflows/feature/refactor.ts +30 -64
- package/src/workflows/feature/review-gate.feature +1 -1
- package/src/workflows/feature/reviewer-walkthrough.feature +0 -31
- package/src/workflows/feature/seam-rejection.feature +0 -4
- package/src/workflows/feature/seam.test.ts +7 -26
- package/src/workflows/feature/seam.ts +52 -165
- package/src/workflows/index.ts +0 -1
- package/src/workflows/shared/lenses.ts +0 -13
- package/src/workflows/shared/options.test.ts +2 -11
- package/src/workflows/shared/options.ts +4 -21
- package/src/workflows/shared/review.test.ts +3 -3
- package/src/workflows/shared/review.ts +12 -2
- package/src/workflows/shared/steps.ts +0 -2
- package/src/workflows/shared/text.test.ts +10 -80
- package/src/workflows/shared/text.ts +35 -211
- package/src/workflows/shared/vars.ts +2 -8
- package/src/workflows/shared/workflows.steps.ts +0 -13
- package/claude/hooks/context.ts +0 -66
- package/src/replay/Slices.test.ts +0 -73
- package/src/replay/Slices.ts +0 -45
- package/src/wire/ContextLimit.fixture.ts +0 -30
- package/src/wire/ContextLimit.test.ts +0 -96
- package/src/wire/ContextLimit.ts +0 -169
- package/src/wire/Slices.ts +0 -25
- package/src/wire/golden/1/slices.json +0 -11
- package/src/workflows/feature/context-limit.feature +0 -37
- package/src/workflows/feature/continuation-cap.feature +0 -69
- package/src/workflows/feature/continuation-rebuild.feature +0 -92
- package/src/workflows/feature/continuation-slices.feature +0 -68
- package/src/workflows/feature/continuation.feature +0 -209
- package/src/workflows/feature/slice-findings.feature +0 -256
- package/src/workflows/feature/slicing-cap.feature +0 -23
- package/src/workflows/feature/slicing.feature +0 -297
- package/src/workflows/feature/slicing.test.ts +0 -49
- package/src/workflows/feature/slicing.ts +0 -105
- package/src/workflows/shared/continuation.ts +0 -92
package/package.json
CHANGED
package/src/cli/Cli.test.ts
CHANGED
|
@@ -112,7 +112,6 @@ describe("parseArgv — scope", () => {
|
|
|
112
112
|
["judge", "--json"],
|
|
113
113
|
["judge", "answer", "--json"],
|
|
114
114
|
["doors", "--json"],
|
|
115
|
-
["slices", "--json"],
|
|
116
115
|
]) {
|
|
117
116
|
const ok = parseArgv(["node", "gtd.js", ...args])
|
|
118
117
|
expect(ok.kind).toBe("command")
|
|
@@ -929,79 +928,6 @@ describe("parseArgv — doors", () => {
|
|
|
929
928
|
})
|
|
930
929
|
})
|
|
931
930
|
|
|
932
|
-
describe("parseArgv — slices", () => {
|
|
933
|
-
const parse = (...args: string[]): CliPlan => parseArgv(["node", "gtd.js", ...args])
|
|
934
|
-
|
|
935
|
-
it("`gtd slices` parses with every --json shape", () => {
|
|
936
|
-
expect(parse("slices")).toMatchObject({
|
|
937
|
-
kind: "command",
|
|
938
|
-
command: { kind: "slices" },
|
|
939
|
-
json: { kind: "off" },
|
|
940
|
-
})
|
|
941
|
-
expect(parse("slices", "--json")).toMatchObject({ json: { kind: "document" } })
|
|
942
|
-
expect(parse("slices", "--json=slices.0.name")).toMatchObject({
|
|
943
|
-
json: { kind: "select", path: "slices.0.name" },
|
|
944
|
-
})
|
|
945
|
-
})
|
|
946
|
-
|
|
947
|
-
it("`gtd slices` takes no argument", () => {
|
|
948
|
-
const plan = parse("slices", "extra")
|
|
949
|
-
expect(plan.kind).toBe("usage")
|
|
950
|
-
if (plan.kind === "usage") expect(plan.message).toContain("too many arguments")
|
|
951
|
-
})
|
|
952
|
-
|
|
953
|
-
it("help lists the command, and --json names it", () => {
|
|
954
|
-
const help = renderHelp()
|
|
955
|
-
expect(help).toMatch(/^ {2}slices\b/m)
|
|
956
|
-
expect(help).toContain("gtd slices")
|
|
957
|
-
})
|
|
958
|
-
|
|
959
|
-
it("--json's scope error names gtd slices", () => {
|
|
960
|
-
const plan = parse("lsp", "--json")
|
|
961
|
-
expect(plan.kind).toBe("usage")
|
|
962
|
-
if (plan.kind === "usage") expect(plan.message).toContain("`gtd slices`")
|
|
963
|
-
})
|
|
964
|
-
})
|
|
965
|
-
|
|
966
|
-
describe("parseArgv — summary --slice", () => {
|
|
967
|
-
const parse = (...args: string[]): CliPlan => parseArgv(["node", "gtd.js", ...args])
|
|
968
|
-
const usage = (plan: CliPlan): string => (plan.kind === "usage" ? plan.message : "")
|
|
969
|
-
|
|
970
|
-
it("`--slice build` and `--slice=build` parse to the slice name", () => {
|
|
971
|
-
for (const plan of [parse("summary", "--slice", "build"), parse("summary", "--slice=build")]) {
|
|
972
|
-
expect(plan).toMatchObject({ kind: "command", command: { kind: "summary", slice: "build" } })
|
|
973
|
-
}
|
|
974
|
-
})
|
|
975
|
-
|
|
976
|
-
it("plain `summary` carries no slice key", () => {
|
|
977
|
-
const plan = parse("summary")
|
|
978
|
-
expect(plan.kind === "command" && plan.command).toEqual({ kind: "summary" })
|
|
979
|
-
})
|
|
980
|
-
|
|
981
|
-
it("bare `--slice` is a usage error naming the value form", () => {
|
|
982
|
-
const plan = parse("summary", "--slice")
|
|
983
|
-
expect(plan.kind).toBe("usage")
|
|
984
|
-
expect(usage(plan)).toContain("--slice")
|
|
985
|
-
expect(usage(plan)).toContain("value")
|
|
986
|
-
})
|
|
987
|
-
|
|
988
|
-
it("`--slice` twice is a usage error", () => {
|
|
989
|
-
expect(parse("summary", "--slice", "a", "--slice", "b").kind).toBe("usage")
|
|
990
|
-
})
|
|
991
|
-
|
|
992
|
-
it("`--slice` on another command fails the scope check naming gtd summary", () => {
|
|
993
|
-
for (const command of ["slices", "next"]) {
|
|
994
|
-
const plan = parse(command, "--slice", "build")
|
|
995
|
-
expect(plan.kind).toBe("usage")
|
|
996
|
-
expect(usage(plan)).toContain("`gtd summary`")
|
|
997
|
-
}
|
|
998
|
-
})
|
|
999
|
-
|
|
1000
|
-
it("help lists the flag", () => {
|
|
1001
|
-
expect(renderHelp()).toContain("--slice=<name>")
|
|
1002
|
-
})
|
|
1003
|
-
})
|
|
1004
|
-
|
|
1005
931
|
describe("parseArgv — gtd history", () => {
|
|
1006
932
|
const parse = (...args: string[]) => parseArgv(["node", "gtd.js", ...args])
|
|
1007
933
|
const usage = (plan: CliPlan): string => (plan.kind === "usage" ? plan.message : "")
|
|
@@ -1142,15 +1068,7 @@ describe("parseArgv — gtd strays", () => {
|
|
|
1142
1068
|
|
|
1143
1069
|
describe("standaloneKinds / needsOf", () => {
|
|
1144
1070
|
it("pins the standalone kinds", () => {
|
|
1145
|
-
expect(standaloneKinds()).toEqual([
|
|
1146
|
-
"lsp",
|
|
1147
|
-
"check",
|
|
1148
|
-
"uncheck",
|
|
1149
|
-
"install",
|
|
1150
|
-
"contextHook",
|
|
1151
|
-
"judgeRun",
|
|
1152
|
-
"strays",
|
|
1153
|
-
])
|
|
1071
|
+
expect(standaloneKinds()).toEqual(["lsp", "check", "uncheck", "install", "judgeRun", "strays"])
|
|
1154
1072
|
})
|
|
1155
1073
|
|
|
1156
1074
|
it("needsOf matches none/fs for the standalone kinds and state for everything else", () => {
|
|
@@ -1158,7 +1076,6 @@ describe("standaloneKinds / needsOf", () => {
|
|
|
1158
1076
|
expect(needsOf("check")).toBe("fs")
|
|
1159
1077
|
expect(needsOf("uncheck")).toBe("fs")
|
|
1160
1078
|
expect(needsOf("install")).toBe("none")
|
|
1161
|
-
expect(needsOf("contextHook")).toBe("none")
|
|
1162
1079
|
expect(needsOf("judgeRun")).toBe("none")
|
|
1163
1080
|
expect(needsOf("strays")).toBe("none")
|
|
1164
1081
|
for (const kind of [
|
|
@@ -1173,7 +1090,6 @@ describe("standaloneKinds / needsOf", () => {
|
|
|
1173
1090
|
"judgeAnswer",
|
|
1174
1091
|
"door",
|
|
1175
1092
|
"doors",
|
|
1176
|
-
"slices",
|
|
1177
1093
|
"historyList",
|
|
1178
1094
|
"historyShow",
|
|
1179
1095
|
"historyMetrics",
|
|
@@ -1251,7 +1167,6 @@ describe("renderHelp", () => {
|
|
|
1251
1167
|
),
|
|
1252
1168
|
)
|
|
1253
1169
|
expect(commands).toContain("judge answer")
|
|
1254
|
-
expect(commands).toContain("context-hook")
|
|
1255
1170
|
const listed = (file: string, list: RegExp): Set<string> => {
|
|
1256
1171
|
const doc = readFileSync(resolve(import.meta.dirname, file), "utf8")
|
|
1257
1172
|
const match = doc.match(list)
|
package/src/cli/Cli.ts
CHANGED
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
} from "./Program.js"
|
|
37
37
|
import type { ToolCommands } from "./ToolCommands.js"
|
|
38
38
|
import { THRESHOLDS } from "./Judges.js"
|
|
39
|
-
import { EXIT_RUNTIME_ERROR, EXIT_USAGE_ERROR
|
|
39
|
+
import { EXIT_RUNTIME_ERROR, EXIT_USAGE_ERROR } from "../wire/index.js"
|
|
40
40
|
|
|
41
41
|
export type { Command, CommandRequirements } from "./Program.js"
|
|
42
42
|
export { needsOf, standaloneKinds } from "./Program.js"
|
|
@@ -91,20 +91,12 @@ const nonNegativeNumber = (raw: string, flag: string): Either.Either<number, str
|
|
|
91
91
|
return Either.right(n)
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
const share = (raw: string, flag: string): Either.Either<number, string> => {
|
|
95
|
-
const n = Number(raw)
|
|
96
|
-
return raw.trim() !== "" && n > 0 && n <= 1
|
|
97
|
-
? Either.right(n)
|
|
98
|
-
: Either.left(`gtd: ${flag} must be a share of the context window in (0, 1] — got "${raw}"`)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
94
|
const JSON_KINDS: ReadonlySet<Command["kind"] | undefined> = new Set<Command["kind"]>([
|
|
102
95
|
"next",
|
|
103
96
|
"land",
|
|
104
97
|
"judge",
|
|
105
98
|
"judgeAnswer",
|
|
106
99
|
"doors",
|
|
107
|
-
"slices",
|
|
108
100
|
"historyList",
|
|
109
101
|
"historyShow",
|
|
110
102
|
"historyMetrics",
|
|
@@ -120,11 +112,11 @@ const FLAGS: readonly FlagRow[] = [
|
|
|
120
112
|
scope: (kind) => JSON_KINDS.has(kind),
|
|
121
113
|
decode: ([raw]) => Either.right(raw ?? ""),
|
|
122
114
|
scopeError:
|
|
123
|
-
"gtd: --json is only valid for `gtd next`/`gtd land`/`gtd judge`/`gtd judge answer`/`gtd doors`/`gtd
|
|
115
|
+
"gtd: --json is only valid for `gtd next`/`gtd land`/`gtd judge`/`gtd judge answer`/`gtd doors`/`gtd history list`/`gtd history show`/`gtd history metrics`/`gtd history judges`/`gtd strays` — " +
|
|
124
116
|
"every other command prints plain text; see `gtd install` for the driver protocol briefing",
|
|
125
117
|
valueHint: "<path>",
|
|
126
118
|
help: [
|
|
127
|
-
"(gtd next/gtd land/gtd judge/gtd judge answer/gtd doors/gtd
|
|
119
|
+
"(gtd next/gtd land/gtd judge/gtd judge answer/gtd doors/gtd history",
|
|
128
120
|
"list/gtd history show/gtd history metrics/gtd history judges/gtd strays only) output structured JSON. Bare --json prints the whole document;",
|
|
129
121
|
"--json=<path> (a dotted key path into that document, e.g.",
|
|
130
122
|
"kind, content, session.id) prints just that value: a",
|
|
@@ -258,56 +250,6 @@ const FLAGS: readonly FlagRow[] = [
|
|
|
258
250
|
"GTD_JUDGE_MODEL, then judge.json, then sonnet",
|
|
259
251
|
],
|
|
260
252
|
},
|
|
261
|
-
{
|
|
262
|
-
name: "--soft",
|
|
263
|
-
arity: 1,
|
|
264
|
-
repeatable: false,
|
|
265
|
-
scope: (kind) => kind === "contextHook",
|
|
266
|
-
decode: ([raw]) => share(raw ?? "", "--soft"),
|
|
267
|
-
scopeError: "gtd: --soft is only valid for `gtd context-hook`",
|
|
268
|
-
valueHint: "<share>",
|
|
269
|
-
help: ["(gtd context-hook only) the soft limit; default 0.6"],
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
name: "--hard",
|
|
273
|
-
arity: 1,
|
|
274
|
-
repeatable: false,
|
|
275
|
-
scope: (kind) => kind === "contextHook",
|
|
276
|
-
decode: ([raw]) => share(raw ?? "", "--hard"),
|
|
277
|
-
scopeError: "gtd: --hard is only valid for `gtd context-hook`",
|
|
278
|
-
valueHint: "<share>",
|
|
279
|
-
help: ["(gtd context-hook only) the hard limit; default 0.7"],
|
|
280
|
-
},
|
|
281
|
-
{
|
|
282
|
-
name: "--window",
|
|
283
|
-
arity: 1,
|
|
284
|
-
repeatable: false,
|
|
285
|
-
scope: (kind) => kind === "contextHook",
|
|
286
|
-
decode: ([raw]) => {
|
|
287
|
-
const window = parseContextWindow(raw)
|
|
288
|
-
return window !== undefined
|
|
289
|
-
? Either.right(window)
|
|
290
|
-
: Either.left(`gtd: --window must be a positive integer — got "${raw ?? ""}"`)
|
|
291
|
-
},
|
|
292
|
-
scopeError: "gtd: --window is only valid for `gtd context-hook`",
|
|
293
|
-
valueHint: "<tokens>",
|
|
294
|
-
help: ["(gtd context-hook only) the model's context window in", "tokens; default 200000"],
|
|
295
|
-
},
|
|
296
|
-
{
|
|
297
|
-
name: "--slice",
|
|
298
|
-
arity: 1,
|
|
299
|
-
repeatable: false,
|
|
300
|
-
scope: (kind) => kind === "summary",
|
|
301
|
-
decode: ([raw]) => Either.right(raw!),
|
|
302
|
-
scopeError: "gtd: --slice is only valid for `gtd summary`",
|
|
303
|
-
valueHint: "<name>",
|
|
304
|
-
help: [
|
|
305
|
-
"(gtd summary only) the closing-message prompt for one",
|
|
306
|
-
"slice, named as `gtd slices` lists it: its own range, human",
|
|
307
|
-
"commits and cost. An unknown name is refused (exit 1) with",
|
|
308
|
-
"every valid slice listed",
|
|
309
|
-
],
|
|
310
|
-
},
|
|
311
253
|
{
|
|
312
254
|
name: "--workflow",
|
|
313
255
|
arity: 1,
|
|
@@ -642,22 +584,6 @@ const COMMAND_ROWS: readonly CommandRow[] = [
|
|
|
642
584
|
"else it writes nothing.",
|
|
643
585
|
],
|
|
644
586
|
},
|
|
645
|
-
{
|
|
646
|
-
token: "context-hook",
|
|
647
|
-
kind: "contextHook",
|
|
648
|
-
arity: "none",
|
|
649
|
-
details: [
|
|
650
|
-
"A Claude Code PostToolUse and PreCompact hook that stops an",
|
|
651
|
-
"agent turn at a share of its context window. Reads the",
|
|
652
|
-
"hook's input on stdin and the context the transcript's",
|
|
653
|
-
"latest assistant message used. Past --soft it blocks with a",
|
|
654
|
-
"request to end the turn with a handoff note; past --hard,",
|
|
655
|
-
"or at PreCompact, it stops the turn with a stopReason",
|
|
656
|
-
'starting "gtd: context limit". Prints nothing otherwise,',
|
|
657
|
-
"and for a subagent's tool call. Always exits 0 on valid",
|
|
658
|
-
"input; see docs/driver.md",
|
|
659
|
-
],
|
|
660
|
-
},
|
|
661
587
|
{
|
|
662
588
|
token: "summary",
|
|
663
589
|
kind: "summary",
|
|
@@ -671,15 +597,10 @@ const COMMAND_ROWS: readonly CommandRow[] = [
|
|
|
671
597
|
"no state transition, no file, no session identity — the",
|
|
672
598
|
"driver pipes the output to a cold agent and does what it",
|
|
673
599
|
"wants with the result (a squash, an amend, a PR body).",
|
|
674
|
-
"
|
|
675
|
-
"
|
|
676
|
-
"
|
|
677
|
-
"
|
|
678
|
-
"replay diverged; an open last slice is provisional. Refuses",
|
|
679
|
-
"(exit 1) when the workflow declares no summary: template,",
|
|
680
|
-
"or when the resolved run has no commits to name — plain",
|
|
681
|
-
"gtd summary is runnable any time before the next thing",
|
|
682
|
-
"lands on the branch",
|
|
600
|
+
"Refuses (exit 1) when the workflow declares no summary:",
|
|
601
|
+
"template, or when the resolved run has no commits to name —",
|
|
602
|
+
"it is runnable any time before the next thing lands on the",
|
|
603
|
+
"branch",
|
|
683
604
|
],
|
|
684
605
|
},
|
|
685
606
|
{
|
|
@@ -722,25 +643,6 @@ const COMMAND_ROWS: readonly CommandRow[] = [
|
|
|
722
643
|
"value off it. Writes nothing",
|
|
723
644
|
],
|
|
724
645
|
},
|
|
725
|
-
{
|
|
726
|
-
token: "slices",
|
|
727
|
-
kind: "slices",
|
|
728
|
-
arity: "none",
|
|
729
|
-
details: [
|
|
730
|
-
"List the slices of the process HEAD closes or sits inside,",
|
|
731
|
-
"first slice first: one `name base..tip` line each (full",
|
|
732
|
-
"hashes; plain output carries no promise). --json prints",
|
|
733
|
-
"{ schema: 1, slices: [{ name, base, tip, closed }] };",
|
|
734
|
-
"--json=<path> reads one value off it. Writes nothing: no",
|
|
735
|
-
"git, no state transition, no session identity. When HEAD",
|
|
736
|
-
"closes a process, every slice is listed as closed; a gtd",
|
|
737
|
-
"version mismatch only warns on stderr. Refuses (exit 1)",
|
|
738
|
-
"when HEAD neither closes nor sits inside a process, or when",
|
|
739
|
-
"today's config disagrees with history about the process:",
|
|
740
|
-
"HEAD closes it and its config files differ from the working",
|
|
741
|
-
"tree, or the replay diverged",
|
|
742
|
-
],
|
|
743
|
-
},
|
|
744
646
|
{
|
|
745
647
|
token: "judge",
|
|
746
648
|
kind: "judge",
|
|
@@ -836,11 +738,11 @@ const COMMAND_ROWS: readonly CommandRow[] = [
|
|
|
836
738
|
kind: "historyMetrics",
|
|
837
739
|
arity: { name: "ref", many: true },
|
|
838
740
|
details: [
|
|
839
|
-
"Report how the kept processes went:
|
|
840
|
-
"landed or was rebuilt (the refactor-first rate),
|
|
841
|
-
"
|
|
842
|
-
"
|
|
843
|
-
"
|
|
741
|
+
"Report how the kept processes went: builds whose naive pass",
|
|
742
|
+
"landed or was rebuilt (the refactor-first rate), Gtd-Cost by",
|
|
743
|
+
"model and phase with the double work of a rebuild, quality-lap",
|
|
744
|
+
"findings per lens, and red checks that passed on their",
|
|
745
|
+
"re-check — per process and in total. Each <ref>",
|
|
844
746
|
"is read as by history show, but a branch name means every",
|
|
845
747
|
"process kept from it; none means every ref.",
|
|
846
748
|
"Reads commit subjects and trailers, never replays, so a",
|
|
@@ -1282,10 +1184,6 @@ export const parseArgv = (argv: readonly string[]): CliPlan => {
|
|
|
1282
1184
|
readonly "--dev"?: boolean
|
|
1283
1185
|
readonly "--provider"?: "fixed" | "jev" | "llm"
|
|
1284
1186
|
readonly "--answers"?: string
|
|
1285
|
-
readonly "--slice"?: string
|
|
1286
|
-
readonly "--soft"?: number
|
|
1287
|
-
readonly "--hard"?: number
|
|
1288
|
-
readonly "--window"?: number
|
|
1289
1187
|
readonly "--jsonl"?: boolean
|
|
1290
1188
|
}
|
|
1291
1189
|
|
|
@@ -1390,27 +1288,10 @@ export const parseArgv = (argv: readonly string[]): CliPlan => {
|
|
|
1390
1288
|
}
|
|
1391
1289
|
}
|
|
1392
1290
|
|
|
1393
|
-
if (kind === "contextHook") {
|
|
1394
|
-
return {
|
|
1395
|
-
kind: "command",
|
|
1396
|
-
command: {
|
|
1397
|
-
kind: "contextHook",
|
|
1398
|
-
...(bag["--soft"] !== undefined ? { soft: bag["--soft"] } : {}),
|
|
1399
|
-
...(bag["--hard"] !== undefined ? { hard: bag["--hard"] } : {}),
|
|
1400
|
-
...(bag["--window"] !== undefined ? { window: bag["--window"] } : {}),
|
|
1401
|
-
},
|
|
1402
|
-
json,
|
|
1403
|
-
verbose,
|
|
1404
|
-
}
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
1291
|
if (kind === "summary") {
|
|
1408
1292
|
return {
|
|
1409
1293
|
kind: "command",
|
|
1410
|
-
command: {
|
|
1411
|
-
kind: "summary",
|
|
1412
|
-
...(bag["--slice"] !== undefined ? { slice: bag["--slice"] } : {}),
|
|
1413
|
-
},
|
|
1294
|
+
command: { kind: "summary" },
|
|
1414
1295
|
json,
|
|
1415
1296
|
verbose,
|
|
1416
1297
|
}
|
|
@@ -1515,7 +1396,6 @@ export const parseArgv = (argv: readonly string[]): CliPlan => {
|
|
|
1515
1396
|
| "validate"
|
|
1516
1397
|
| "install"
|
|
1517
1398
|
| "doors"
|
|
1518
|
-
| "slices"
|
|
1519
1399
|
| "base"
|
|
1520
1400
|
| "judge"
|
|
1521
1401
|
| "judgeAnswer"
|
package/src/cli/Metrics.test.ts
CHANGED
|
@@ -10,50 +10,50 @@ const turn = (subject: string, opts: { cost?: string; findings?: number } = {}):
|
|
|
10
10
|
})
|
|
11
11
|
|
|
12
12
|
describe("processMetrics", () => {
|
|
13
|
-
it("counts a naive pass applied as it is as a landed
|
|
13
|
+
it("counts a naive pass applied as it is as a landed build", () => {
|
|
14
14
|
const m = processMetrics([
|
|
15
15
|
turn("gtd(human): idle → seam.naive"),
|
|
16
16
|
turn("gtd(agent): seam.naive → seam.check", { cost: "0.4 coder" }),
|
|
17
17
|
turn("gtd(check): seam.check → implement.applying"),
|
|
18
18
|
turn("gtd(check): implement.applying → idle"),
|
|
19
19
|
])
|
|
20
|
-
expect(m.
|
|
20
|
+
expect(m.builds).toEqual({ decided: 1, landed: 1, rebuilt: 0, refactored: 0 })
|
|
21
21
|
expect(m.refactorFirstRate).toBe(0)
|
|
22
22
|
expect(m.cost.byPhase).toEqual({ naive: 0.4, refactor: 0, implement: 0, other: 0 })
|
|
23
23
|
expect(m.cost.doubleWork).toEqual({ naive: 0, rebuild: 0, total: 0 })
|
|
24
24
|
})
|
|
25
25
|
|
|
26
|
-
it("charges a rebuilt
|
|
26
|
+
it("charges a rebuilt build's naive pass and rebuild as double work, refactor apart", () => {
|
|
27
27
|
const m = processMetrics([
|
|
28
28
|
turn("gtd(human): idle → seam.naive"),
|
|
29
|
-
turn("gtd(agent): seam.naive → seam.
|
|
30
|
-
turn("gtd(
|
|
29
|
+
turn("gtd(agent): seam.naive → seam.check", { cost: "0.4 coder" }),
|
|
30
|
+
turn("gtd(check): seam.check → seam.naive"),
|
|
31
|
+
turn("gtd(agent): seam.naive → seam.judge", { cost: "0.2 coder" }),
|
|
31
32
|
turn("gtd(judge): seam.judge → seam.note", { cost: "0.01 judge" }),
|
|
32
33
|
turn("gtd(agent): seam.note → refactor.deriving", { cost: "0.05 coder" }),
|
|
33
34
|
turn("gtd(check): refactor.deriving → refactor.building"),
|
|
34
35
|
turn("gtd(agent): refactor.building → refactor.closing", { cost: "0.3 coder" }),
|
|
35
36
|
turn("gtd(check): refactor.closing → implement.building"),
|
|
36
|
-
turn("gtd(agent): implement.building → implement.
|
|
37
|
-
turn("gtd(agent): implement.
|
|
37
|
+
turn("gtd(agent): implement.building → implement.health.check", { cost: "0.5 coder" }),
|
|
38
|
+
turn("gtd(agent): implement.fix → idle", { cost: "0.25 coder" }),
|
|
38
39
|
])
|
|
39
|
-
expect(m.
|
|
40
|
+
expect(m.builds).toEqual({ decided: 1, landed: 0, rebuilt: 1, refactored: 1 })
|
|
40
41
|
expect(m.refactorFirstRate).toBe(1)
|
|
41
|
-
expect(m.continuations).toEqual({ "seam.naive": 1, "implement.building": 1 })
|
|
42
42
|
expect(m.cost.total).toBe(1.71)
|
|
43
43
|
expect(m.cost.byModel).toEqual({ coder: 1.7, judge: 0.01 })
|
|
44
44
|
expect(m.cost.byPhase).toEqual({ naive: 0.6, refactor: 0.35, implement: 0.75, other: 0.01 })
|
|
45
45
|
expect(m.cost.doubleWork).toEqual({ naive: 0.6, rebuild: 0.75, total: 1.35 })
|
|
46
46
|
})
|
|
47
47
|
|
|
48
|
-
it("rebuilds without a refactor when the
|
|
48
|
+
it("rebuilds without a refactor when the build leaves the seam for a build turn", () => {
|
|
49
49
|
const m = processMetrics([
|
|
50
50
|
turn("gtd(judge): seam.judge → implement.building"),
|
|
51
51
|
turn("gtd(agent): implement.building → idle"),
|
|
52
52
|
])
|
|
53
|
-
expect(m.
|
|
53
|
+
expect(m.builds).toEqual({ decided: 1, landed: 0, rebuilt: 1, refactored: 0 })
|
|
54
54
|
})
|
|
55
55
|
|
|
56
|
-
it("keeps each
|
|
56
|
+
it("keeps each build's decision apart", () => {
|
|
57
57
|
const m = processMetrics([
|
|
58
58
|
turn("gtd(agent): seam.naive → seam.check", { cost: "1" }),
|
|
59
59
|
turn("gtd(check): seam.check → implement.applying"),
|
|
@@ -61,20 +61,19 @@ describe("processMetrics", () => {
|
|
|
61
61
|
turn("gtd(check): implement.closing → seam.naive"),
|
|
62
62
|
turn("gtd(agent): seam.naive → seam.note", { cost: "2" }),
|
|
63
63
|
turn("gtd(agent): seam.note → refactor.deriving"),
|
|
64
|
-
turn("gtd(agent): refactor.
|
|
64
|
+
turn("gtd(agent): refactor.building → refactor.closing", { cost: "1" }),
|
|
65
65
|
turn("gtd(check): refactor.closing → implement.building"),
|
|
66
66
|
turn("gtd(agent): implement.building → idle", { cost: "3" }),
|
|
67
67
|
])
|
|
68
|
-
expect(m.
|
|
68
|
+
expect(m.builds).toEqual({ decided: 2, landed: 1, rebuilt: 1, refactored: 1 })
|
|
69
69
|
expect(m.refactorFirstRate).toBe(0.5)
|
|
70
|
-
expect(m.continuations).toEqual({ "refactor.building": 1 })
|
|
71
70
|
expect(m.cost.byModel).toEqual({ unspecified: 8 })
|
|
72
71
|
expect(m.cost.doubleWork).toEqual({ naive: 2, rebuild: 3, total: 5 })
|
|
73
72
|
})
|
|
74
73
|
|
|
75
|
-
it("counts a naive
|
|
74
|
+
it("counts a naive pass under an older history's scope as naive cost", () => {
|
|
76
75
|
const m = processMetrics([
|
|
77
|
-
turn("gtd(agent): seam.
|
|
76
|
+
turn("gtd(agent): seam.naive → seam.judge", { cost: "1" }),
|
|
78
77
|
turn("gtd(judge): seam.judge → seam.fresh.naive"),
|
|
79
78
|
turn("gtd(agent): seam.fresh.naive → seam.judge", { cost: "2" }),
|
|
80
79
|
turn("gtd(judge): seam.judge → implement.building"),
|
|
@@ -83,14 +82,14 @@ describe("processMetrics", () => {
|
|
|
83
82
|
expect(m.cost.doubleWork.naive).toBe(3)
|
|
84
83
|
})
|
|
85
84
|
|
|
86
|
-
it("turns a landed
|
|
85
|
+
it("turns a landed build whose naive pass fails to apply into a rebuild", () => {
|
|
87
86
|
const m = processMetrics([
|
|
88
87
|
turn("gtd(agent): seam.naive → seam.check", { cost: "1" }),
|
|
89
88
|
turn("gtd(check): seam.check → implement.applying"),
|
|
90
89
|
turn("gtd(check): implement.applying → implement.building"),
|
|
91
90
|
turn("gtd(agent): implement.building → implement.health.check", { cost: "2" }),
|
|
92
91
|
])
|
|
93
|
-
expect(m.
|
|
92
|
+
expect(m.builds).toEqual({ decided: 1, landed: 0, rebuilt: 1, refactored: 0 })
|
|
94
93
|
expect(m.cost.doubleWork).toEqual({ naive: 1, rebuild: 2, total: 3 })
|
|
95
94
|
})
|
|
96
95
|
|
|
@@ -107,7 +106,7 @@ describe("processMetrics", () => {
|
|
|
107
106
|
})
|
|
108
107
|
})
|
|
109
108
|
|
|
110
|
-
it("ends a rebuild's double work at the
|
|
109
|
+
it("ends a rebuild's double work at the build's closing", () => {
|
|
111
110
|
const m = processMetrics([
|
|
112
111
|
turn("gtd(judge): seam.judge → implement.building"),
|
|
113
112
|
turn("gtd(agent): implement.building → implement.closing", { cost: "1" }),
|
|
@@ -134,14 +133,6 @@ describe("processMetrics", () => {
|
|
|
134
133
|
expect(processMetrics([turn("gtd(human): idle → building")]).refactorFirstRate).toBeNull()
|
|
135
134
|
})
|
|
136
135
|
|
|
137
|
-
it("counts continuations only for agent turns, the bare form included", () => {
|
|
138
|
-
const m = processMetrics([
|
|
139
|
-
turn("gtd(agent): seam.continue.naive"),
|
|
140
|
-
turn("gtd(human): seam.continue.naive → seam.oversized"),
|
|
141
|
-
])
|
|
142
|
-
expect(m.continuations).toEqual({ "seam.naive": 1 })
|
|
143
|
-
})
|
|
144
|
-
|
|
145
136
|
it("sums findings per quality lens and skips commits gtd did not write", () => {
|
|
146
137
|
const m = processMetrics([
|
|
147
138
|
turn(
|
|
@@ -201,16 +192,15 @@ describe("totalMetrics", () => {
|
|
|
201
192
|
turn("gtd(check): seam.check → implement.applying"),
|
|
202
193
|
])
|
|
203
194
|
const b = processMetrics([
|
|
204
|
-
turn("gtd(agent): seam.
|
|
195
|
+
turn("gtd(agent): seam.naive → seam.note", { cost: "2 b" }),
|
|
205
196
|
turn("gtd(agent): seam.note → refactor.deriving"),
|
|
206
197
|
turn("gtd(agent): seam.naive → seam.note"),
|
|
207
198
|
turn("gtd(agent): seam.note → refactor.deriving"),
|
|
208
199
|
])
|
|
209
200
|
const total = totalMetrics([a, b])
|
|
210
201
|
expect(total.processes).toBe(2)
|
|
211
|
-
expect(total.
|
|
202
|
+
expect(total.builds).toEqual({ decided: 3, landed: 1, rebuilt: 2, refactored: 2 })
|
|
212
203
|
expect(total.refactorFirstRate).toBe(0.667)
|
|
213
|
-
expect(total.continuations).toEqual({ "seam.naive": 1 })
|
|
214
204
|
expect(total.cost.byModel).toEqual({ a: 1, b: 2 })
|
|
215
205
|
})
|
|
216
206
|
})
|