@pmelab/gtd 52.0.0 → 53.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +96 -131
- 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-D0MKVFFa.mjs → reserved-CQ7amcKG.mjs} +87 -133
- package/dist/chunks/{review-ZzUQH1KZ.mjs → review-CNgN_V8T.mjs} +2 -2
- package/dist/chunks/{tail-I1-vVq0P.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 +159 -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 +400 -675
- package/package.json +5 -4
- 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 +13 -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/acceptance-criteria.feature +159 -3
- package/src/workflows/feature/criteria.test.ts +23 -15
- package/src/workflows/feature/criteria.ts +72 -39
- package/src/workflows/feature/fast-path.feature +4 -0
- package/src/workflows/feature/feature.test.ts +6 -5
- package/src/workflows/feature/feature.ts +34 -118
- package/src/workflows/feature/flag-mode.feature +4 -0
- package/src/workflows/feature/hands-on-skip.feature +37 -65
- package/src/workflows/feature/index.ts +0 -1
- package/src/workflows/feature/lens-models.feature +4 -0
- package/src/workflows/feature/lens-review.feature +4 -0
- package/src/workflows/feature/naive-acceptance.feature +4 -0
- package/src/workflows/feature/one-step-land.feature +4 -4
- package/src/workflows/feature/planning.ts +2 -1
- package/src/workflows/feature/prototype-rounds.feature +5 -1
- package/src/workflows/feature/prototype.feature +11 -3
- package/src/workflows/feature/prototype.ts +18 -29
- package/src/workflows/feature/refactor-absorbs.feature +7 -8
- package/src/workflows/feature/refactor-first.feature +4 -5
- package/src/workflows/feature/refactor-lens-models.feature +4 -0
- package/src/workflows/feature/refactor-rebuild.feature +4 -6
- package/src/workflows/feature/refactor-review.feature +31 -11
- package/src/workflows/feature/refactor.ts +30 -64
- package/src/workflows/feature/review-answers.feature +4 -0
- package/src/workflows/feature/review-gate.feature +13 -1
- package/src/workflows/feature/reviewer-walkthrough.feature +4 -0
- package/src/workflows/feature/risk-tier.feature +32 -0
- package/src/workflows/feature/seam-rejection.feature +8 -4
- package/src/workflows/feature/seam.feature +8 -0
- package/src/workflows/feature/seam.test.ts +11 -28
- package/src/workflows/feature/seam.ts +52 -165
- package/src/workflows/feature/severity-budget.feature +4 -0
- package/src/workflows/feature/severity-sorted-review.feature +4 -0
- package/src/workflows/feature/standards.feature +4 -0
- package/src/workflows/feature/tier-pin.feature +4 -0
- package/src/workflows/index.ts +0 -1
- package/src/workflows/shared/lenses.ts +0 -13
- package/src/workflows/shared/options.test.ts +2 -11
- package/src/workflows/shared/options.ts +4 -21
- package/src/workflows/shared/review.test.ts +3 -3
- package/src/workflows/shared/steps.ts +0 -2
- package/src/workflows/shared/text.test.ts +41 -71
- package/src/workflows/shared/text.ts +82 -212
- 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 -65
- package/src/workflows/feature/continuation-rebuild.feature +0 -88
- package/src/workflows/feature/continuation-slices.feature +0 -64
- package/src/workflows/feature/continuation.feature +0 -205
- package/src/workflows/feature/slice-findings.feature +0 -252
- package/src/workflows/feature/slicing-cap.feature +0 -23
- package/src/workflows/feature/slicing.feature +0 -265
- 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/src/cli/Metrics.ts
CHANGED
|
@@ -18,7 +18,8 @@ export interface MetricCommit {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export interface Metrics {
|
|
21
|
-
|
|
21
|
+
/** Each naive pass's decision: landed as it is, or rebuilt (after a refactor or not). */
|
|
22
|
+
readonly builds: {
|
|
22
23
|
readonly decided: number
|
|
23
24
|
readonly landed: number
|
|
24
25
|
readonly rebuilt: number
|
|
@@ -26,8 +27,6 @@ export interface Metrics {
|
|
|
26
27
|
}
|
|
27
28
|
/** `rebuilt / decided`, to three places; null without a decision. */
|
|
28
29
|
readonly refactorFirstRate: number | null
|
|
29
|
-
/** Continuation turns per phase, keyed by the step without its `continue` segment. */
|
|
30
|
-
readonly continuations: Readonly<Record<string, number>>
|
|
31
30
|
readonly cost: {
|
|
32
31
|
readonly total: number
|
|
33
32
|
readonly byModel: Readonly<Record<string, number>>
|
|
@@ -37,7 +36,7 @@ export interface Metrics {
|
|
|
37
36
|
readonly implement: number
|
|
38
37
|
readonly other: number
|
|
39
38
|
}
|
|
40
|
-
/** A rebuilt
|
|
39
|
+
/** A rebuilt build's naive pass and the implement turns that rebuilt it. */
|
|
41
40
|
readonly doubleWork: {
|
|
42
41
|
readonly naive: number
|
|
43
42
|
readonly rebuild: number
|
|
@@ -54,11 +53,9 @@ type Phase = keyof Metrics["cost"]["byPhase"]
|
|
|
54
53
|
// Keyed by the lap's path without `.reviewing`, so the refactor lap's lenses stay apart from the feature's.
|
|
55
54
|
const LENS = /^((?:.*\.)?quality\.[^.]+)\.reviewing$/
|
|
56
55
|
|
|
57
|
-
const CONTINUE = /(^|\.)continue\./
|
|
58
|
-
|
|
59
56
|
const inSeam = (step: string): boolean => step.startsWith("seam.")
|
|
60
57
|
|
|
61
|
-
//
|
|
58
|
+
// Any seam step named `naive` is the pass, whatever scope an older history ran it under.
|
|
62
59
|
const phaseOf = (step: string): Phase => {
|
|
63
60
|
const scope = step.split(".")[0]
|
|
64
61
|
if (inSeam(step) && step.endsWith(".naive")) return "naive"
|
|
@@ -83,8 +80,7 @@ const roundAll = (record: Record<string, number>): Record<string, number> =>
|
|
|
83
80
|
Object.fromEntries(Object.entries(record).map(([k, v]) => [k, round(v)]))
|
|
84
81
|
|
|
85
82
|
interface Tally {
|
|
86
|
-
readonly
|
|
87
|
-
readonly continuations: Record<string, number>
|
|
83
|
+
readonly builds: { decided: number; landed: number; rebuilt: number; refactored: number }
|
|
88
84
|
readonly byModel: Record<string, number>
|
|
89
85
|
readonly byPhase: Record<Phase, number>
|
|
90
86
|
readonly doubleWork: { naive: number; rebuild: number }
|
|
@@ -93,8 +89,7 @@ interface Tally {
|
|
|
93
89
|
}
|
|
94
90
|
|
|
95
91
|
const emptyTally = (): Tally => ({
|
|
96
|
-
|
|
97
|
-
continuations: record(),
|
|
92
|
+
builds: { decided: 0, landed: 0, rebuilt: 0, refactored: 0 },
|
|
98
93
|
byModel: record(),
|
|
99
94
|
byPhase: { naive: 0, refactor: 0, implement: 0, other: 0 },
|
|
100
95
|
doubleWork: { naive: 0, rebuild: 0 },
|
|
@@ -106,9 +101,8 @@ const sum = (record: Readonly<Record<string, number>>): number =>
|
|
|
106
101
|
Object.values(record).reduce((a, b) => a + b, 0)
|
|
107
102
|
|
|
108
103
|
const finish = (t: Tally): Metrics => ({
|
|
109
|
-
|
|
110
|
-
refactorFirstRate: t.
|
|
111
|
-
continuations: t.continuations,
|
|
104
|
+
builds: t.builds,
|
|
105
|
+
refactorFirstRate: t.builds.decided === 0 ? null : round(t.builds.rebuilt / t.builds.decided, 3),
|
|
112
106
|
cost: {
|
|
113
107
|
total: round(sum(t.byModel)),
|
|
114
108
|
byModel: roundAll(t.byModel),
|
|
@@ -123,8 +117,8 @@ const finish = (t: Tally): Metrics => ({
|
|
|
123
117
|
flaky: { total: sum(t.flaky), byStep: t.flaky },
|
|
124
118
|
})
|
|
125
119
|
|
|
126
|
-
/** The open
|
|
127
|
-
interface
|
|
120
|
+
/** The open build: its naive cost so far, and whether its implement turns rebuild it. */
|
|
121
|
+
interface Build {
|
|
128
122
|
naive: number
|
|
129
123
|
rebuilding: boolean
|
|
130
124
|
/** Landed, not yet applied, with its naive cost: an apply that fails turns it into a rebuild. */
|
|
@@ -133,7 +127,7 @@ interface Slice {
|
|
|
133
127
|
|
|
134
128
|
const tallyCost = (
|
|
135
129
|
t: Tally,
|
|
136
|
-
open:
|
|
130
|
+
open: Build,
|
|
137
131
|
costs: ReturnType<typeof parseCommitMessage>["cost"],
|
|
138
132
|
phase: Phase,
|
|
139
133
|
): void => {
|
|
@@ -145,50 +139,48 @@ const tallyCost = (
|
|
|
145
139
|
}
|
|
146
140
|
}
|
|
147
141
|
|
|
148
|
-
/** A
|
|
149
|
-
const tallyDecision = (t: Tally, open:
|
|
150
|
-
// The
|
|
142
|
+
/** A build is decided where its turns leave the seam: for `implement.applying` the naive pass landed — unless it fails to apply and is built afresh — anywhere else it is rebuilt. */
|
|
143
|
+
const tallyDecision = (t: Tally, open: Build, at: string, to: string): void => {
|
|
144
|
+
// The build's closing ends its rebuild: later implement turns (an amend after review) are new work.
|
|
151
145
|
if (at === "implement.closing") open.rebuilding = false
|
|
152
146
|
if (open.applying !== undefined && at === "implement.applying") {
|
|
153
147
|
const naive = open.applying
|
|
154
148
|
open.applying = undefined
|
|
155
149
|
if (to !== "implement.building") return
|
|
156
|
-
t.
|
|
150
|
+
t.builds.landed--
|
|
157
151
|
rebuild(t, open, naive)
|
|
158
152
|
return
|
|
159
153
|
}
|
|
160
154
|
if (!inSeam(at) || inSeam(to)) return
|
|
161
|
-
t.
|
|
155
|
+
t.builds.decided++
|
|
162
156
|
const naive = open.naive
|
|
163
157
|
open.naive = 0
|
|
164
158
|
if (to === "implement.applying") {
|
|
165
159
|
open.applying = naive
|
|
166
160
|
open.rebuilding = false
|
|
167
|
-
t.
|
|
161
|
+
t.builds.landed++
|
|
168
162
|
return
|
|
169
163
|
}
|
|
170
164
|
rebuild(t, open, naive)
|
|
171
|
-
if (at === "seam.note") t.
|
|
165
|
+
if (at === "seam.note") t.builds.refactored++
|
|
172
166
|
}
|
|
173
167
|
|
|
174
|
-
const rebuild = (t: Tally, open:
|
|
168
|
+
const rebuild = (t: Tally, open: Build, naive: number): void => {
|
|
175
169
|
open.rebuilding = true
|
|
176
|
-
t.
|
|
170
|
+
t.builds.rebuilt++
|
|
177
171
|
t.doubleWork.naive += naive
|
|
178
172
|
}
|
|
179
173
|
|
|
180
174
|
/** Oldest first; a commit gtd did not write is skipped. */
|
|
181
175
|
export const processMetrics = (commits: readonly MetricCommit[]): Metrics => {
|
|
182
176
|
const t = emptyTally()
|
|
183
|
-
const open:
|
|
177
|
+
const open: Build = { naive: 0, rebuilding: false, applying: undefined }
|
|
184
178
|
for (const commit of commits) {
|
|
185
179
|
const message = parseCommitMessage(commit.message)
|
|
186
180
|
const subject = message.parsed
|
|
187
181
|
if (subject === undefined) continue
|
|
188
182
|
const at = subject.from ?? subject.to
|
|
189
183
|
tallyCost(t, open, message.cost, phaseOf(at))
|
|
190
|
-
if (subject.actor === "agent" && CONTINUE.test(at))
|
|
191
|
-
add(t.continuations, at.replace(CONTINUE, "$1"), 1)
|
|
192
184
|
const lens = LENS.exec(at)?.[1]
|
|
193
185
|
if (lens !== undefined && commit.findings > 0) add(t.byLens, lens, commit.findings)
|
|
194
186
|
if (message.marks.some((m) => m.name === "flaky")) add(t.flaky, at, 1)
|
|
@@ -200,9 +192,8 @@ export const processMetrics = (commits: readonly MetricCommit[]): Metrics => {
|
|
|
200
192
|
export const totalMetrics = (all: readonly Metrics[]): Metrics & { readonly processes: number } => {
|
|
201
193
|
const t = emptyTally()
|
|
202
194
|
for (const m of all) {
|
|
203
|
-
for (const key of Object.keys(t.
|
|
204
|
-
t.
|
|
205
|
-
for (const [k, v] of Object.entries(m.continuations)) add(t.continuations, k, v)
|
|
195
|
+
for (const key of Object.keys(t.builds) as (keyof Tally["builds"])[])
|
|
196
|
+
t.builds[key] += m.builds[key]
|
|
206
197
|
for (const [k, v] of Object.entries(m.cost.byModel)) add(t.byModel, k, v)
|
|
207
198
|
for (const key of Object.keys(t.byPhase) as Phase[]) t.byPhase[key] += m.cost.byPhase[key]
|
|
208
199
|
t.doubleWork.naive += m.cost.doubleWork.naive
|
|
@@ -256,12 +247,10 @@ const listed = (record: Readonly<Record<string, number>>): string =>
|
|
|
256
247
|
.join(", ")
|
|
257
248
|
|
|
258
249
|
const line = (label: string, m: Metrics): string => {
|
|
259
|
-
const {
|
|
250
|
+
const { builds, cost } = m
|
|
260
251
|
const percent = m.refactorFirstRate === null ? "-" : `${Math.round(m.refactorFirstRate * 100)}%`
|
|
261
|
-
const continued = sum(m.continuations)
|
|
262
252
|
return [
|
|
263
|
-
`${label}: ${
|
|
264
|
-
`${continued} continuations${continued > 0 ? ` (${listed(m.continuations)})` : ""}`,
|
|
253
|
+
`${label}: ${builds.decided} builds, ${builds.rebuilt} rebuilt (refactor-first ${percent}), ${builds.refactored} refactored`,
|
|
265
254
|
`cost ${cost.total} (${listed(cost.byPhase)}; by model ${listed(cost.byModel) || "-"}), double work ${cost.doubleWork.total}`,
|
|
266
255
|
`${m.findings.total} findings${m.findings.total > 0 ? ` (${listed(m.findings.byLens)})` : ""}`,
|
|
267
256
|
`${m.flaky.total} flaky${m.flaky.total > 0 ? ` (${listed(m.flaky.byStep)})` : ""}`,
|
package/src/cli/Program.test.ts
CHANGED
|
@@ -1677,7 +1677,6 @@ describe("runCommand — refuses in a repository with no commits", () => {
|
|
|
1677
1677
|
start: { kind: "start", actor: "human", workflow: "feature", vars: {}, label: "" },
|
|
1678
1678
|
door: { kind: "door", name: "fix", args: [] },
|
|
1679
1679
|
doors: { kind: "doors" },
|
|
1680
|
-
slices: { kind: "slices" },
|
|
1681
1680
|
abandon: { kind: "abandon" },
|
|
1682
1681
|
restore: { kind: "restore" },
|
|
1683
1682
|
next: { kind: "next" },
|
|
@@ -1685,7 +1684,6 @@ describe("runCommand — refuses in a repository with no commits", () => {
|
|
|
1685
1684
|
check: { kind: "check", mode: "qa", file: ".gtd/TODO.md" },
|
|
1686
1685
|
uncheck: { kind: "uncheck", file: ".gtd/REVIEW.md" },
|
|
1687
1686
|
install: { kind: "install" },
|
|
1688
|
-
contextHook: { kind: "contextHook" },
|
|
1689
1687
|
summary: { kind: "summary" },
|
|
1690
1688
|
base: { kind: "base" },
|
|
1691
1689
|
judge: { kind: "judge" },
|
|
@@ -1706,14 +1704,13 @@ describe("runCommand — refuses in a repository with no commits", () => {
|
|
|
1706
1704
|
const NO_COMMITS_MESSAGE =
|
|
1707
1705
|
"gtd requires a repository with at least one commit — make an initial commit, then run gtd again"
|
|
1708
1706
|
|
|
1709
|
-
it("derives exactly the
|
|
1707
|
+
it("derives exactly the eighteen non-standalone kinds — a canary for the table-driven cases below", () => {
|
|
1710
1708
|
expect(stateKinds.sort()).toEqual(
|
|
1711
1709
|
[
|
|
1712
1710
|
"abandon",
|
|
1713
1711
|
"base",
|
|
1714
1712
|
"door",
|
|
1715
1713
|
"doors",
|
|
1716
|
-
"slices",
|
|
1717
1714
|
"start",
|
|
1718
1715
|
"land",
|
|
1719
1716
|
"next",
|
|
@@ -1856,37 +1853,6 @@ export const summary = (c) => \`entry=\${c.entryCommit} tip=\${c.processTip} hum
|
|
|
1856
1853
|
})
|
|
1857
1854
|
})
|
|
1858
1855
|
|
|
1859
|
-
describe("gtd summary --slice", () => {
|
|
1860
|
-
const SLICED = `import { agent, human, slice } from "@pmelab/gtd/flows"
|
|
1861
|
-
|
|
1862
|
-
export default async () => {
|
|
1863
|
-
await human("idle", { message: "hi" })
|
|
1864
|
-
await slice("build", async () => {
|
|
1865
|
-
await agent("working", "go")
|
|
1866
|
-
})
|
|
1867
|
-
await slice("verify", async () => {
|
|
1868
|
-
await agent("polishing", "polish")
|
|
1869
|
-
})
|
|
1870
|
-
}
|
|
1871
|
-
|
|
1872
|
-
export const summary = (c) => \`\${c.slice ? c.slice.name + " " + c.slice.index + "/" + c.slice.count : "plain"} \${c.processBase}..\${c.processTip}\`
|
|
1873
|
-
`
|
|
1874
|
-
|
|
1875
|
-
it("a version warning goes to stderr, stdout still carries the prompt", async () => {
|
|
1876
|
-
const repo = seed(SLICED)
|
|
1877
|
-
await landTurn(repo, { "NOTE.md": "a note\n" })
|
|
1878
|
-
await landTurn(repo, { "WORK.md": "done\n" })
|
|
1879
|
-
repo.writeFile("POLISH.md", "ok\n")
|
|
1880
|
-
repo.seedCommit(
|
|
1881
|
-
"gtd(agent): polishing → idle\n\nGtd-Step: polishing#1\nGtd-Version: 0.0.1\nGtd-Format: 1",
|
|
1882
|
-
)
|
|
1883
|
-
const { exitCode, stdout, stderr } = await run(repo, "summary", "--slice", "verify")
|
|
1884
|
-
expect(exitCode).toBe(0)
|
|
1885
|
-
expect(stderr).toContain("recorded by gtd 0.0.1")
|
|
1886
|
-
expect(stdout).toContain("verify 2/2")
|
|
1887
|
-
})
|
|
1888
|
-
})
|
|
1889
|
-
|
|
1890
1856
|
describe("gtd base — prints the review anchor hash", () => {
|
|
1891
1857
|
// Each round's review window starts at the commit that entered `deciding`;
|
|
1892
1858
|
// FEEDBACK.md sends the process round again, a clean accept finishes it.
|
package/src/cli/Program.ts
CHANGED
|
@@ -43,8 +43,6 @@ import {
|
|
|
43
43
|
type StatusChange,
|
|
44
44
|
selectPath,
|
|
45
45
|
EXIT_OK,
|
|
46
|
-
renderSlicesJson,
|
|
47
|
-
slicesDocument,
|
|
48
46
|
} from "../wire/index.js"
|
|
49
47
|
import {
|
|
50
48
|
currentRest,
|
|
@@ -53,7 +51,6 @@ import {
|
|
|
53
51
|
renderRest,
|
|
54
52
|
restAt,
|
|
55
53
|
previewLanding,
|
|
56
|
-
processSlices,
|
|
57
54
|
noProcessUnderway,
|
|
58
55
|
restIsIdle,
|
|
59
56
|
reviewBaseFor,
|
|
@@ -148,7 +145,6 @@ export type Command =
|
|
|
148
145
|
}
|
|
149
146
|
| { readonly kind: "door"; readonly name: string; readonly args: readonly string[] }
|
|
150
147
|
| { readonly kind: "doors" }
|
|
151
|
-
| { readonly kind: "slices" }
|
|
152
148
|
| { readonly kind: "abandon" }
|
|
153
149
|
| { readonly kind: "restore" }
|
|
154
150
|
| { readonly kind: "next" }
|
|
@@ -163,13 +159,7 @@ export type Command =
|
|
|
163
159
|
}
|
|
164
160
|
| { readonly kind: "uncheck"; readonly file: string }
|
|
165
161
|
| { readonly kind: "install" }
|
|
166
|
-
| {
|
|
167
|
-
readonly kind: "contextHook"
|
|
168
|
-
readonly soft?: number
|
|
169
|
-
readonly hard?: number
|
|
170
|
-
readonly window?: number
|
|
171
|
-
}
|
|
172
|
-
| { readonly kind: "summary"; readonly slice?: string }
|
|
162
|
+
| { readonly kind: "summary" }
|
|
173
163
|
| { readonly kind: "base" }
|
|
174
164
|
| { readonly kind: "judge" }
|
|
175
165
|
| { readonly kind: "judgeAnswer" }
|
|
@@ -337,21 +327,12 @@ const planLanding = (
|
|
|
337
327
|
* closes or sits inside its own closing message. Writes nothing — no git, no
|
|
338
328
|
* state transition, no file. Refuses (throws, mapped to the runtime-error
|
|
339
329
|
* exit code) when the workflow declares no `summary` prompt, or the resolved
|
|
340
|
-
* run has no commits to name.
|
|
341
|
-
* (listing the valid slices), a config edited since the process closed, and a
|
|
342
|
-
* replay that diverged. The edge never narrates: the CLI prints the
|
|
343
|
-
* version warning, before the unknown-slice refusal; a replay refusal carries
|
|
344
|
-
* it in its message.
|
|
330
|
+
* run has no commits to name.
|
|
345
331
|
*/
|
|
346
|
-
const runSummaryCommand = (
|
|
347
|
-
out: ArtifactOut,
|
|
348
|
-
sliceName: string | undefined,
|
|
349
|
-
): Effect.Effect<void, Error, CommandRequirements> =>
|
|
332
|
+
const runSummaryCommand = (out: ArtifactOut): Effect.Effect<void, Error, CommandRequirements> =>
|
|
350
333
|
Effect.gen(function* () {
|
|
351
334
|
const run = yield* summaryRun
|
|
352
|
-
const { prompt
|
|
353
|
-
if (versionWarning !== undefined) yield* (yield* Narrator).warn(versionWarning)
|
|
354
|
-
if (unknownSlice !== undefined) return yield* Effect.fail(new Error(unknownSlice))
|
|
335
|
+
const { prompt } = yield* summaryFor(run)
|
|
355
336
|
if (prompt === undefined) {
|
|
356
337
|
return yield* Effect.fail(
|
|
357
338
|
new Error(
|
|
@@ -445,24 +426,6 @@ const readStdin = (): Effect.Effect<string, Error> =>
|
|
|
445
426
|
catch: (e) => (e instanceof Error ? e : new Error(String(e))),
|
|
446
427
|
})
|
|
447
428
|
|
|
448
|
-
/** Stdout is written once: a hook reads one JSON document, or none. */
|
|
449
|
-
const runContextHookCommand = (
|
|
450
|
-
command: Extract<Command, { kind: "contextHook" }>,
|
|
451
|
-
out: ArtifactOut,
|
|
452
|
-
): Effect.Effect<void, Error, ToolCommands> =>
|
|
453
|
-
Effect.gen(function* () {
|
|
454
|
-
const tools = yield* ToolCommands
|
|
455
|
-
const input = yield* readStdin()
|
|
456
|
-
out.write(
|
|
457
|
-
yield* tools.contextHook({
|
|
458
|
-
input,
|
|
459
|
-
...(command.soft !== undefined ? { soft: command.soft } : {}),
|
|
460
|
-
...(command.hard !== undefined ? { hard: command.hard } : {}),
|
|
461
|
-
...(command.window !== undefined ? { window: command.window } : {}),
|
|
462
|
-
}),
|
|
463
|
-
)
|
|
464
|
-
})
|
|
465
|
-
|
|
466
429
|
/** Stdout is written once, after the answerer resolves — a rejection leaves it empty. */
|
|
467
430
|
const runJudgeRunCommand = (
|
|
468
431
|
command: Extract<Command, { kind: "judgeRun" }>,
|
|
@@ -701,22 +664,6 @@ const runDoorsCommand = (
|
|
|
701
664
|
else yield* writeSelection(out, document, json.path)
|
|
702
665
|
})
|
|
703
666
|
|
|
704
|
-
const runSlicesCommand = (
|
|
705
|
-
json: JsonMode,
|
|
706
|
-
out: ArtifactOut,
|
|
707
|
-
): Effect.Effect<void, Error, CommandRequirements> =>
|
|
708
|
-
Effect.gen(function* () {
|
|
709
|
-
const { slices, versionWarning } = yield* processSlices
|
|
710
|
-
if (versionWarning !== undefined) yield* (yield* Narrator).warn(versionWarning)
|
|
711
|
-
if (json.kind === "off") {
|
|
712
|
-
out.write(slices.map((s) => `${s.name} ${s.base}..${s.tip}\n`).join(""))
|
|
713
|
-
return
|
|
714
|
-
}
|
|
715
|
-
const document = slicesDocument(slices)
|
|
716
|
-
if (json.kind === "document") out.write(renderSlicesJson(document))
|
|
717
|
-
else yield* writeSelection(out, document, json.path)
|
|
718
|
-
})
|
|
719
|
-
|
|
720
667
|
/**
|
|
721
668
|
* Ends the process underway without completing it. Nothing is discarded: a
|
|
722
669
|
* `git reset --mixed` to the process's start commit leaves every turn
|
|
@@ -1217,7 +1164,6 @@ export const needsOf = (kind: Command["kind"]): Needs => {
|
|
|
1217
1164
|
switch (kind) {
|
|
1218
1165
|
case "lsp":
|
|
1219
1166
|
case "install":
|
|
1220
|
-
case "contextHook":
|
|
1221
1167
|
case "judgeRun":
|
|
1222
1168
|
case "strays":
|
|
1223
1169
|
return "none"
|
|
@@ -1235,7 +1181,6 @@ export const standaloneKinds = (): readonly Command["kind"][] => [
|
|
|
1235
1181
|
"check",
|
|
1236
1182
|
"uncheck",
|
|
1237
1183
|
"install",
|
|
1238
|
-
"contextHook",
|
|
1239
1184
|
"judgeRun",
|
|
1240
1185
|
"strays",
|
|
1241
1186
|
]
|
|
@@ -1281,8 +1226,6 @@ const dispatchVoidCommand = (
|
|
|
1281
1226
|
return runDoorCommand(command.name, command.args, out)
|
|
1282
1227
|
case "doors":
|
|
1283
1228
|
return runDoorsCommand(json, out)
|
|
1284
|
-
case "slices":
|
|
1285
|
-
return runSlicesCommand(json, out)
|
|
1286
1229
|
case "abandon":
|
|
1287
1230
|
return runAbandonCommand(out)
|
|
1288
1231
|
case "restore":
|
|
@@ -1305,10 +1248,8 @@ const dispatchVoidCommand = (
|
|
|
1305
1248
|
return runUncheckCommand(command.file)
|
|
1306
1249
|
case "install":
|
|
1307
1250
|
return runInstallCommand(out)
|
|
1308
|
-
case "contextHook":
|
|
1309
|
-
return runContextHookCommand(command, out)
|
|
1310
1251
|
case "summary":
|
|
1311
|
-
return runSummaryCommand(out
|
|
1252
|
+
return runSummaryCommand(out)
|
|
1312
1253
|
case "base":
|
|
1313
1254
|
return runBaseCommand(out)
|
|
1314
1255
|
case "judge":
|
package/src/cli/ToolCommands.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Context } from "effect"
|
|
2
2
|
import type { Effect } from "effect"
|
|
3
3
|
import type { UiSettings } from "../flows/index.js"
|
|
4
|
-
import type { ContextHookRequest } from "../wire/index.js"
|
|
5
4
|
|
|
6
5
|
/** How to run THIS gtd — a tool (the LSP) never reads a different version's state than the one it ships with. */
|
|
7
6
|
export type SelfGtd = readonly [string, ...string[]]
|
|
@@ -38,8 +37,6 @@ export class ToolCommands extends Context.Tag("ToolCommands")<
|
|
|
38
37
|
out: { readonly write: (chunk: string) => void; readonly flush: () => void },
|
|
39
38
|
) => Effect.Effect<void, Error>
|
|
40
39
|
readonly lsp: (gtd: SelfGtd) => Effect.Effect<void, Error>
|
|
41
|
-
/** The hook's stdout: a JSON document, or empty to let the turn go on. */
|
|
42
|
-
readonly contextHook: (request: ContextHookRequest) => Effect.Effect<string, Error>
|
|
43
40
|
readonly judge: (request: JudgeRequest) => Promise<string>
|
|
44
41
|
}
|
|
45
42
|
>() {}
|