@pmelab/gtd 59.0.1 → 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.
Files changed (83) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +37 -33
  3. package/claude/hooks/doors.ts +8 -4
  4. package/dist/chunks/flows-DENt0uWV.mjs +707 -0
  5. package/dist/chunks/{steering-CCNvngSH.mjs → steering-DF_BMadH.mjs} +292 -289
  6. package/dist/chunks/{testkit-07Z_ENch.mjs → testkit-BvecVjQ0.mjs} +570 -390
  7. package/dist/chunks/{wire-BTUhRGXj.mjs → wire-DZ8R1RU9.mjs} +8 -1
  8. package/dist/flows.mjs +2 -676
  9. package/dist/gtd.bundle.mjs +751 -645
  10. package/dist/steering.mjs +2 -2
  11. package/dist/testkit/gherkin.mjs +245 -36
  12. package/dist/testkit.mjs +3 -2
  13. package/dist/web/main.js +234 -232
  14. package/dist/wire.mjs +2 -2
  15. package/dist/workflow-kit.mjs +3 -3
  16. package/package.json +1 -1
  17. package/skills/authoring/SKILL.md +66 -60
  18. package/src/cli/Cli.test.ts +183 -182
  19. package/src/cli/Cli.ts +164 -186
  20. package/src/cli/History.ts +9 -4
  21. package/src/cli/Judges.ts +1 -1
  22. package/src/cli/Output.test.ts +35 -0
  23. package/src/cli/Output.ts +31 -1
  24. package/src/cli/Program.test.ts +65 -79
  25. package/src/cli/Program.ts +157 -118
  26. package/src/edge/Edge.test.ts +64 -56
  27. package/src/edge/Edge.ts +3 -3
  28. package/src/flows/factory.test.ts +14 -0
  29. package/src/flows/factory.ts +22 -5
  30. package/src/flows/helpers.test.ts +35 -0
  31. package/src/flows/helpers.ts +19 -10
  32. package/src/flows/index.test.ts +66 -0
  33. package/src/flows/index.ts +109 -5
  34. package/src/flows/internal.ts +7 -0
  35. package/src/flows/runtime.test.ts +192 -1
  36. package/src/flows/runtime.ts +188 -54
  37. package/src/gherkin/git-control.feature +6 -6
  38. package/src/gherkin/index.test.ts +127 -0
  39. package/src/gherkin/index.ts +370 -51
  40. package/src/gherkin/messages.test.ts +38 -4
  41. package/src/gherkin/messages.ts +21 -4
  42. package/src/gherkin/more-steps.feature +170 -0
  43. package/src/gherkin/scenario-end.feature +101 -0
  44. package/src/gherkin/testkit.feature +83 -7
  45. package/src/install/Install.test.ts +7 -7
  46. package/src/install/Install.ts +8 -8
  47. package/src/replay/Replay.test.ts +13 -2
  48. package/src/replay/Replay.ts +19 -30
  49. package/src/replay/judgeDiffShape.test.ts +1 -1
  50. package/src/steering/Footnotes.test.ts +1 -1
  51. package/src/steering/SteeringFormats.test.ts +1 -1
  52. package/src/steering/freeform.test.ts +1 -1
  53. package/src/steering/index.test.ts +27 -1
  54. package/src/steering/index.ts +6 -104
  55. package/src/steering/internal.ts +11 -0
  56. package/src/steering/qa.test.ts +7 -2
  57. package/src/steering/reads.test.ts +42 -0
  58. package/src/steering/reads.ts +43 -0
  59. package/src/steering/registry.ts +93 -0
  60. package/src/steering/review.test.ts +2 -2
  61. package/src/step/planStart.test.ts +18 -16
  62. package/src/step/planStart.ts +9 -9
  63. package/src/testkit/TestRepo.test.ts +245 -7
  64. package/src/testkit/TestRepo.ts +271 -40
  65. package/src/testkit/imports.test.ts +58 -0
  66. package/src/testkit/imports.ts +102 -0
  67. package/src/testkit/index.test.ts +28 -0
  68. package/src/testkit/index.ts +14 -2
  69. package/src/twins/EmittedScriptRecognizer.ts +2 -6
  70. package/src/twins/EmittedScripts.test.ts +4 -2
  71. package/src/twins/InMemRepo.ts +1 -0
  72. package/src/wire/Script.ts +11 -0
  73. package/src/wire/WireSchema.test.ts +3 -0
  74. package/src/wire/golden/1/script.json +4 -0
  75. package/src/wire/index.ts +1 -0
  76. package/src/workflow/SteeringFormats.test.ts +1 -1
  77. package/src/workflow/compile.ts +2 -2
  78. package/src/workflow/discovery.test.ts +3 -3
  79. package/src/workflow/load.test.ts +256 -116
  80. package/src/workflow/load.ts +169 -146
  81. package/src/workflow-kit/index.test.ts +25 -0
  82. package/src/workflow-kit/index.ts +37 -4
  83. package/src/workflow-kit/review.ts +2 -2
