@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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +37 -33
- 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-07Z_ENch.mjs → testkit-BvecVjQ0.mjs} +570 -390
- package/dist/chunks/{wire-BTUhRGXj.mjs → wire-DZ8R1RU9.mjs} +8 -1
- package/dist/flows.mjs +2 -676
- package/dist/gtd.bundle.mjs +751 -645
- package/dist/steering.mjs +2 -2
- package/dist/testkit/gherkin.mjs +245 -36
- 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 +66 -60
- package/src/cli/Cli.test.ts +183 -182
- package/src/cli/Cli.ts +164 -186
- 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 +65 -79
- package/src/cli/Program.ts +157 -118
- package/src/edge/Edge.test.ts +64 -56
- package/src/edge/Edge.ts +3 -3
- package/src/flows/factory.test.ts +14 -0
- package/src/flows/factory.ts +22 -5
- 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 +370 -51
- package/src/gherkin/messages.test.ts +38 -4
- package/src/gherkin/messages.ts +21 -4
- 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 +245 -7
- package/src/testkit/TestRepo.ts +271 -40
- 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 +2 -6
- package/src/twins/EmittedScripts.test.ts +4 -2
- package/src/twins/InMemRepo.ts +1 -0
- 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 +169 -146
- 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
|
@@ -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 {
|
|
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
|
+
})
|
package/src/cli/Program.test.ts
CHANGED
|
@@ -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,20 @@ 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
|
-
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, "
|
|
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, "
|
|
137
|
+
const { exitCode, stderr } = await run(repo, "start", "default")
|
|
138
138
|
expect(exitCode).toBe(2)
|
|
139
|
-
expect(stderr).toContain('
|
|
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, "
|
|
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
|
|
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, "
|
|
162
|
-
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("")
|
|
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, "
|
|
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, "
|
|
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, "
|
|
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, "
|
|
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
|
|
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, "
|
|
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, "
|
|
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
|
|
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 =
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
|
|
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("
|
|
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, "
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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).
|
|
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",
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
2121
|
+
export default workflow(
|
|
2122
|
+
async () => {
|
|
2137
2123
|
await judge("idle", { questions: [${QUESTION}], evidence: ${evidence} })
|
|
2138
2124
|
await ${then}
|
|
2139
|
-
}
|
|
2140
|
-
|
|
2141
|
-
|
|
2125
|
+
},
|
|
2126
|
+
{ defaults: ${vars} },
|
|
2127
|
+
)
|
|
2142
2128
|
`
|
|
2143
2129
|
|
|
2144
2130
|
const seededRepo = (): InMemRepo =>
|