@pmelab/gtd 23.2.0 → 24.1.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 +44 -11
- package/claude/hooks/access.ts +1 -1
- package/dist/chunks/Schema-Dh-mkwkq.mjs +25353 -0
- package/dist/chunks/reserved-ejn-LVYO.mjs +1388 -0
- package/dist/chunks/rolldown-runtime-8H4AJuhK.mjs +14 -0
- package/dist/chunks/steering-D-PvRC5W.mjs +16296 -0
- package/dist/chunks/wire-DA7q2ikx.mjs +473 -0
- package/dist/flows.mjs +525 -0
- package/dist/gtd.bundle.mjs +43830 -56044
- package/dist/steering.mjs +2 -0
- package/dist/testkit.mjs +13006 -0
- package/dist/wire.mjs +2 -0
- package/dist/workflow-kit.mjs +352 -0
- package/dist/workflows/feature.mjs +315 -0
- package/dist/workflows/fix.mjs +40 -0
- package/dist/workflows/review.mjs +25 -0
- package/dist/workflows.mjs +1953 -1724
- package/package.json +70 -8
- package/skills/authoring/SKILL.md +5 -4
- package/src/cli/Cli.test.ts +1329 -0
- package/src/cli/Cli.ts +1348 -0
- package/src/cli/Program.test.ts +2262 -0
- package/src/cli/Program.ts +1331 -0
- package/src/cli/ToolCommands.ts +43 -0
- package/src/cli/index.ts +14 -0
- package/src/edge/Edge.test.ts +1016 -0
- package/src/edge/Edge.ts +1225 -0
- package/src/edge/index.ts +16 -0
- package/src/emit/Emit.test.ts +425 -0
- package/src/emit/Emit.ts +139 -0
- package/src/emit/GitScript.test.ts +260 -0
- package/src/emit/GitScript.ts +131 -0
- package/src/emit/ModeContradiction.test.ts +120 -0
- package/src/emit/ModeContradiction.ts +66 -0
- package/src/emit/OutcomeScript.test.ts +238 -0
- package/src/emit/OutcomeScript.ts +73 -0
- package/src/emit/SteeringMode.test.ts +274 -0
- package/src/emit/SteeringMode.ts +256 -0
- package/src/emit/index.ts +40 -0
- package/src/flows/factory.ts +5 -1
- package/src/flows/runtime.ts +3 -0
- package/src/install/ConfigScaffold.ts +19 -0
- package/src/install/Install.test.ts +426 -0
- package/src/install/Install.ts +634 -0
- package/src/install/Version.ts +27 -0
- package/src/install/index.ts +3 -0
- package/src/platform/CommandRunner.ts +74 -0
- package/src/platform/Commentary.test.ts +126 -0
- package/src/platform/Commentary.ts +92 -0
- package/src/platform/Git.test.ts +50 -0
- package/src/platform/Git.ts +552 -0
- package/src/platform/Host.ts +42 -0
- package/src/platform/RetainedHistory.test.ts +65 -0
- package/src/platform/RetainedHistory.ts +34 -0
- package/src/platform/Workspace.test.ts +669 -0
- package/src/platform/Workspace.ts +631 -0
- package/src/platform/WorktreeState.test.ts +162 -0
- package/src/platform/WorktreeState.ts +50 -0
- package/src/platform/index.ts +22 -0
- package/src/replay/Access.test.ts +53 -0
- package/src/replay/Access.ts +46 -0
- package/src/replay/Format.test.ts +20 -0
- package/src/replay/Format.ts +14 -0
- package/src/replay/Replay.test.ts +887 -0
- package/src/replay/Replay.ts +669 -0
- package/src/replay/Trailers.test.ts +117 -0
- package/src/replay/Trailers.ts +213 -0
- package/src/replay/Tree.test.ts +25 -0
- package/src/replay/Tree.ts +45 -0
- package/src/replay/index.ts +11 -0
- package/src/steering/Blocks.fixture.ts +6 -0
- package/src/steering/Blocks.test.ts +167 -0
- package/src/steering/Blocks.ts +321 -0
- package/src/steering/CodeThreads.test.ts +127 -0
- package/src/steering/CodeThreads.ts +197 -0
- package/src/steering/Edits.test.ts +19 -0
- package/src/steering/Edits.ts +20 -0
- package/src/steering/Eol.test.ts +16 -0
- package/src/steering/Eol.ts +2 -0
- package/src/steering/FORMATS.md +58 -0
- package/src/steering/Footnotes.test.ts +977 -0
- package/src/steering/Footnotes.ts +661 -0
- package/src/steering/Inline.fixture.ts +7 -0
- package/src/steering/Inline.test.ts +227 -0
- package/src/steering/Inline.ts +195 -0
- package/src/steering/MarkdownTree.test.ts +247 -0
- package/src/steering/MarkdownTree.ts +260 -0
- package/src/steering/Outline.ts +19 -0
- package/src/steering/SteeringFormat.test.ts +91 -0
- package/src/steering/SteeringFormat.ts +267 -0
- package/src/steering/SteeringFormats.test.ts +163 -0
- package/src/steering/Threads.ts +163 -0
- package/src/steering/contract.test.ts +264 -0
- package/src/steering/fixtures/code-threads/cache.stripped.ts.txt +6 -0
- package/src/steering/fixtures/code-threads/cache.ts.md +15 -0
- package/src/steering/fixtures/free-form/notes.md +12 -0
- package/src/steering/fixtures/free-form/notes.read.json +42 -0
- package/src/steering/fixtures/qa/answered.md +23 -0
- package/src/steering/fixtures/qa/answered.read.json +119 -0
- package/src/steering/fixtures/qa/requirements.md +160 -0
- package/src/steering/fixtures/qa/requirements.read.json +475 -0
- package/src/steering/fixtures/review/review.md +76 -0
- package/src/steering/fixtures/review/review.read.json +237 -0
- package/src/steering/fixtures/review/ticked.md +23 -0
- package/src/steering/fixtures/review/ticked.read.json +99 -0
- package/src/steering/fixtures/review-notes/baseline.md +14 -0
- package/src/steering/fixtures/review-notes/noted.md +19 -0
- package/src/steering/freeform.test.ts +311 -0
- package/src/steering/freeform.ts +148 -0
- package/src/steering/index.test.ts +62 -0
- package/src/steering/index.ts +140 -0
- package/src/steering/qa.test.ts +2160 -0
- package/src/steering/qa.ts +1104 -0
- package/src/steering/review.test.ts +1829 -0
- package/src/steering/review.ts +1180 -0
- package/src/step/LandStep.ts +31 -0
- package/src/step/RepoSnapshot.ts +29 -0
- package/src/step/accessRefusal.test.ts +58 -0
- package/src/step/accessRefusal.ts +26 -0
- package/src/step/index.ts +5 -0
- package/src/step/planStart.test.ts +202 -0
- package/src/step/planStart.ts +167 -0
- package/src/step/planStep.test.ts +104 -0
- package/src/step/planStep.ts +89 -0
- package/src/step/snapshot.fixture.ts +28 -0
- package/src/testkit/EmittedScriptRecognizer.test.ts +639 -0
- package/src/testkit/EmittedScriptRecognizer.ts +642 -0
- package/src/testkit/FakeGitOperations.ts +95 -0
- package/src/testkit/GitTiers.test.ts +11 -0
- package/src/testkit/GitTiers.ts +1052 -0
- package/src/testkit/InMemRepo.ts +467 -0
- package/src/testkit/Layers.test.ts +58 -0
- package/src/testkit/Layers.ts +241 -0
- package/src/testkit/StrictGitOperations.ts +31 -0
- package/src/testkit/TestRepo.test.ts +119 -0
- package/src/testkit/TestRepo.ts +274 -0
- package/src/testkit/cliIo.ts +36 -0
- package/src/testkit/index.ts +9 -0
- package/src/testkit/steps.ts +177 -0
- package/src/testkit/testkit.feature +65 -0
- package/src/wire/BeatDocument.test.ts +419 -0
- package/src/wire/BeatDocument.ts +114 -0
- package/src/wire/BeatPlain.test.ts +113 -0
- package/src/wire/BeatPlain.ts +127 -0
- package/src/wire/BeatStatus.test.ts +59 -0
- package/src/wire/BeatStatus.ts +81 -0
- package/src/wire/Demand.test.ts +142 -0
- package/src/wire/Demand.ts +108 -0
- package/src/wire/ExitCodes.test.ts +21 -0
- package/src/wire/ExitCodes.ts +16 -0
- package/src/wire/Glob.ts +29 -0
- package/src/wire/Judge.ts +22 -0
- package/src/wire/Land.test.ts +120 -0
- package/src/wire/Land.ts +75 -0
- package/src/wire/Select.test.ts +148 -0
- package/src/wire/Select.ts +78 -0
- package/src/wire/Sessions.test.ts +59 -0
- package/src/wire/Sessions.ts +39 -0
- package/src/wire/WireSchema.test.ts +162 -0
- package/src/wire/constants.ts +22 -0
- package/src/wire/golden/1/beat-capture.json +34 -0
- package/src/wire/golden/1/beat-message.json +34 -0
- package/src/wire/golden/1/beat-prompt.json +44 -0
- package/src/wire/golden/1/beat-script.json +34 -0
- package/src/wire/golden/1/beat-stalled.json +34 -0
- package/src/wire/golden/1/judge.json +14 -0
- package/src/wire/golden/1/land-noop.json +10 -0
- package/src/wire/golden/1/land.json +10 -0
- package/src/wire/index.ts +12 -0
- package/src/wire/types.ts +46 -0
- package/src/workflow/Diagnostic.test.ts +93 -0
- package/src/workflow/Diagnostic.ts +54 -0
- package/src/workflow/SteeringFormats.test.ts +27 -0
- package/src/workflow/SteeringFormats.ts +6 -0
- package/src/workflow/Workflow.ts +77 -0
- package/src/workflow/compile.test.ts +53 -0
- package/src/workflow/compile.ts +56 -0
- package/src/workflow/discovery.ts +122 -0
- package/src/workflow/fingerprint.test.ts +52 -0
- package/src/workflow/fingerprint.ts +53 -0
- package/src/workflow/index.ts +33 -0
- package/src/workflow/load.test.ts +1017 -0
- package/src/workflow/load.ts +836 -0
- package/src/workflow/skills.test.ts +109 -0
- package/src/workflow/skills.ts +90 -0
- package/src/workflow/vars.ts +33 -0
- package/src/workflow-kit/health.test.ts +30 -0
- package/src/workflow-kit/health.ts +86 -0
- package/src/workflow-kit/index.ts +7 -0
- package/src/workflow-kit/quality.test.ts +17 -0
- package/src/workflow-kit/quality.ts +45 -0
- package/src/workflow-kit/review.ts +324 -0
- package/src/{workflows → workflow-kit}/scenarios.ts +21 -28
- package/src/workflows/feature/feature.test.ts +32 -0
- package/src/workflows/{bundled.ts → feature/feature.ts} +11 -63
- package/src/workflows/feature/index.ts +10 -0
- package/src/workflows/{packages.test.ts → feature/packages.test.ts} +2 -2
- package/src/workflows/{packages.ts → feature/packages.ts} +3 -3
- package/src/workflows/{planning.ts → feature/planning.ts} +2 -3
- package/src/workflows/{review.test.ts → feature/review.test.ts} +9 -44
- package/src/workflows/feature/review.ts +106 -0
- package/src/workflows/feature/steps.test.ts +82 -0
- package/src/workflows/feature/steps.ts +92 -0
- package/src/workflows/fix/fix.feature +80 -0
- package/src/workflows/fix/fix.test.ts +74 -0
- package/src/workflows/fix/fix.ts +52 -0
- package/src/workflows/fix/index.ts +6 -0
- package/src/workflows/index.ts +36 -2
- package/src/workflows/review/index.ts +6 -0
- package/src/workflows/review/review.test.ts +99 -0
- package/src/workflows/review/review.ts +33 -0
- package/src/workflows/{access.test.ts → shared/access.test.ts} +1 -1
- package/src/workflows/{access.ts → shared/access.ts} +3 -5
- package/src/workflows/shared/config.fixture.ts +12 -0
- package/src/workflows/{health.test.ts → shared/health.test.ts} +1 -1
- package/src/workflows/{health.ts → shared/health.ts} +38 -55
- package/src/workflows/{options.test.ts → shared/options.test.ts} +2 -2
- package/src/workflows/{options.ts → shared/options.ts} +14 -2
- package/src/workflows/shared/reserved.ts +11 -0
- package/src/workflows/{scenarios.test.ts → shared/scenarios.test.ts} +1 -1
- package/src/workflows/shared/scenarios.ts +35 -0
- package/src/workflows/{skills.test.ts → shared/skills.test.ts} +3 -1
- package/src/workflows/{skills.ts → shared/skills.ts} +7 -6
- package/src/workflows/{steps.test.ts → shared/steps.test.ts} +1 -66
- package/src/workflows/{steps.ts → shared/steps.ts} +8 -98
- package/src/workflows/shared/tail.test.ts +205 -0
- package/src/workflows/shared/tail.ts +135 -0
- package/src/workflows/{text.fixture.ts → shared/text.fixture.ts} +2 -2
- package/src/workflows/{text.ts → shared/text.ts} +48 -7
- package/src/workflows/{threads.test.ts → shared/threads.test.ts} +1 -1
- package/src/workflows/shared/workflows.steps.ts +7 -0
- package/src/workflows/review.ts +0 -362
- /package/src/workflows/{prose.ts → shared/prose.ts} +0 -0
- /package/src/workflows/{text.test.ts → shared/text.test.ts} +0 -0
- /package/src/workflows/{vars.ts → shared/vars.ts} +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CliIo, ToolCommands } from "../cli/index.js"
|
|
2
|
+
import type { InMemRepo } from "./InMemRepo.js"
|
|
3
|
+
import { testLayers } from "./Layers.js"
|
|
4
|
+
|
|
5
|
+
export interface CapturedCliResult {
|
|
6
|
+
readonly stdout: string
|
|
7
|
+
readonly stderr: string
|
|
8
|
+
readonly exitCode: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const makeCapturingCliIo = (
|
|
12
|
+
repo: InMemRepo,
|
|
13
|
+
env: Readonly<Record<string, string | undefined>> = {},
|
|
14
|
+
tools: Partial<ToolCommands["Type"]> = {},
|
|
15
|
+
): { readonly io: CliIo; readonly result: () => CapturedCliResult } => {
|
|
16
|
+
let stdout = ""
|
|
17
|
+
let stderr = ""
|
|
18
|
+
let exitCode = 0
|
|
19
|
+
const io: CliIo = {
|
|
20
|
+
stdout: (chunk) => {
|
|
21
|
+
stdout += chunk
|
|
22
|
+
},
|
|
23
|
+
stderr: (chunk) => {
|
|
24
|
+
stderr += chunk
|
|
25
|
+
},
|
|
26
|
+
exit: (code) => {
|
|
27
|
+
exitCode = code
|
|
28
|
+
},
|
|
29
|
+
// `narrate: io.stderr` is the SAME sink `stderr` above already captures
|
|
30
|
+
// errors into — narration lands in the exact same buffer, exactly like a
|
|
31
|
+
// real invocation's narration and remediation share one fd.
|
|
32
|
+
layers: (verbose) =>
|
|
33
|
+
testLayers(repo, { env, narrate: (line) => (stderr += line), verbose, tools }),
|
|
34
|
+
}
|
|
35
|
+
return { io, result: () => ({ stdout, stderr, exitCode }) }
|
|
36
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { runsItsScript, testRepo, type TestRepo } from "./TestRepo.js"
|
|
2
|
+
export { InMemRepo } from "./InMemRepo.js"
|
|
3
|
+
export { makeInMemoryWorkspaceOps, testLayers, type ScriptedCommand } from "./Layers.js"
|
|
4
|
+
export { makeCapturingCliIo } from "./cliIo.js"
|
|
5
|
+
export { applyEmittedScript } from "./EmittedScriptRecognizer.js"
|
|
6
|
+
export { gitTiers, runGitServiceContract, runGitScriptContract } from "./GitTiers.js"
|
|
7
|
+
export { strictGitOperations } from "./StrictGitOperations.js"
|
|
8
|
+
export { fakeGitOperations } from "./FakeGitOperations.js"
|
|
9
|
+
export type { TestkitWorld } from "./steps.js"
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// Generic quickpickle steps over `testRepo`: they name no workflow, so any
|
|
2
|
+
// workflow's `.feature` can use them. A workflow's own steps live beside it.
|
|
3
|
+
import {
|
|
4
|
+
Given,
|
|
5
|
+
QuickPickleWorld,
|
|
6
|
+
Then,
|
|
7
|
+
When,
|
|
8
|
+
setWorldConstructor,
|
|
9
|
+
type InfoConstructor,
|
|
10
|
+
type QuickPickleWorldInterface,
|
|
11
|
+
} from "quickpickle"
|
|
12
|
+
import assert from "node:assert"
|
|
13
|
+
import type { TestContext } from "vitest"
|
|
14
|
+
import { runsItsScript, testRepo, type Actor, type TestRepo } from "./TestRepo.js"
|
|
15
|
+
|
|
16
|
+
export class TestkitWorld extends QuickPickleWorld {
|
|
17
|
+
// Re-declared: under `nodenext` a subclass loses the base's members from TS's view.
|
|
18
|
+
declare info: QuickPickleWorldInterface["info"]
|
|
19
|
+
|
|
20
|
+
constructor(context: TestContext, info: InfoConstructor) {
|
|
21
|
+
super(context, info)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private gtdConfig: string | undefined = undefined
|
|
25
|
+
private env: Record<string, string> = {}
|
|
26
|
+
/** Per step, what its scripted actor does, in the order the scenario declared it. */
|
|
27
|
+
actions = new Map<string, Actor[]>()
|
|
28
|
+
verdicts = new Map<string, string>()
|
|
29
|
+
private created: TestRepo | undefined = undefined
|
|
30
|
+
|
|
31
|
+
// The repository is created on first use and keeps the config and
|
|
32
|
+
// environment it was created with, so a later change to either is refused
|
|
33
|
+
// rather than silently ignored.
|
|
34
|
+
configure(config: string): void {
|
|
35
|
+
this.assertNotCreated("a gtd config")
|
|
36
|
+
this.gtdConfig = config
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setEnv(name: string, value: string): void {
|
|
40
|
+
this.assertNotCreated(`the environment variable ${name}`)
|
|
41
|
+
this.env[name] = value
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private assertNotCreated(what: string): void {
|
|
45
|
+
if (this.created !== undefined) {
|
|
46
|
+
throw new Error(`${what} must be set before any step that touches the repository`)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
repo(): TestRepo {
|
|
51
|
+
if (this.gtdConfig === undefined)
|
|
52
|
+
throw new Error("no gtd config: add a `gtd config` step first")
|
|
53
|
+
return (this.created ??= testRepo({ workflow: this.gtdConfig, env: this.env }))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
act(step: string, action: Actor): void {
|
|
57
|
+
this.actions.set(step, [...(this.actions.get(step) ?? []), action])
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async drive(): Promise<void> {
|
|
61
|
+
const actors: Record<string, Actor> = {}
|
|
62
|
+
for (const [step, actions] of this.actions) {
|
|
63
|
+
actors[step] = async (beat, repo) => {
|
|
64
|
+
for (const action of actions) await action(beat, repo)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
for (const [step, verdict] of this.verdicts) actors[step] = () => verdict
|
|
68
|
+
await this.repo().drive(actors)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setWorldConstructor(TestkitWorld)
|
|
73
|
+
|
|
74
|
+
const withNewline = (content: string): string => (content.endsWith("\n") ? content : `${content}\n`)
|
|
75
|
+
|
|
76
|
+
const args = (list: string): string[] => list.split(" ").filter((arg) => arg !== "")
|
|
77
|
+
|
|
78
|
+
Given("a gtd config with:", (world: TestkitWorld, source: string) => {
|
|
79
|
+
world.configure(withNewline(source))
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
Given(
|
|
83
|
+
"an environment variable {string} set to {string}",
|
|
84
|
+
(world: TestkitWorld, name: string, value: string) => {
|
|
85
|
+
world.setEnv(name, value)
|
|
86
|
+
},
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
Given("a file {string} with:", (world: TestkitWorld, path: string, content: string) => {
|
|
90
|
+
world.repo().write(path, withNewline(content))
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
Given("the working tree is committed as {string}", (world: TestkitWorld, message: string) => {
|
|
94
|
+
world.repo().commit(message)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
Given("step {string} changes nothing", (world: TestkitWorld, step: string) => {
|
|
98
|
+
world.act(step, () => undefined)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
Given(
|
|
102
|
+
"step {string} writes {string} with:",
|
|
103
|
+
(world: TestkitWorld, step: string, path: string, content: string) => {
|
|
104
|
+
world.act(step, (_, repo) => repo.write(path, withNewline(content)))
|
|
105
|
+
},
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
Given("step {string} runs its script", (world: TestkitWorld, step: string) => {
|
|
109
|
+
world.act(step, runsItsScript)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
Given("judge step {string} answers:", (world: TestkitWorld, step: string, verdict: string) => {
|
|
113
|
+
world.verdicts.set(step, String(verdict))
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
When("gtd starts workflow {string}", async (world: TestkitWorld, workflow: string) => {
|
|
117
|
+
await world.repo().start(workflow)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
When(
|
|
121
|
+
"gtd starts workflow {string} with {string}",
|
|
122
|
+
async (world: TestkitWorld, workflow: string, list: string) => {
|
|
123
|
+
await world.repo().start(workflow, ...args(list))
|
|
124
|
+
},
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
When("gtd opens door {string}", async (world: TestkitWorld, door: string) => {
|
|
128
|
+
await world.repo().door(door)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
When(
|
|
132
|
+
"gtd opens door {string} with {string}",
|
|
133
|
+
async (world: TestkitWorld, door: string, list: string) => {
|
|
134
|
+
await world.repo().door(door, ...args(list))
|
|
135
|
+
},
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
When("gtd lands", async (world: TestkitWorld) => {
|
|
139
|
+
await world.repo().land()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
When("the process is driven", async (world: TestkitWorld) => {
|
|
143
|
+
await world.drive()
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
Then(
|
|
147
|
+
"opening door {string} is refused with {string}",
|
|
148
|
+
async (world: TestkitWorld, door: string, message: string) => {
|
|
149
|
+
await assert.rejects(world.repo().door(door), (error: Error) => error.message.includes(message))
|
|
150
|
+
},
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
Then("the process rests at {string}", async (world: TestkitWorld, step: string) => {
|
|
154
|
+
assert.strictEqual((await world.repo().next()).state, step)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
Then("the resting step's content contains {string}", async (world: TestkitWorld, text: string) => {
|
|
158
|
+
const { content } = await world.repo().next()
|
|
159
|
+
assert.ok(content.includes(text), content)
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
Then("the last commit subject is {string}", (world: TestkitWorld, subject: string) => {
|
|
163
|
+
assert.strictEqual(world.repo().subjects().at(-1), subject)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
Then("the commit subjects end with:", (world: TestkitWorld, list: string) => {
|
|
167
|
+
const expected = list.split("\n").filter((line) => line.trim() !== "")
|
|
168
|
+
assert.deepStrictEqual(world.repo().subjects().slice(-expected.length), expected)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
Then("{string} contains {string}", (world: TestkitWorld, path: string, text: string) => {
|
|
172
|
+
assert.ok(world.repo().read(path)?.includes(text), `${path}: ${world.repo().read(path)}`)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
Then("{string} does not exist", (world: TestkitWorld, path: string) => {
|
|
176
|
+
assert.strictEqual(world.repo().read(path), undefined)
|
|
177
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Feature: testkit drives any workflow against the real engine
|
|
2
|
+
|
|
3
|
+
A workflow the scenario writes itself, never a shipped one: scripted actors
|
|
4
|
+
stand in for the agent, the check and the judge.
|
|
5
|
+
|
|
6
|
+
Background:
|
|
7
|
+
Given a gtd config with:
|
|
8
|
+
"""
|
|
9
|
+
import { agent, human, judge, read, run } from "@pmelab/gtd/flows"
|
|
10
|
+
|
|
11
|
+
export const tiny = async () => {
|
|
12
|
+
await agent("plan", "Write PLAN.md.")
|
|
13
|
+
await run("check", "npm test > .gtd/FEEDBACK.md 2>&1 && rm .gtd/FEEDBACK.md")
|
|
14
|
+
if (read(".gtd/FEEDBACK.md") !== undefined) await agent("repair", "Make the suite pass.")
|
|
15
|
+
const { answers } = await judge("ready", {
|
|
16
|
+
questions: [{ id: "ready", primitive: "noul", instructions: "Ready?", criteria: "" }],
|
|
17
|
+
evidence: { plan: read("PLAN.md") ?? "" },
|
|
18
|
+
})
|
|
19
|
+
await human(answers.ready?.answer === "yes" ? "ship" : "rework")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default tiny
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
Scenario: a green check goes straight to the judge, whose verdict picks the gate
|
|
26
|
+
Given step "plan" writes "PLAN.md" with:
|
|
27
|
+
"""
|
|
28
|
+
add two numbers
|
|
29
|
+
"""
|
|
30
|
+
And step "check" changes nothing
|
|
31
|
+
And judge step "ready" answers:
|
|
32
|
+
"""
|
|
33
|
+
[{ "id": "ready", "answer": true, "p": 0.9 }]
|
|
34
|
+
"""
|
|
35
|
+
When gtd starts workflow "tiny"
|
|
36
|
+
And the process is driven
|
|
37
|
+
Then the process rests at "ship"
|
|
38
|
+
And the commit subjects end with:
|
|
39
|
+
"""
|
|
40
|
+
gtd(human): plan
|
|
41
|
+
gtd(agent): plan → check
|
|
42
|
+
gtd(check): check → ready
|
|
43
|
+
gtd(judge): ready → ship
|
|
44
|
+
"""
|
|
45
|
+
And "PLAN.md" contains "add two numbers"
|
|
46
|
+
|
|
47
|
+
Scenario: a red check sends the process to repair, and an unscripted step is where it rests
|
|
48
|
+
Given step "plan" writes "PLAN.md" with:
|
|
49
|
+
"""
|
|
50
|
+
add two numbers
|
|
51
|
+
"""
|
|
52
|
+
And step "check" writes ".gtd/FEEDBACK.md" with:
|
|
53
|
+
"""
|
|
54
|
+
1 test failing
|
|
55
|
+
"""
|
|
56
|
+
When gtd starts workflow "tiny"
|
|
57
|
+
And the process is driven
|
|
58
|
+
Then the process rests at "repair"
|
|
59
|
+
And the resting step's content contains "Make the suite pass."
|
|
60
|
+
And the last commit subject is "gtd(check): check → repair"
|
|
61
|
+
|
|
62
|
+
# Fails if run: proves the `workflows` project honours @skip.
|
|
63
|
+
@skip
|
|
64
|
+
Scenario: a scenario tagged @skip never runs
|
|
65
|
+
Then the process rests at "a step this workflow does not have"
|