@@ -0,0 +1,35 @@
1
+ import { Effect, Exit } from "effect"
2
+ import { describe, expect, it } from "vitest"
3
+ import { WIRE_SCHEMA } from "../wire/index.js"
4
+ import { writeScript, type JsonMode } from "./Output.js"
5
+
6
+ const run = (json: JsonMode, script: string, plain?: string) => {
7
+ let written = ""
8
+ const out = { write: (chunk: string) => (written += chunk), flush: () => {} }
9
+ const exit = Effect.runSyncExit(writeScript(json, out, script, plain))
10
+ return { exit, written }
11
+ }
12
+
13
+ describe("writeScript", () => {
14
+ it("plain prints the script, or the plain text when given", () => {
15
+ expect(run({ kind: "off" }, "git reset\n").written).toBe("git reset\n")
16
+ expect(run({ kind: "off" }, "", "nothing to do\n").written).toBe("nothing to do\n")
17
+ })
18
+
19
+ it("--json prints { schema, script }, the script ending in exactly one newline", () => {
20
+ expect(JSON.parse(run({ kind: "document" }, "git reset\n\n").written)).toEqual({
21
+ schema: WIRE_SCHEMA,
22
+ script: "git reset\n",
23
+ })
24
+ expect(JSON.parse(run({ kind: "document" }, "", "nothing\n").written)).toEqual({
25
+ schema: WIRE_SCHEMA,
26
+ script: "",
27
+ })
28
+ })
29
+
30
+ it("--json=script prints the script alone; an unknown path fails", () => {
31
+ expect(run({ kind: "select", path: "script" }, "git reset").written).toBe("git reset\n\n")
32
+ expect(run({ kind: "select", path: "schema" }, "x").written).toBe(`${WIRE_SCHEMA}\n`)
33
+ expect(Exit.isFailure(run({ kind: "select", path: "nope" }, "x").exit)).toBe(true)
34
+ })
35
+ })
package/src/cli/Output.ts CHANGED
@@ -1,6 +1,12 @@
1
1
  import { Effect } from "effect"
2
2
  import { CommandRunner, GitService, Host, Narrator, Workspace } from "../platform/index.js"
3
- import { selectPath, type EXIT_OK, type EXIT_STRAYS_FOUND } from "../wire/index.js"
3
+ import {
4
+ renderScriptJson,
5
+ scriptFields,
6
+ selectPath,
7
+ type EXIT_OK,
8
+ type EXIT_STRAYS_FOUND,
9
+ } from "../wire/index.js"
4
10
  import { ConfigDiscovery, ConfigService } from "../workflow/index.js"
5
11
  import type { ToolCommands } from "./ToolCommands.js"
6
12
 
@@ -81,3 +87,27 @@ export const writeJson = (
81
87
  else yield* writeSelection(out, document, json.path)
82
88
  return true
83
89
  })
90
+
91
+ /** Exactly one trailing newline: a script embedded in a `--json` document is never re-normalized at its tail. */
92
+ export const normalizeScriptNewline = (script: string): string =>
93
+ script.length === 0 ? script : `${script.replace(/\n+$/, "")}\n`
94
+
95
+ /**
96
+ * A script-printing command's output: the script itself (or `plain`, when
97
+ * there is none to run), `{ schema, script }` under `--json`, or one field.
98
+ */
99
+ export const writeScript = (
100
+ json: JsonMode,
101
+ out: ArtifactOut,
102
+ script: string,
103
+ plain: string = script,
104
+ ): Effect.Effect<void, Error> =>
105
+ Effect.gen(function* () {
106
+ if (json.kind === "off") {
107
+ out.write(plain)
108
+ return
109
+ }
110
+ const fields = scriptFields(normalizeScriptNewline(script))
111
+ if (json.kind === "document") out.write(renderScriptJson(fields))
112
+ else yield* writeSelection(out, fields, json.path)
113
+ })
@@ -81,25 +81,26 @@ const seedIdle = (): InMemRepo => {
81
81
  return repo
82
82
  }
