@pmelab/gtd 59.0.0 → 60.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 +42 -38
- package/claude/hooks/doors.ts +8 -4
- package/dist/chunks/flows-DENt0uWV.mjs +707 -0
- package/dist/chunks/{steering-CCNvngSH.mjs → steering-DF_BMadH.mjs} +292 -289
- package/dist/chunks/{testkit-CMBAAyRK.mjs → testkit-BvecVjQ0.mjs} +937 -772
- package/dist/chunks/{wire-C12dahrH.mjs → wire-DZ8R1RU9.mjs} +9 -2
- package/dist/flows.mjs +2 -676
- package/dist/gtd.bundle.mjs +1101 -1025
- package/dist/steering.mjs +2 -2
- package/dist/testkit/gherkin.mjs +371 -82
- package/dist/testkit.mjs +3 -2
- package/dist/web/main.js +234 -232
- package/dist/wire.mjs +2 -2
- package/dist/workflow-kit.mjs +3 -3
- package/package.json +1 -1
- package/skills/authoring/SKILL.md +68 -61
- package/src/cli/Cli.test.ts +311 -229
- package/src/cli/Cli.ts +486 -585
- package/src/cli/Help.test.ts +47 -0
- package/src/cli/Help.ts +70 -0
- package/src/cli/History.ts +9 -4
- package/src/cli/Judges.ts +1 -1
- package/src/cli/Output.test.ts +35 -0
- package/src/cli/Output.ts +31 -1
- package/src/cli/Program.test.ts +131 -82
- package/src/cli/Program.ts +197 -111
- package/src/edge/Edge.test.ts +64 -56
- package/src/edge/Edge.ts +4 -4
- package/src/emit/Emit.test.ts +1 -1
- package/src/emit/Emit.ts +1 -1
- package/src/flows/factory.test.ts +14 -0
- package/src/flows/factory.ts +25 -6
- package/src/flows/helpers.test.ts +35 -0
- package/src/flows/helpers.ts +19 -10
- package/src/flows/index.test.ts +66 -0
- package/src/flows/index.ts +109 -5
- package/src/flows/internal.ts +7 -0
- package/src/flows/runtime.test.ts +192 -1
- package/src/flows/runtime.ts +188 -54
- package/src/gherkin/git-control.feature +6 -6
- package/src/gherkin/index.test.ts +127 -0
- package/src/gherkin/index.ts +481 -98
- package/src/gherkin/messages.test.ts +124 -0
- package/src/gherkin/messages.ts +101 -0
- package/src/gherkin/more-steps.feature +170 -0
- package/src/gherkin/scenario-end.feature +101 -0
- package/src/gherkin/testkit.feature +83 -7
- package/src/install/Install.test.ts +7 -7
- package/src/install/Install.ts +8 -8
- package/src/replay/Replay.test.ts +13 -2
- package/src/replay/Replay.ts +19 -30
- package/src/replay/judgeDiffShape.test.ts +1 -1
- package/src/steering/Footnotes.test.ts +1 -1
- package/src/steering/SteeringFormats.test.ts +1 -1
- package/src/steering/freeform.test.ts +1 -1
- package/src/steering/index.test.ts +27 -1
- package/src/steering/index.ts +6 -104
- package/src/steering/internal.ts +11 -0
- package/src/steering/qa.test.ts +7 -2
- package/src/steering/reads.test.ts +42 -0
- package/src/steering/reads.ts +43 -0
- package/src/steering/registry.ts +93 -0
- package/src/steering/review.test.ts +2 -2
- package/src/step/planStart.test.ts +18 -16
- package/src/step/planStart.ts +9 -9
- package/src/testkit/TestRepo.test.ts +289 -6
- package/src/testkit/TestRepo.ts +317 -41
- package/src/testkit/imports.test.ts +58 -0
- package/src/testkit/imports.ts +102 -0
- package/src/testkit/index.test.ts +28 -0
- package/src/testkit/index.ts +14 -2
- package/src/twins/EmittedScriptRecognizer.ts +7 -14
- package/src/twins/EmittedScripts.test.ts +4 -2
- package/src/twins/InMemRepo.ts +1 -0
- package/src/wire/Land.ts +1 -1
- package/src/wire/Script.ts +11 -0
- package/src/wire/WireSchema.test.ts +3 -0
- package/src/wire/golden/1/script.json +4 -0
- package/src/wire/index.ts +1 -0
- package/src/workflow/SteeringFormats.test.ts +1 -1
- package/src/workflow/compile.ts +2 -2
- package/src/workflow/discovery.test.ts +3 -3
- package/src/workflow/load.test.ts +256 -116
- package/src/workflow/load.ts +175 -152
- package/src/workflow/skills.test.ts +5 -5
- package/src/workflow/skills.ts +1 -1
- package/src/workflow-kit/index.test.ts +25 -0
- package/src/workflow-kit/index.ts +37 -4
- package/src/workflow-kit/review.ts +2 -2
package/src/cli/Program.test.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* e2e tier observes. Argv parsing is pinned in `src/cli/Cli.test.ts`.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { Cause, Effect, Exit } from "effect"
|
|
7
|
+
import { Cause, Context, Effect, Exit } from "effect"
|
|
8
8
|
import { PassThrough } from "node:stream"
|
|
9
9
|
import { afterEach, describe, expect, it, vi } from "vitest"
|
|
10
10
|
|
|
@@ -17,7 +17,7 @@ vi.mock("node:os", async (importOriginal) => ({
|
|
|
17
17
|
|
|
18
18
|
import { runCli, type Command } from "./index.js"
|
|
19
19
|
import { EXIT_USAGE_ERROR, stallDiagnosis, noopText, WIRE_SCHEMA } from "../wire/index.js"
|
|
20
|
-
import { needsOf, runCommand, SelectorUsageError } from "./Program.js"
|
|
20
|
+
import { needsOf, runCommand, SelectorUsageError, type CommandRequirements } from "./Program.js"
|
|
21
21
|
import { InMemRepo, makeCapturingCliIo, testLayers, applyEmittedScript } from "../twins/index.js"
|
|
22
22
|
import {
|
|
23
23
|
commitAll,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
noteOutcome,
|
|
27
27
|
restoredOutcome,
|
|
28
28
|
} from "../emit/index.js"
|
|
29
|
-
import { HISTORY_REF } from "../platform/index.js"
|
|
29
|
+
import { GitService, HISTORY_REF, Host } from "../platform/index.js"
|
|
30
30
|
import { contentHash } from "../workflow/index.js"
|
|
31
31
|
import { WORKFLOWS_REPO } from "../install/index.js"
|
|
32
32
|
|
|
@@ -81,25 +81,26 @@ const seedIdle = (): InMemRepo => {
|
|
|
81
81
|
return repo
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
describe("gtd
|
|
85
|
-
const WORKFLOW = `import { agent, human } from "@pmelab/gtd/flows"
|
|
84
|
+
describe("gtd start <name> — a custom workflow's named workflow", () => {
|
|
85
|
+
const WORKFLOW = `import { agent, human, workflow } from "@pmelab/gtd/flows"
|
|
86
86
|
|
|
87
87
|
export default async () => {
|
|
88
88
|
await human("idle", { message: "hi" })
|
|
89
89
|
await agent("working", "go")
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
export const hotfix =
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
export const hotfix = workflow(
|
|
93
|
+
async () => {
|
|
94
|
+
await agent("working", "go")
|
|
95
|
+
},
|
|
96
|
+
{ defaults: { greeting: "hello" } },
|
|
97
|
+
)
|
|
97
98
|
`
|
|
98
99
|
|
|
99
100
|
it("the happy path emits the opening commit's script — gtd itself writes nothing", async () => {
|
|
100
101
|
const repo = seed(WORKFLOW)
|
|
101
102
|
const before = repo.commitHistory().length
|
|
102
|
-
const { stdout, exitCode } = await run(repo, "
|
|
103
|
+
const { stdout, exitCode } = await run(repo, "start", "hotfix")
|
|
103
104
|
expect(exitCode).toBe(0)
|
|
104
105
|
expect(stdout).toContain(
|
|
105
106
|
commitAll(
|
|
@@ -109,21 +110,33 @@ export const defaults = { greeting: "hello" }
|
|
|
109
110
|
expect(repo.commitHistory()).toHaveLength(before)
|
|
110
111
|
})
|
|
111
112
|
|
|
112
|
-
it("an unknown
|
|
113
|
+
it("an unknown name is a usage error (exit 2) pointing at the list", async () => {
|
|
113
114
|
const repo = seed(WORKFLOW)
|
|
114
115
|
const before = repo.commitHistory().length
|
|
115
|
-
const { exitCode, stderr } = await run(repo, "
|
|
116
|
+
const { exitCode, stderr } = await run(repo, "start", "bogus")
|
|
116
117
|
expect(exitCode).toBe(2)
|
|
117
118
|
expect(repo.commitHistory()).toHaveLength(before)
|
|
118
|
-
expect(stderr).toContain('
|
|
119
|
-
|
|
119
|
+
expect(stderr).toContain('no workflow or door is named "bogus" — run `gtd start` to list them')
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
it("lists the paths its script commits as comments a path cannot break out of", async () => {
|
|
123
|
+
const repo = seed(WORKFLOW)
|
|
124
|
+
repo.writeFile("NOTE.md", "a note\n")
|
|
125
|
+
repo.writeFile("evil\nrm -rf x", "")
|
|
126
|
+
const { stdout, exitCode } = await run(repo, "start", "hotfix")
|
|
127
|
+
expect(exitCode).toBe(0)
|
|
128
|
+
expect(stdout).toContain("# commits every change in the working tree (git add -A):\n")
|
|
129
|
+
expect(stdout).toContain("# A NOTE.md\n")
|
|
130
|
+
expect(stdout).toContain("evil?rm -rf x")
|
|
131
|
+
expect(stdout).not.toMatch(/^rm -rf x/m)
|
|
132
|
+
expect(applyEmittedScript(repo, new Map(), stdout).ok).toBe(true)
|
|
120
133
|
})
|
|
121
134
|
|
|
122
135
|
it("`default` is not startable by name", async () => {
|
|
123
136
|
const repo = seed(WORKFLOW)
|
|
124
|
-
const { exitCode, stderr } = await run(repo, "
|
|
137
|
+
const { exitCode, stderr } = await run(repo, "start", "default")
|
|
125
138
|
expect(exitCode).toBe(2)
|
|
126
|
-
expect(stderr).toContain('
|
|
139
|
+
expect(stderr).toContain('no workflow or door is named "default"')
|
|
127
140
|
})
|
|
128
141
|
|
|
129
142
|
it("--entry is an ordinary unknown flag", async () => {
|
|
@@ -136,17 +149,18 @@ export const defaults = { greeting: "hello" }
|
|
|
136
149
|
const repo = seed(WORKFLOW)
|
|
137
150
|
await landTurn(repo, { ".gtd/TODO.md": "sketch\n" })
|
|
138
151
|
const before = repo.commitHistory().length
|
|
139
|
-
const { exitCode, stderr } = await run(repo, "
|
|
152
|
+
const { exitCode, stderr } = await run(repo, "start", "hotfix")
|
|
140
153
|
expect(exitCode).toBe(1)
|
|
141
154
|
expect(repo.commitHistory()).toHaveLength(before)
|
|
142
155
|
expect(stderr).toContain("already underway")
|
|
143
156
|
})
|
|
144
157
|
|
|
145
|
-
it("an undeclared --var name
|
|
158
|
+
it("an undeclared --var name is a usage error (exit 2), listing the declared names", async () => {
|
|
146
159
|
const repo = seed(WORKFLOW)
|
|
147
160
|
const before = repo.commitHistory().length
|
|
148
|
-
const { exitCode, stderr } = await run(repo, "
|
|
149
|
-
expect(exitCode).toBe(
|
|
161
|
+
const { exitCode, stderr, stdout } = await run(repo, "start", "hotfix", "--var", "bogus=1")
|
|
162
|
+
expect(exitCode).toBe(2)
|
|
163
|
+
expect(stdout).toBe("")
|
|
150
164
|
expect(repo.commitHistory()).toHaveLength(before)
|
|
151
165
|
expect(stderr).toContain("bogus")
|
|
152
166
|
expect(stderr).toContain("greeting")
|
|
@@ -155,7 +169,7 @@ export const defaults = { greeting: "hello" }
|
|
|
155
169
|
it("a declared --var override renders as a Gtd-Var trailer in the emitted commit script", async () => {
|
|
156
170
|
const repo = seed(WORKFLOW)
|
|
157
171
|
const before = repo.commitHistory().length
|
|
158
|
-
const { exitCode, stdout } = await run(repo, "
|
|
172
|
+
const { exitCode, stdout } = await run(repo, "start", "hotfix", "--var", "greeting=world")
|
|
159
173
|
expect(exitCode).toBe(0)
|
|
160
174
|
expect(stdout).toContain("Gtd-Var: greeting=world")
|
|
161
175
|
expect(repo.commitHistory()).toHaveLength(before)
|
|
@@ -165,7 +179,7 @@ export const defaults = { greeting: "hello" }
|
|
|
165
179
|
const repo = seed(WORKFLOW)
|
|
166
180
|
repo.writeFile("scratch.txt", "uncommitted\n")
|
|
167
181
|
const before = repo.commitHistory().length
|
|
168
|
-
const { exitCode, stdout } = await run(repo, "
|
|
182
|
+
const { exitCode, stdout } = await run(repo, "start", "hotfix")
|
|
169
183
|
expect(exitCode).toBe(0)
|
|
170
184
|
expect(stdout.length).toBeGreaterThan(0)
|
|
171
185
|
expect(repo.commitHistory()).toHaveLength(before)
|
|
@@ -173,7 +187,7 @@ export const defaults = { greeting: "hello" }
|
|
|
173
187
|
|
|
174
188
|
it("applying the start script opens the workflow's episode at its first step", async () => {
|
|
175
189
|
const repo = seed(WORKFLOW)
|
|
176
|
-
const { stdout } = await run(repo, "
|
|
190
|
+
const { stdout } = await run(repo, "start", "hotfix")
|
|
177
191
|
expect(applyEmittedScript(repo, new Map(), stdout).ok).toBe(true)
|
|
178
192
|
expect(repo.lastCommitSubject()).toBe("gtd(human): working")
|
|
179
193
|
const next = JSON.parse((await run(repo, "next", "--json")).stdout) as Record<string, unknown>
|
|
@@ -184,7 +198,7 @@ export const defaults = { greeting: "hello" }
|
|
|
184
198
|
|
|
185
199
|
it("a pinned workflow the config no longer defines is refused with the abandon advice", async () => {
|
|
186
200
|
const repo = seed(WORKFLOW)
|
|
187
|
-
const { stdout } = await run(repo, "
|
|
201
|
+
const { stdout } = await run(repo, "start", "hotfix")
|
|
188
202
|
applyEmittedScript(repo, new Map(), stdout)
|
|
189
203
|
repo.writeFile("gtd.config.ts", WORKFLOW.replace("export const hotfix", "const hotfix"))
|
|
190
204
|
const { exitCode, stderr } = await run(repo, "next")
|
|
@@ -194,18 +208,16 @@ export const defaults = { greeting: "hello" }
|
|
|
194
208
|
})
|
|
195
209
|
})
|
|
196
210
|
|
|
197
|
-
describe("gtd
|
|
211
|
+
describe("gtd guide", () => {
|
|
198
212
|
it("where no gtd.config.ts governs, prints the briefing and warns where to get a workflow — writing nothing", async () => {
|
|
199
213
|
const repo = new InMemRepo()
|
|
200
214
|
repo.writeFile("README.md", "# test project\n")
|
|
201
215
|
repo.seedCommit("chore: init")
|
|
202
216
|
const before = repo.snapshot()
|
|
203
|
-
const { stdout, stderr, exitCode } = await run(repo, "
|
|
217
|
+
const { stdout, stderr, exitCode } = await run(repo, "guide")
|
|
204
218
|
expect(exitCode).toBe(0)
|
|
205
219
|
expect(stdout).toMatch(/Make sure a .gtd\.config\.ts. governs the repository/)
|
|
206
|
-
expect(stderr).toContain(
|
|
207
|
-
"gtd install: no gtd.config.ts governs here, and gtd ships no workflow",
|
|
208
|
-
)
|
|
220
|
+
expect(stderr).toContain("gtd guide: no gtd.config.ts governs here, and gtd ships no workflow")
|
|
209
221
|
expect(stderr).toContain(WORKFLOWS_REPO)
|
|
210
222
|
expect(repo.hasPath("gtd.config.ts")).toBe(false)
|
|
211
223
|
expect(repo.snapshot()).toBe(before)
|
|
@@ -213,42 +225,35 @@ describe("gtd install", () => {
|
|
|
213
225
|
|
|
214
226
|
it("where a gtd.config.ts governs, prints the briefing alone", async () => {
|
|
215
227
|
const repo = seedIdle()
|
|
216
|
-
const { stdout, stderr, exitCode } = await run(repo, "
|
|
228
|
+
const { stdout, stderr, exitCode } = await run(repo, "guide")
|
|
217
229
|
expect(exitCode).toBe(0)
|
|
218
230
|
expect(stdout).toMatch(/Make sure a .gtd\.config\.ts. governs the repository/)
|
|
219
231
|
expect(stderr).not.toContain(WORKFLOWS_REPO)
|
|
220
232
|
})
|
|
221
233
|
})
|
|
222
234
|
|
|
223
|
-
describe("gtd
|
|
224
|
-
const WORKFLOW = `import { human } from "@pmelab/gtd/flows"
|
|
235
|
+
describe("gtd start <name> — a workflow that fixes its diff base", () => {
|
|
236
|
+
const WORKFLOW = `import { human, workflow } from "@pmelab/gtd/flows"
|
|
225
237
|
|
|
226
238
|
export default async () => {
|
|
227
239
|
await human("idle", { message: "hi" })
|
|
228
240
|
}
|
|
229
241
|
|
|
230
|
-
export const review =
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
export const base = (workflow, vars) => (workflow === "review" ? vars.reviewBase : undefined)
|
|
242
|
+
export const review = workflow(
|
|
243
|
+
async () => {
|
|
244
|
+
await human("checking", { message: "check" })
|
|
245
|
+
},
|
|
246
|
+
{ defaults: { reviewBase: "" }, base: (_workflow, vars) => vars.reviewBase },
|
|
247
|
+
)
|
|
237
248
|
`
|
|
238
249
|
|
|
239
|
-
it("
|
|
250
|
+
it("start review --var reviewBase=<base> emits a script anchoring the process to that base", async () => {
|
|
240
251
|
const repo = seed(WORKFLOW)
|
|
241
252
|
const base = repo.commitHistory().at(-1)!.hash
|
|
242
253
|
repo.writeFile("scratch.txt", "more work\n")
|
|
243
254
|
repo.seedCommit("chore: more work")
|
|
244
255
|
const before = repo.commitHistory().length
|
|
245
|
-
const { stdout, exitCode } = await run(
|
|
246
|
-
repo,
|
|
247
|
-
"--workflow",
|
|
248
|
-
"review",
|
|
249
|
-
"--var",
|
|
250
|
-
`reviewBase=${base}`,
|
|
251
|
-
)
|
|
256
|
+
const { stdout, exitCode } = await run(repo, "start", "review", "--var", `reviewBase=${base}`)
|
|
252
257
|
expect(exitCode).toBe(0)
|
|
253
258
|
expect(stdout).toContain("gtd(human): checking")
|
|
254
259
|
expect(stdout).toContain("Gtd-Workflow: review")
|
|
@@ -440,7 +445,7 @@ export default async () => {
|
|
|
440
445
|
})
|
|
441
446
|
|
|
442
447
|
describe("gtd next --json — stall detection (attempt commits)", () => {
|
|
443
|
-
const WORKFLOW = `import { agent, human, run } from "@pmelab/gtd/flows"
|
|
448
|
+
const WORKFLOW = `import { agent, human, run, workflow } from "@pmelab/gtd/flows"
|
|
444
449
|
|
|
445
450
|
const work = async () => {
|
|
446
451
|
await agent("working", "do the work described in NOTE.md")
|
|
@@ -452,7 +457,7 @@ export default async () => {
|
|
|
452
457
|
await work()
|
|
453
458
|
}
|
|
454
459
|
|
|
455
|
-
export const resume = work
|
|
460
|
+
export const resume = workflow(work)
|
|
456
461
|
`
|
|
457
462
|
|
|
458
463
|
const atWorking = async (): Promise<InMemRepo> => {
|
|
@@ -489,7 +494,7 @@ export const resume = work
|
|
|
489
494
|
|
|
490
495
|
it("a clean-tree entry commit resting at a prompt step is NOT a stall — the actor differs (human entered, agent acts)", async () => {
|
|
491
496
|
const repo = seed(WORKFLOW)
|
|
492
|
-
const { stdout: entry } = await run(repo, "
|
|
497
|
+
const { stdout: entry } = await run(repo, "start", "resume")
|
|
493
498
|
expect(applyEmittedScript(repo, new Map(), entry).ok).toBe(true)
|
|
494
499
|
const { stdout, exitCode } = await run(repo, "next", "--json")
|
|
495
500
|
expect(exitCode).toBe(0)
|
|
@@ -988,18 +993,19 @@ describe("gtd check <mode> <file>", () => {
|
|
|
988
993
|
)
|
|
989
994
|
})
|
|
990
995
|
|
|
991
|
-
it("an absent file
|
|
996
|
+
it("an absent file is a refusal (exit 1) naming it", async () => {
|
|
992
997
|
const repo = bareRepo()
|
|
993
|
-
const { stdout, exitCode } = await run(repo, "check", "qa", ".gtd/TODO.md")
|
|
994
|
-
expect(exitCode).toBe(
|
|
998
|
+
const { stdout, stderr, exitCode } = await run(repo, "check", "qa", ".gtd/TODO.md")
|
|
999
|
+
expect(exitCode).toBe(1)
|
|
995
1000
|
expect(stdout).toBe("")
|
|
1001
|
+
expect(stderr).toContain("gtd check: no such file: .gtd/TODO.md")
|
|
996
1002
|
})
|
|
997
1003
|
|
|
998
|
-
it("an unknown mode is a usage
|
|
1004
|
+
it("an unknown mode is a usage error (exit 2) naming the known modes", async () => {
|
|
999
1005
|
const repo = bareRepo()
|
|
1000
1006
|
repo.writeFile("TODO.md", validQaDoc)
|
|
1001
1007
|
const { stderr, exitCode } = await run(repo, "check", "bogus", "TODO.md")
|
|
1002
|
-
expect(exitCode).toBe(
|
|
1008
|
+
expect(exitCode).toBe(2)
|
|
1003
1009
|
expect(stderr).toContain('unknown mode "bogus"')
|
|
1004
1010
|
expect(stderr).toContain("qa")
|
|
1005
1011
|
expect(stderr).toContain("review")
|
|
@@ -1231,13 +1237,6 @@ describe("gtd check <mode> <file> --open-questions", () => {
|
|
|
1231
1237
|
expect(stderr).toContain(".gtd/TODO.md")
|
|
1232
1238
|
})
|
|
1233
1239
|
|
|
1234
|
-
it("check without the flag is unchanged: an absent file still exits 0 with no output", async () => {
|
|
1235
|
-
const repo = bareRepo()
|
|
1236
|
-
const { stdout, exitCode } = await run(repo, "check", "qa", ".gtd/TODO.md")
|
|
1237
|
-
expect(exitCode).toBe(0)
|
|
1238
|
-
expect(stdout).toBe("")
|
|
1239
|
-
})
|
|
1240
|
-
|
|
1241
1240
|
it("--json is out of scope for check --open-questions too — a usage error, never a JSON envelope", async () => {
|
|
1242
1241
|
const repo = bareRepo()
|
|
1243
1242
|
repo.writeFile(".gtd/TODO.md", docWithUnanswered)
|
|
@@ -1264,7 +1263,7 @@ describe("gtd check <mode> <file> --open-questions", () => {
|
|
|
1264
1263
|
"--open-questions",
|
|
1265
1264
|
".gtd/TODO.md",
|
|
1266
1265
|
)
|
|
1267
|
-
expect(exitCode).
|
|
1266
|
+
expect(exitCode).toBe(2)
|
|
1268
1267
|
expect(stderr).toContain('unknown mode "bogus"')
|
|
1269
1268
|
expect(stderr).toContain("qa")
|
|
1270
1269
|
expect(stderr).toContain("review")
|
|
@@ -1705,16 +1704,14 @@ describe("runCommand — refuses in a repository with no commits", () => {
|
|
|
1705
1704
|
lsp: { kind: "lsp" },
|
|
1706
1705
|
ui: { kind: "ui", selfSigned: false, dev: false },
|
|
1707
1706
|
land: { kind: "land" },
|
|
1708
|
-
start: { kind: "start",
|
|
1709
|
-
door: { kind: "door", name: "fix", args: [] },
|
|
1710
|
-
doors: { kind: "doors" },
|
|
1707
|
+
start: { kind: "start", name: "fix", args: [], vars: {} },
|
|
1711
1708
|
abandon: { kind: "abandon" },
|
|
1712
1709
|
restore: { kind: "restore" },
|
|
1713
1710
|
next: { kind: "next" },
|
|
1714
1711
|
validate: { kind: "validate" },
|
|
1715
1712
|
check: { kind: "check", mode: "qa", file: ".gtd/TODO.md" },
|
|
1716
1713
|
uncheck: { kind: "uncheck", file: ".gtd/REVIEW.md" },
|
|
1717
|
-
|
|
1714
|
+
guide: { kind: "guide" },
|
|
1718
1715
|
summary: { kind: "summary" },
|
|
1719
1716
|
base: { kind: "base" },
|
|
1720
1717
|
judge: { kind: "judge" },
|
|
@@ -1733,15 +1730,13 @@ describe("runCommand — refuses in a repository with no commits", () => {
|
|
|
1733
1730
|
)
|
|
1734
1731
|
|
|
1735
1732
|
const NO_COMMITS_MESSAGE =
|
|
1736
|
-
"gtd
|
|
1733
|
+
"gtd: the repository has no commits yet — make an initial commit, then run gtd again"
|
|
1737
1734
|
|
|
1738
|
-
it("derives exactly the
|
|
1735
|
+
it("derives exactly the sixteen non-standalone kinds — a canary for the table-driven cases below", () => {
|
|
1739
1736
|
expect(stateKinds.sort()).toEqual(
|
|
1740
1737
|
[
|
|
1741
1738
|
"abandon",
|
|
1742
1739
|
"base",
|
|
1743
|
-
"door",
|
|
1744
|
-
"doors",
|
|
1745
1740
|
"start",
|
|
1746
1741
|
"land",
|
|
1747
1742
|
"next",
|
|
@@ -1817,16 +1812,19 @@ describe("runCommand — refuses in a repository with no commits", () => {
|
|
|
1817
1812
|
})
|
|
1818
1813
|
|
|
1819
1814
|
describe("gtd summary", () => {
|
|
1820
|
-
const SUMMARY_WORKFLOW = `import { agent, human } from "@pmelab/gtd/flows"
|
|
1815
|
+
const SUMMARY_WORKFLOW = `import { agent, human, workflow } from "@pmelab/gtd/flows"
|
|
1821
1816
|
|
|
1822
|
-
export default
|
|
1817
|
+
export default workflow(
|
|
1818
|
+
async () => {
|
|
1823
1819
|
await human("idle", { message: "hi" })
|
|
1824
1820
|
await agent("working", "go")
|
|
1825
1821
|
await human("reviewing", { message: "check it" })
|
|
1826
1822
|
await agent("polishing", "polish")
|
|
1827
|
-
}
|
|
1828
|
-
|
|
1829
|
-
|
|
1823
|
+
},
|
|
1824
|
+
{
|
|
1825
|
+
summary: (c) => \`entry=\${c.entryCommit} tip=\${c.processTip} humans=\${c.humanCommits.length}\`,
|
|
1826
|
+
},
|
|
1827
|
+
)
|
|
1830
1828
|
`
|
|
1831
1829
|
|
|
1832
1830
|
const reviewed = async (): Promise<InMemRepo> => {
|
|
@@ -2118,14 +2116,15 @@ describe("gtd judge / gtd judge answer", () => {
|
|
|
2118
2116
|
const QUESTION = `{ id: "q1", primitive: "noul", instructions: "i", criteria: "c" }`
|
|
2119
2117
|
|
|
2120
2118
|
const judgeWorkflow = (evidence: string, then: string, vars = "{}"): string =>
|
|
2121
|
-
`import { agent, human, judge, read } from "@pmelab/gtd/flows"
|
|
2119
|
+
`import { agent, human, judge, read, workflow } from "@pmelab/gtd/flows"
|
|
2122
2120
|
|
|
2123
|
-
export default
|
|
2121
|
+
export default workflow(
|
|
2122
|
+
async () => {
|
|
2124
2123
|
await judge("idle", { questions: [${QUESTION}], evidence: ${evidence} })
|
|
2125
2124
|
await ${then}
|
|
2126
|
-
}
|
|
2127
|
-
|
|
2128
|
-
|
|
2125
|
+
},
|
|
2126
|
+
{ defaults: ${vars} },
|
|
2127
|
+
)
|
|
2129
2128
|
`
|
|
2130
2129
|
|
|
2131
2130
|
const seededRepo = (): InMemRepo =>
|
|
@@ -2294,3 +2293,53 @@ export const defaults = ${vars}
|
|
|
2294
2293
|
expect(repo.lastCommitMessage()).not.toContain("Gtd-Payload:")
|
|
2295
2294
|
})
|
|
2296
2295
|
})
|
|
2296
|
+
|
|
2297
|
+
describe("runCommand — the repository-root guard's messages", () => {
|
|
2298
|
+
const guarded = async (
|
|
2299
|
+
override: (
|
|
2300
|
+
git: Context.Tag.Service<GitService>,
|
|
2301
|
+
host: Context.Tag.Service<Host>,
|
|
2302
|
+
) => Effect.Effect<unknown, Error, CommandRequirements>,
|
|
2303
|
+
): Promise<string> => {
|
|
2304
|
+
const exit = await Effect.runPromiseExit(
|
|
2305
|
+
Effect.gen(function* () {
|
|
2306
|
+
return yield* override(yield* GitService, yield* Host)
|
|
2307
|
+
}).pipe(Effect.provide(testLayers(seedIdle()))),
|
|
2308
|
+
)
|
|
2309
|
+
return Exit.isFailure(exit) ? (Cause.squash(exit.cause) as Error).message : ""
|
|
2310
|
+
}
|
|
2311
|
+
const out = { write: () => {}, flush: () => {} }
|
|
2312
|
+
const next = runCommand({ kind: "next" }, { kind: "off" }, out)
|
|
2313
|
+
|
|
2314
|
+
it("outside a git repository says so instead of quoting git", async () => {
|
|
2315
|
+
const message = await guarded((git) =>
|
|
2316
|
+
next.pipe(
|
|
2317
|
+
Effect.provideService(GitService, {
|
|
2318
|
+
...git,
|
|
2319
|
+
topLevel: () =>
|
|
2320
|
+
Effect.fail(
|
|
2321
|
+
new Error(
|
|
2322
|
+
"git rev-parse --show-toplevel failed (exit 128): fatal: not a git repository (or any of the parent directories): .git",
|
|
2323
|
+
),
|
|
2324
|
+
),
|
|
2325
|
+
}),
|
|
2326
|
+
),
|
|
2327
|
+
)
|
|
2328
|
+
expect(message).toBe("gtd: not a git repository — run gtd inside one (`git init` makes one)")
|
|
2329
|
+
})
|
|
2330
|
+
|
|
2331
|
+
it("from a subdirectory names the root to cd to", async () => {
|
|
2332
|
+
const message = await guarded((_git, host) =>
|
|
2333
|
+
next.pipe(
|
|
2334
|
+
Effect.provideService(Host, {
|
|
2335
|
+
...host,
|
|
2336
|
+
root: "/repo/my sub",
|
|
2337
|
+
realPath: (path: string) => Effect.succeed(path),
|
|
2338
|
+
}),
|
|
2339
|
+
),
|
|
2340
|
+
)
|
|
2341
|
+
expect(message).toBe(
|
|
2342
|
+
"gtd: run gtd from the repository root — cd /repo (the current directory is /repo/my sub)",
|
|
2343
|
+
)
|
|
2344
|
+
})
|
|
2345
|
+
})
|