@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
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest"
|
|
2
|
+
import { agent, read } from "../flows/index.js"
|
|
3
|
+
import { installContext, type FlowContext, type StepRequest } from "./index.js"
|
|
4
|
+
|
|
5
|
+
afterEach(() => installContext(undefined))
|
|
6
|
+
|
|
7
|
+
describe("the flow-context harness", () => {
|
|
8
|
+
it("renders a prompt and records a step against an installed context", async () => {
|
|
9
|
+
const requests: StepRequest[] = []
|
|
10
|
+
installContext({
|
|
11
|
+
read: (path: string) => (path === "PLAN.md" ? "add a button" : undefined),
|
|
12
|
+
step: async (request: StepRequest) => {
|
|
13
|
+
requests.push(request)
|
|
14
|
+
},
|
|
15
|
+
} as Partial<FlowContext> as FlowContext)
|
|
16
|
+
|
|
17
|
+
await agent("build", `Build: ${read("PLAN.md")}`)
|
|
18
|
+
|
|
19
|
+
expect(requests).toEqual([
|
|
20
|
+
{ kind: "agent", name: "build", prompt: "Build: add a button", options: {} },
|
|
21
|
+
])
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it("leaves a flow failing outside a replay once uninstalled", () => {
|
|
25
|
+
installContext(undefined)
|
|
26
|
+
expect(() => read("PLAN.md")).toThrow("called outside a gtd replay")
|
|
27
|
+
})
|
|
28
|
+
})
|
package/src/testkit/index.ts
CHANGED
|
@@ -2,15 +2,27 @@
|
|
|
2
2
|
// repository. It imports no test runner, so any runner can use it; the
|
|
3
3
|
// gherkin steps are the opt-in \`@pmelab/gtd/testkit/gherkin\`.
|
|
4
4
|
export {
|
|
5
|
+
inTurn,
|
|
5
6
|
runsItsScript,
|
|
6
7
|
testRepo,
|
|
7
|
-
|
|
8
|
+
verdict,
|
|
9
|
+
verdictWhen,
|
|
10
|
+
whenPrompted,
|
|
8
11
|
type Actors,
|
|
9
12
|
type CannedCommand,
|
|
10
13
|
type DriveOptions,
|
|
14
|
+
type RunScriptOptions,
|
|
15
|
+
type ScriptedActor,
|
|
16
|
+
type TestCommit,
|
|
11
17
|
type TestRepo,
|
|
12
18
|
type TestRepoOptions,
|
|
19
|
+
type Verdict,
|
|
20
|
+
type VerdictEntry,
|
|
13
21
|
type WorkflowModule,
|
|
14
22
|
} from "./TestRepo.js"
|
|
15
|
-
export {
|
|
23
|
+
export type { BeatDocument } from "../wire/index.js"
|
|
24
|
+
export type { CapturedCliResult } from "../twins/index.js"
|
|
16
25
|
export type { TestkitWorld } from "../gherkin/index.js"
|
|
26
|
+
// The flow-context harness: render a prompt or stub a step synchronously,
|
|
27
|
+
// against a replay context the test installs instead of the engine.
|
|
28
|
+
export { installContext, type FlowContext, type StepRequest } from "../flows/internal.js"
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
softResetTo,
|
|
17
17
|
updateRef,
|
|
18
18
|
binaryGuard,
|
|
19
|
-
DID_NOT_RUN_COMMENT,
|
|
20
19
|
failurePromptWrapper,
|
|
21
20
|
fileExistsGuard,
|
|
22
21
|
PRESENTATION_FAILURE_WARNING,
|
|
@@ -31,7 +30,7 @@ import {
|
|
|
31
30
|
formatFinding,
|
|
32
31
|
steeringFormatFor,
|
|
33
32
|
type SteeringFormat,
|
|
34
|
-
} from "../steering/
|
|
33
|
+
} from "../steering/internal.js"
|
|
35
34
|
import { refNameFault } from "../replay/index.js"
|
|
36
35
|
import { renderFormat } from "../wire/index.js"
|
|
37
36
|
import { applyFlowScript, rewritten } from "./FlowScripts.js"
|
|
@@ -519,12 +518,8 @@ const recognizeGtdCheck = (repo: InMemRepo, block: string): BlockOutcome | undef
|
|
|
519
518
|
const known = BUILT_IN_MODE_NAMES.join(", ")
|
|
520
519
|
return { kind: "failed", error: `gtd check: unknown mode "${mode}" — known modes: ${known}` }
|
|
521
520
|
}
|
|
522
|
-
// An ABSENT file has nothing to check and exits 0 — `runCheckCommand`'s own
|
|
523
|
-
// documented behavior. Validating `""` instead would report every "missing
|
|
524
|
-
// header" finding the format has, turning "the reviewer deleted the file"
|
|
525
|
-
// (a case the review-doc guard owns) into a script failure.
|
|
526
521
|
const content = repo.readFile(file)
|
|
527
|
-
if (content === undefined) return { kind: "
|
|
522
|
+
if (content === undefined) return { kind: "failed", error: `gtd check: no such file: ${file}` }
|
|
528
523
|
const findings = format.validate(content).map((finding) => formatFinding(file, finding))
|
|
529
524
|
if (findings.length === 0) return { kind: "noop" }
|
|
530
525
|
return {
|
|
@@ -552,13 +547,11 @@ const recognizeGtdUncheck = (repo: InMemRepo, block: string): BlockOutcome | und
|
|
|
552
547
|
}
|
|
553
548
|
|
|
554
549
|
/**
|
|
555
|
-
*
|
|
556
|
-
*
|
|
557
|
-
* script, so recognizing it as a no-op (like the retry-helper function
|
|
558
|
-
* definition) keeps the `@inmem` tier green.
|
|
550
|
+
* A block of nothing but comments is a no-op in `sh` too — `combinedScript`'s
|
|
551
|
+
* leading `DID_NOT_RUN_COMMENT`, and the list of paths a start commits.
|
|
559
552
|
*/
|
|
560
|
-
const
|
|
561
|
-
block
|
|
553
|
+
const recognizeCommentBlock = (block: string): BlockOutcome | undefined =>
|
|
554
|
+
block.split("\n").every((line) => line.startsWith("#")) ? { kind: "noop" } : undefined
|
|
562
555
|
|
|
563
556
|
/** `combinedScript`'s optional half. Reports `noop` whatever the inner result, mirroring bash's `( … ) || <warning>`. Exception: under `git: false` any unrecognized inner block fails the outer script (git or not), so the in-memory twin knowingly diverges from `sh` rather than run git unseen. */
|
|
564
557
|
const PRESENTATION_FAILURE_TEXT = `${extractQuotedTokens(PRESENTATION_FAILURE_WARNING)[0]!.replace(/\\n$/, "")}\n`
|
|
@@ -695,7 +688,7 @@ const recognizersFor = (
|
|
|
695
688
|
git: boolean,
|
|
696
689
|
): ReadonlyArray<(block: string) => BlockOutcome | undefined> => [
|
|
697
690
|
(block) => recognizeSetFlags(block),
|
|
698
|
-
(block) =>
|
|
691
|
+
(block) => recognizeCommentBlock(block),
|
|
699
692
|
(block) => recognizePrecondition(repo, block),
|
|
700
693
|
(block) => recognizeFileExistsGuard(repo, block),
|
|
701
694
|
(block) => recognizeBinaryGuard(commands, block),
|
|
@@ -140,8 +140,10 @@ for (const makeTier of gitTiers) {
|
|
|
140
140
|
})
|
|
141
141
|
|
|
142
142
|
describe("gtd check", () => {
|
|
143
|
-
it("
|
|
144
|
-
|
|
143
|
+
it("refuses an absent file, and passes a valid one", () => {
|
|
144
|
+
const absent = run(script(`gtd check qa '${FILE}'`))
|
|
145
|
+
expect(absent).toMatchObject({ ok: false, status: 1 })
|
|
146
|
+
expect(absent.output).toContain(`gtd check: no such file: ${FILE}`)
|
|
145
147
|
t.seed.writeFile(FILE, "## Open Questions\n")
|
|
146
148
|
expect(run(script(`gtd check qa '${FILE}'`))).toEqual({ ok: true, status: 0, output: "" })
|
|
147
149
|
})
|
package/src/twins/InMemRepo.ts
CHANGED
|
@@ -30,6 +30,7 @@ function makeHash(message: string, parent: string | null, tree: Map<string, stri
|
|
|
30
30
|
export class InMemRepo {
|
|
31
31
|
// Data-only marker read by no code path: embeds the sentinel in every
|
|
32
32
|
// instance so a leaked object still carries it.
|
|
33
|
+
// fallow-ignore-next-line unused-class-member
|
|
33
34
|
readonly testDouble = TEST_DOUBLE_SENTINEL
|
|
34
35
|
|
|
35
36
|
private commits: Map<string, Commit> = new Map()
|
package/src/wire/Land.ts
CHANGED
|
@@ -71,5 +71,5 @@ export const landProseText = (subject: string): string => renderFormat(FMT_LAND_
|
|
|
71
71
|
*/
|
|
72
72
|
export const renderLandPlain = (fields: LandFields, command = "land"): string =>
|
|
73
73
|
fields.subject !== null
|
|
74
|
-
? `${landProseText(fields.subject).trimEnd()}\n(
|
|
74
|
+
? `${landProseText(fields.subject).trimEnd()}\n(to land it: s=$(gtd ${command} --json=script) && sh -c "$s")\n`
|
|
75
75
|
: noopText(fields.state)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { WIRE_SCHEMA } from "./constants.js"
|
|
2
|
+
|
|
3
|
+
/** `--json` of every command that prints a script and nothing else to decide on: abandon, restore, start, door, validate, history fetch. */
|
|
4
|
+
export interface ScriptFields {
|
|
5
|
+
readonly schema: number
|
|
6
|
+
readonly script: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const scriptFields = (script: string): ScriptFields => ({ schema: WIRE_SCHEMA, script })
|
|
10
|
+
|
|
11
|
+
export const renderScriptJson = (fields: ScriptFields): string => JSON.stringify(fields) + "\n"
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
landFields,
|
|
10
10
|
renderBeatJson,
|
|
11
11
|
renderLandJson,
|
|
12
|
+
renderScriptJson,
|
|
13
|
+
scriptFields,
|
|
12
14
|
statusOf,
|
|
13
15
|
WIRE_SCHEMA,
|
|
14
16
|
type BeatKind,
|
|
@@ -116,6 +118,7 @@ const documents: Readonly<Record<string, string>> = {
|
|
|
116
118
|
}),
|
|
117
119
|
),
|
|
118
120
|
"judge.json": JUDGE,
|
|
121
|
+
"script.json": renderScriptJson(scriptFields("git reset --mixed ...\n")),
|
|
119
122
|
}
|
|
120
123
|
|
|
121
124
|
describe(`wire schema ${WIRE_SCHEMA}`, () => {
|
package/src/wire/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export * from "./BeatStatus.js"
|
|
|
6
6
|
export * from "./BeatDocument.js"
|
|
7
7
|
export * from "./BeatPlain.js"
|
|
8
8
|
export * from "./Land.js"
|
|
9
|
+
export * from "./Script.js"
|
|
9
10
|
export * from "./Judge.js"
|
|
10
11
|
export { selectPath } from "./Select.js"
|
|
11
12
|
export { resolveSession } from "./Sessions.js"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
2
|
import fc from "fast-check"
|
|
3
3
|
import { isSeededValidateCommand, seededValidateCommand } from "./SteeringFormats.js"
|
|
4
|
-
import { BUILT_IN_MODE_NAMES } from "../steering/
|
|
4
|
+
import { BUILT_IN_MODE_NAMES } from "../steering/internal.js"
|
|
5
5
|
|
|
6
6
|
describe("seededValidateCommand / isSeededValidateCommand", () => {
|
|
7
7
|
it("recognizes its own seeded command for every built-in mode", () => {
|
package/src/workflow/compile.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { seededValidateCommand } from "./SteeringFormats.js"
|
|
2
|
-
import { BUILT_IN_MODE_NAMES } from "../steering/
|
|
2
|
+
import { BUILT_IN_MODE_NAMES } from "../steering/internal.js"
|
|
3
3
|
import type { ModeDef } from "./Workflow.js"
|
|
4
|
-
import { configProblems, type GtdConfig, type UiSettings } from "../flows/
|
|
4
|
+
import { configProblems, type GtdConfig, type UiSettings } from "../flows/internal.js"
|
|
5
5
|
import type { Diagnostic } from "./Diagnostic.js"
|
|
6
6
|
|
|
7
7
|
export interface CompiledConfig {
|
|
@@ -22,15 +22,15 @@ interface Tier {
|
|
|
22
22
|
|
|
23
23
|
const config = (imports: string): string =>
|
|
24
24
|
[
|
|
25
|
-
'import { agent, human } from "@pmelab/gtd/flows"',
|
|
25
|
+
'import { agent, human, workflow } from "@pmelab/gtd/flows"',
|
|
26
26
|
imports,
|
|
27
27
|
"export default async () => {",
|
|
28
28
|
' await human("idle", { message: "go" })',
|
|
29
29
|
' await agent("working", brief)',
|
|
30
30
|
"}",
|
|
31
|
-
"export const named = async () => {",
|
|
31
|
+
"export const named = workflow(async () => {",
|
|
32
32
|
' await agent("working", brief)',
|
|
33
|
-
"}",
|
|
33
|
+
"})",
|
|
34
34
|
"",
|
|
35
35
|
].join("\n")
|
|
36
36
|
|