83
83
 
84
- describe("gtd --workflow <name> — a custom workflow's named workflow", () => {
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 = async () => {
93
- await agent("working", "go")
94
- }
95
-
96
- export const defaults = { greeting: "hello" }
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, "--workflow", "hotfix")
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,20 @@ export const defaults = { greeting: "hello" }
109
110
  expect(repo.commitHistory()).toHaveLength(before)
110
111
  })
111
112
 
112
- it("an unknown workflow is a usage error (exit 2) listing every startable name", async () => {
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, "--workflow", "bogus")
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('unknown workflow "bogus"')
119
- expect(stderr).toContain("startable: hotfix")
119
+ expect(stderr).toContain('no workflow or door is named "bogus" — run `gtd start` to list them')
120
120
  })
121
121
 
122
122
  it("lists the paths its script commits as comments a path cannot break out of", async () => {
123
123
  const repo = seed(WORKFLOW)
124
124
  repo.writeFile("NOTE.md", "a note\n")
125
125
  repo.writeFile("evil\nrm -rf x", "")
126
- const { stdout, exitCode } = await run(repo, "--workflow", "hotfix")
126
+ const { stdout, exitCode } = await run(repo, "start", "hotfix")
127
127
  expect(exitCode).toBe(0)
128
128
  expect(stdout).toContain("# commits every change in the working tree (git add -A):\n")
129
129
  expect(stdout).toContain("# A NOTE.md\n")
@@ -134,9 +134,9 @@ export const defaults = { greeting: "hello" }
134
134
 
135
135
  it("`default` is not startable by name", async () => {
136
136
  const repo = seed(WORKFLOW)
137
- const { exitCode, stderr } = await run(repo, "--workflow", "default")
137
+ const { exitCode, stderr } = await run(repo, "start", "default")
138
138
  expect(exitCode).toBe(2)
139
- expect(stderr).toContain('unknown workflow "default"')
139
+ expect(stderr).toContain('no workflow or door is named "default"')
140
140
  })
141
141
 
142
142
  it("--entry is an ordinary unknown flag", async () => {
@@ -149,17 +149,18 @@ export const defaults = { greeting: "hello" }
149
149
  const repo = seed(WORKFLOW)
150
150
  await landTurn(repo, { ".gtd/TODO.md": "sketch\n" })
151
151
  const before = repo.commitHistory().length
152
- const { exitCode, stderr } = await run(repo, "--workflow", "hotfix")
152
+ const { exitCode, stderr } = await run(repo, "start", "hotfix")
153
153
  expect(exitCode).toBe(1)
154
154
  expect(repo.commitHistory()).toHaveLength(before)
155
155
  expect(stderr).toContain("already underway")
156
156
  })
157
157
 
158
- it("an undeclared --var name refuses, listing the declared names", async () => {
158
+ it("an undeclared --var name is a usage error (exit 2), listing the declared names", async () => {
159
159
  const repo = seed(WORKFLOW)
160
160
  const before = repo.commitHistory().length
161
- const { exitCode, stderr } = await run(repo, "--workflow", "hotfix", "--var", "bogus=1")
162
- expect(exitCode).toBe(1)
161
+ const { exitCode, stderr, stdout } = await run(repo, "start", "hotfix", "--var", "bogus=1")
162
+ expect(exitCode).toBe(2)
163
+ expect(stdout).toBe("")
163
164
  expect(repo.commitHistory()).toHaveLength(before)
164
165
  expect(stderr).toContain("bogus")
165
166
  expect(stderr).toContain("greeting")
@@ -168,7 +169,7 @@ export const defaults = { greeting: "hello" }
168
169
  it("a declared --var override renders as a Gtd-Var trailer in the emitted commit script", async () => {
169
170
  const repo = seed(WORKFLOW)
170
171
  const before = repo.commitHistory().length
171
- const { exitCode, stdout } = await run(repo, "--workflow", "hotfix", "--var", "greeting=world")
172
+ const { exitCode, stdout } = await run(repo, "start", "hotfix", "--var", "greeting=world")
172
173
  expect(exitCode).toBe(0)
173
174
  expect(stdout).toContain("Gtd-Var: greeting=world")
174
175
  expect(repo.commitHistory()).toHaveLength(before)
@@ -178,7 +179,7 @@ export const defaults = { greeting: "hello" }
178
179
  const repo = seed(WORKFLOW)
179
180
  repo.writeFile("scratch.txt", "uncommitted\n")
180
181
  const before = repo.commitHistory().length
181
- const { exitCode, stdout } = await run(repo, "--workflow", "hotfix")
182
+ const { exitCode, stdout } = await run(repo, "start", "hotfix")
182
183
  expect(exitCode).toBe(0)
183
184
  expect(stdout.length).toBeGreaterThan(0)
184
185
  expect(repo.commitHistory()).toHaveLength(before)
@@ -186,7 +187,7 @@ export const defaults = { greeting: "hello" }
186
187
 
187
188
  it("applying the start script opens the workflow's episode at its first step", async () => {
188
189
  const repo = seed(WORKFLOW)
189
- const { stdout } = await run(repo, "--workflow", "hotfix")
190
+ const { stdout } = await run(repo, "start", "hotfix")
190
191
  expect(applyEmittedScript(repo, new Map(), stdout).ok).toBe(true)
191
192
  expect(repo.lastCommitSubject()).toBe("gtd(human): working")
192
193
  const next = JSON.parse((await run(repo, "next", "--json")).stdout) as Record<string, unknown>
@@ -197,7 +198,7 @@ export const defaults = { greeting: "hello" }
197
198
 
198
199
  it("a pinned workflow the config no longer defines is refused with the abandon advice", async () => {
199
200
  const repo = seed(WORKFLOW)
200
- const { stdout } = await run(repo, "--workflow", "hotfix")
201
+ const { stdout } = await run(repo, "start", "hotfix")
201
202
  applyEmittedScript(repo, new Map(), stdout)
202
203
  repo.writeFile("gtd.config.ts", WORKFLOW.replace("export const hotfix", "const hotfix"))
203
204
  const { exitCode, stderr } = await run(repo, "next")
@@ -207,18 +208,16 @@ export const defaults = { greeting: "hello" }
207
208
  })
208
209
  })
209
210
 
210
- describe("gtd install", () => {
211
+ describe("gtd guide", () => {
211
212
  it("where no gtd.config.ts governs, prints the briefing and warns where to get a workflow — writing nothing", async () => {
212
213
  const repo = new InMemRepo()
213
214
  repo.writeFile("README.md", "# test project\n")
214
215
  repo.seedCommit("chore: init")
215
216
  const before = repo.snapshot()
216
- const { stdout, stderr, exitCode } = await run(repo, "install")
217
+ const { stdout, stderr, exitCode } = await run(repo, "guide")
217
218
  expect(exitCode).toBe(0)
218
219
  expect(stdout).toMatch(/Make sure a .gtd\.config\.ts. governs the repository/)
219
- expect(stderr).toContain(
220
- "gtd install: no gtd.config.ts governs here, and gtd ships no workflow",
221
- )
220
+ expect(stderr).toContain("gtd guide: no gtd.config.ts governs here, and gtd ships no workflow")
222
221
  expect(stderr).toContain(WORKFLOWS_REPO)
223
222
  expect(repo.hasPath("gtd.config.ts")).toBe(false)
224
223
  expect(repo.snapshot()).toBe(before)
@@ -226,42 +225,35 @@ describe("gtd install", () => {
226
225
 
227
226
  it("where a gtd.config.ts governs, prints the briefing alone", async () => {
228
227
  const repo = seedIdle()
229
- const { stdout, stderr, exitCode } = await run(repo, "install")
228
+ const { stdout, stderr, exitCode } = await run(repo, "guide")
230
229
  expect(exitCode).toBe(0)
231
230
  expect(stdout).toMatch(/Make sure a .gtd\.config\.ts. governs the repository/)
232
231
  expect(stderr).not.toContain(WORKFLOWS_REPO)
233
232
  })
234
233
  })
235
234
 
236
- describe("gtd --workflow <name> — a workflow that fixes its diff base", () => {
237
- 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"
238
237
 
239
238
  export default async () => {
240
239
  await human("idle", { message: "hi" })
241
240
  }
242
241
 
243
- export const review = async () => {
244
- await human("checking", { message: "check" })
245
- }
246
-
247
- export const defaults = { reviewBase: "" }
248
-
249
- 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
+ )
250
248
  `
251
249
 
252
- it("--workflow review --var reviewBase=<base> emits a script anchoring the process to that base", async () => {
250
+ it("start review --var reviewBase=<base> emits a script anchoring the process to that base", async () => {
253
251
  const repo = seed(WORKFLOW)
254
252
  const base = repo.commitHistory().at(-1)!.hash
255
253
  repo.writeFile("scratch.txt", "more work\n")
256
254
  repo.seedCommit("chore: more work")
257
255
  const before = repo.commitHistory().length
258
- const { stdout, exitCode } = await run(
259
- repo,
260
- "--workflow",
261
- "review",
262
- "--var",
263
- `reviewBase=${base}`,
264
- )
256
+ const { stdout, exitCode } = await run(repo, "start", "review", "--var", `reviewBase=${base}`)
265
257
  expect(exitCode).toBe(0)
266
258
  expect(stdout).toContain("gtd(human): checking")
267
259
  expect(stdout).toContain("Gtd-Workflow: review")
@@ -453,7 +445,7 @@ export default async () => {
453
445
  })
454
446
 
455
447
  describe("gtd next --json — stall detection (attempt commits)", () => {
456
- const WORKFLOW = `import { agent, human, run } from "@pmelab/gtd/flows"
448
+ const WORKFLOW = `import { agent, human, run, workflow } from "@pmelab/gtd/flows"
457
449
 
458
450
  const work = async () => {
459
451
  await agent("working", "do the work described in NOTE.md")
@@ -465,7 +457,7 @@ export default async () => {
465
457
  await work()
466
458
  }
467
459
 
468
- export const resume = work
460
+ export const resume = workflow(work)
469
461
  `
470
462
 
471
463
  const atWorking = async (): Promise<InMemRepo> => {
@@ -502,7 +494,7 @@ export const resume = work
502
494
 
503
495
  it("a clean-tree entry commit resting at a prompt step is NOT a stall — the actor differs (human entered, agent acts)", async () => {
504
496
  const repo = seed(WORKFLOW)
505
- const { stdout: entry } = await run(repo, "--workflow", "resume")
497
+ const { stdout: entry } = await run(repo, "start", "resume")
506
498
  expect(applyEmittedScript(repo, new Map(), entry).ok).toBe(true)
507
499
  const { stdout, exitCode } = await run(repo, "next", "--json")
508
500
  expect(exitCode).toBe(0)
@@ -1001,18 +993,19 @@ describe("gtd check <mode> <file>", () => {
1001
993
  )
1002
994
  })
1003
995
 
1004
- it("an absent file exits 0 with no output", async () => {
996
+ it("an absent file is a refusal (exit 1) naming it", async () => {
1005
997
  const repo = bareRepo()
1006
- const { stdout, exitCode } = await run(repo, "check", "qa", ".gtd/TODO.md")
1007
- expect(exitCode).toBe(0)
998
+ const { stdout, stderr, exitCode } = await run(repo, "check", "qa", ".gtd/TODO.md")
999
+ expect(exitCode).toBe(1)
1008
1000
  expect(stdout).toBe("")
1001
+ expect(stderr).toContain("gtd check: no such file: .gtd/TODO.md")
1009
1002
  })
1010
1003
 
1011
- it("an unknown mode is a usage-style error naming the known modes", async () => {
1004
+ it("an unknown mode is a usage error (exit 2) naming the known modes", async () => {
1012
1005
  const repo = bareRepo()
1013
1006
  repo.writeFile("TODO.md", validQaDoc)
1014
1007
  const { stderr, exitCode } = await run(repo, "check", "bogus", "TODO.md")
1015
- expect(exitCode).toBe(1)
1008
+ expect(exitCode).toBe(2)
1016
1009
  expect(stderr).toContain('unknown mode "bogus"')
1017
1010
  expect(stderr).toContain("qa")
1018
1011
  expect(stderr).toContain("review")
@@ -1244,13 +1237,6 @@ describe("gtd check <mode> <file> --open-questions", () => {
1244
1237
  expect(stderr).toContain(".gtd/TODO.md")
1245
1238
  })
1246
1239
 
1247
- it("check without the flag is unchanged: an absent file still exits 0 with no output", async () => {
1248
- const repo = bareRepo()
1249
- const { stdout, exitCode } = await run(repo, "check", "qa", ".gtd/TODO.md")
1250
- expect(exitCode).toBe(0)
1251
- expect(stdout).toBe("")
1252
- })
1253
-
1254
1240
  it("--json is out of scope for check --open-questions too — a usage error, never a JSON envelope", async () => {
1255
1241
  const repo = bareRepo()
1256
1242
  repo.writeFile(".gtd/TODO.md", docWithUnanswered)
@@ -1277,7 +1263,7 @@ describe("gtd check <mode> <file> --open-questions", () => {
1277
1263
  "--open-questions",
1278
1264
  ".gtd/TODO.md",
1279
1265
  )
1280
- expect(exitCode).not.toBe(0)
1266
+ expect(exitCode).toBe(2)
1281
1267
  expect(stderr).toContain('unknown mode "bogus"')
1282
1268
  expect(stderr).toContain("qa")
1283
1269
  expect(stderr).toContain("review")
@@ -1718,16 +1704,14 @@ describe("runCommand — refuses in a repository with no commits", () => {
1718
1704
  lsp: { kind: "lsp" },
1719
1705
  ui: { kind: "ui", selfSigned: false, dev: false },
1720
1706
  land: { kind: "land" },
1721
- start: { kind: "start", actor: "human", workflow: "feature", vars: {}, label: "" },
1722
- door: { kind: "door", name: "fix", args: [] },
1723
- doors: { kind: "doors" },
1707
+ start: { kind: "start", name: "fix", args: [], vars: {} },
1724
1708
  abandon: { kind: "abandon" },
1725
1709
  restore: { kind: "restore" },
1726
1710
  next: { kind: "next" },
1727
1711
  validate: { kind: "validate" },
1728
1712
  check: { kind: "check", mode: "qa", file: ".gtd/TODO.md" },
1729
1713
  uncheck: { kind: "uncheck", file: ".gtd/REVIEW.md" },
1730
- install: { kind: "install" },
1714
+ guide: { kind: "guide" },
1731
1715
  summary: { kind: "summary" },
1732
1716
  base: { kind: "base" },
1733
1717
  judge: { kind: "judge" },
@@ -1748,13 +1732,11 @@ describe("runCommand — refuses in a repository with no commits", () => {
1748
1732
  const NO_COMMITS_MESSAGE =
1749
1733
  "gtd: the repository has no commits yet — make an initial commit, then run gtd again"
1750
1734
 
1751
- it("derives exactly the eighteen non-standalone kinds — a canary for the table-driven cases below", () => {
1735
+ it("derives exactly the sixteen non-standalone kinds — a canary for the table-driven cases below", () => {
1752
1736
  expect(stateKinds.sort()).toEqual(
1753
1737
  [
1754
1738
  "abandon",
1755
1739
  "base",
1756
- "door",
1757
- "doors",
1758
1740
  "start",
1759
1741
  "land",
1760
1742
  "next",
@@ -1830,16 +1812,19 @@ describe("runCommand — refuses in a repository with no commits", () => {
1830
1812
  })
1831
1813
 
1832
1814
  describe("gtd summary", () => {
1833
- const SUMMARY_WORKFLOW = `import { agent, human } from "@pmelab/gtd/flows"
1815
+ const SUMMARY_WORKFLOW = `import { agent, human, workflow } from "@pmelab/gtd/flows"
1834
1816
 
1835
- export default async () => {
1817
+ export default workflow(
1818
+ async () => {
1836
1819
  await human("idle", { message: "hi" })
1837
1820
  await agent("working", "go")
1838
1821
  await human("reviewing", { message: "check it" })
1839
1822
  await agent("polishing", "polish")
1840
- }
1841
-
1842
- export const summary = (c) => \`entry=\${c.entryCommit} tip=\${c.processTip} humans=\${c.humanCommits.length}\`
1823
+ },
1824
+ {
1825
+ summary: (c) => \`entry=\${c.entryCommit} tip=\${c.processTip} humans=\${c.humanCommits.length}\`,
1826
+ },
1827
+ )
1843
1828
  `
1844
1829
 
1845
1830
  const reviewed = async (): Promise<InMemRepo> => {
@@ -2131,14 +2116,15 @@ describe("gtd judge / gtd judge answer", () => {
2131
2116
  const QUESTION = `{ id: "q1", primitive: "noul", instructions: "i", criteria: "c" }`
2132
2117
 
2133
2118
  const judgeWorkflow = (evidence: string, then: string, vars = "{}"): string =>
2134
- `import { agent, human, judge, read } from "@pmelab/gtd/flows"
2119
+ `import { agent, human, judge, read, workflow } from "@pmelab/gtd/flows"
2135
2120
 
2136
- export default async () => {
2121
+ export default workflow(
2122
+ async () => {
2137
2123
  await judge("idle", { questions: [${QUESTION}], evidence: ${evidence} })
2138
2124
  await ${then}
2139
- }
2140
-
2141
- export const defaults = ${vars}
2125
+ },
2126
+ { defaults: ${vars} },
2127
+ )
2142
2128
  `
2143
2129
 
2144
2130
  const seededRepo = (): InMemRepo =>