@pmelab/gtd 27.3.0 → 27.5.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 +22 -1
- package/dist/chunks/{Schema-CyImoHTj.mjs → Schema-DO5ttJaI.mjs} +1 -1
- package/dist/chunks/{reserved-BPdVZkgv.mjs → reserved-DM5W_LAt.mjs} +64 -3
- package/dist/chunks/{tail-iLAMoigl.mjs → tail-6gvLXXcg.mjs} +1 -1
- package/dist/chunks/{wire-BY1S-obW.mjs → wire-C12dahrH.mjs} +6 -4
- package/dist/flows.mjs +27 -2
- package/dist/gtd.bundle.mjs +218 -32
- package/dist/testkit.mjs +358 -37
- package/dist/wire.mjs +1 -1
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +140 -64
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +2 -2
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +346 -107
- package/package.json +1 -1
- package/src/cli/Cli.test.ts +49 -1
- package/src/cli/Cli.ts +25 -8
- package/src/cli/History.ts +5 -0
- package/src/cli/Program.ts +1 -0
- package/src/edge/Edge.test.ts +205 -1
- package/src/edge/Edge.ts +105 -12
- package/src/emit/GitScript.test.ts +39 -3
- package/src/emit/GitScript.ts +25 -1
- package/src/emit/index.ts +2 -0
- package/src/flags/index.test.ts +14 -1
- package/src/flags/index.ts +14 -3
- package/src/flows/helpers.ts +34 -2
- package/src/flows/runtime.ts +7 -0
- package/src/install/Install.ts +2 -1
- package/src/platform/Git.test.ts +25 -1
- package/src/platform/Git.ts +30 -0
- package/src/platform/index.ts +1 -0
- package/src/replay/Format.test.ts +16 -1
- package/src/replay/Replay.test.ts +91 -0
- package/src/replay/Replay.ts +22 -2
- package/src/replay/Trailers.test.ts +110 -1
- package/src/replay/Trailers.ts +37 -1
- package/src/replay/index.ts +2 -0
- package/src/replay/judgeDiffShape.test.ts +43 -0
- package/src/replay/seamGate.test.ts +62 -0
- package/src/step/LandStep.ts +1 -0
- package/src/step/planStart.ts +3 -2
- package/src/step/planStep.test.ts +32 -0
- package/src/step/planStep.ts +3 -1
- package/src/testkit/EmittedScriptRecognizer.test.ts +27 -0
- package/src/testkit/EmittedScriptRecognizer.ts +12 -0
- package/src/testkit/FakeGitOperations.ts +5 -0
- package/src/testkit/GitTiers.ts +158 -0
- package/src/testkit/InMemRepo.ts +38 -3
- package/src/testkit/TestRepo.ts +3 -0
- package/src/testkit/steps.ts +18 -0
- package/src/wire/BeatDocument.test.ts +25 -0
- package/src/wire/BeatDocument.ts +4 -2
- package/src/wire/BeatPlain.test.ts +1 -0
- package/src/wire/BeatStatus.test.ts +2 -0
- package/src/wire/BeatStatus.ts +13 -2
- package/src/wire/WireSchema.test.ts +10 -0
- package/src/wire/types.ts +8 -0
- package/src/workflow/Workflow.ts +1 -0
- package/src/workflow/index.ts +8 -1
- package/src/workflow/vars.test.ts +23 -1
- package/src/workflow/vars.ts +24 -0
- package/src/workflows/feature/feature.ts +9 -2
- package/src/workflows/feature/index.ts +1 -0
- package/src/workflows/feature/seam-pin.feature +52 -0
- package/src/workflows/feature/seam.feature +387 -0
- package/src/workflows/feature/seam.test.ts +88 -0
- package/src/workflows/feature/seam.ts +110 -0
- package/src/workflows/index.ts +1 -0
- package/src/workflows/shared/options.test.ts +12 -1
- package/src/workflows/shared/options.ts +1 -0
- package/src/workflows/shared/skills.test.ts +20 -2
- package/src/workflows/shared/skills.ts +6 -0
- package/src/workflows/shared/text.test.ts +59 -0
- package/src/workflows/shared/text.ts +86 -0
- package/src/workflows/shared/vars.ts +4 -0
- package/src/workflows/shared/workflows.steps.ts +39 -1
|
@@ -17,6 +17,11 @@ import {
|
|
|
17
17
|
buildReviewCollectingPrompt,
|
|
18
18
|
designGateAnswerMessage,
|
|
19
19
|
designTriagePrompt,
|
|
20
|
+
implementBuildingPrompt,
|
|
21
|
+
implementFixPrompt,
|
|
22
|
+
naivePassPrompt,
|
|
23
|
+
seamByHandMessage,
|
|
24
|
+
seamSteerMessage,
|
|
20
25
|
summaryPrompt,
|
|
21
26
|
withSkills,
|
|
22
27
|
} from "./text.js"
|
|
@@ -441,3 +446,57 @@ describe("buildReviewReviewingPrompt scenarios", () => {
|
|
|
441
446
|
expect(prompt).not.toContain("Weakened:")
|
|
442
447
|
})
|
|
443
448
|
})
|
|
449
|
+
|
|
450
|
+
describe("the seam phase's texts", () => {
|
|
451
|
+
const rejection = { strays: ["src/flows/helpers.ts", "src/flows/runtime.ts"], p: 0.85 }
|
|
452
|
+
|
|
453
|
+
it("asks the first naive pass for the quickest working change, with no rejection", () => {
|
|
454
|
+
const prompt = renderText(() => naivePassPrompt())
|
|
455
|
+
expect(prompt).toContain(".gtd/REQUIREMENTS.md")
|
|
456
|
+
expect(prompt).not.toContain("rejected")
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
it("names the reject verdict, its p, every stray and the criterion on a retry", () => {
|
|
460
|
+
const prompt = renderText(() => naivePassPrompt(rejection))
|
|
461
|
+
expect(prompt).toContain("reject")
|
|
462
|
+
expect(prompt).toContain("0.85")
|
|
463
|
+
expect(prompt).toContain("`src/flows/helpers.ts`")
|
|
464
|
+
expect(prompt).toContain("`src/flows/runtime.ts`")
|
|
465
|
+
expect(prompt).toContain("solved the task the wrong way")
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
it("cites the reference as reading material only and asks for a new release flag", () => {
|
|
469
|
+
const prompt = renderText(() => implementBuildingPrompt("abc123"))
|
|
470
|
+
expect(prompt).toContain("git show abc123")
|
|
471
|
+
expect(prompt).toContain("never apply")
|
|
472
|
+
expect(prompt).toContain("release flag")
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
it("keeps a fix turn to the feedback file's rules", () => {
|
|
476
|
+
expect(renderText(() => implementFixPrompt())).toContain(".gtd/FEEDBACK.md")
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
it("names both steer answers and the overrule risk, and lists every stray", () => {
|
|
480
|
+
const message = renderText(() => seamSteerMessage(rejection))
|
|
481
|
+
expect(message).toContain("edit `.gtd/REQUIREMENTS.md`")
|
|
482
|
+
expect(message).toContain("clean")
|
|
483
|
+
expect(message).toContain("Overrule")
|
|
484
|
+
expect(message).toContain("unaddressed")
|
|
485
|
+
expect(message).toContain("src/flows/helpers.ts")
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
it("names every unreadable path for a by-hand landing", () => {
|
|
489
|
+
const message = renderText(() =>
|
|
490
|
+
seamByHandMessage(["vendor/a.bin", "src/flows/helpers.ts"], ["vendor/a.bin"], "abc123"),
|
|
491
|
+
)
|
|
492
|
+
expect(message).toContain("`vendor/a.bin` (unreadable)")
|
|
493
|
+
expect(message).toContain("`src/flows/helpers.ts`\n")
|
|
494
|
+
expect(message).toContain("git show abc123 --")
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
it("keeps a stray path with a newline on one line", () => {
|
|
498
|
+
const message = renderText(() => seamSteerMessage({ strays: ["a\nb.ts"], p: 0.9 }))
|
|
499
|
+
expect(message).toContain('`"a\\nb.ts"`')
|
|
500
|
+
expect(message).not.toContain("a\nb.ts")
|
|
501
|
+
})
|
|
502
|
+
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
agent,
|
|
3
3
|
codeThreads,
|
|
4
|
+
displayPath,
|
|
4
5
|
env,
|
|
5
6
|
head,
|
|
6
7
|
quote,
|
|
@@ -1017,3 +1018,88 @@ ${it.processCostByModel.map((m) => `- ${m.model}: ${m.cost}\n`).join("")}
|
|
|
1017
1018
|
Print the closing message and stop — this writes nothing itself.
|
|
1018
1019
|
`
|
|
1019
1020
|
}
|
|
1021
|
+
|
|
1022
|
+
// ── The seam phase (release flag `naive_pass`) ──────────────────────────────
|
|
1023
|
+
|
|
1024
|
+
const listed = (paths: readonly string[], unreadable: readonly string[] = []): string =>
|
|
1025
|
+
paths
|
|
1026
|
+
.map(
|
|
1027
|
+
(path) => ` - \`${displayPath(path)}\`${unreadable.includes(path) ? " (unreadable)" : ""}`,
|
|
1028
|
+
)
|
|
1029
|
+
.join("\n")
|
|
1030
|
+
|
|
1031
|
+
export interface SeamRejection {
|
|
1032
|
+
readonly strays: readonly string[]
|
|
1033
|
+
readonly p: number
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
export const naivePassPrompt = (rejection?: SeamRejection): string =>
|
|
1037
|
+
`${stateFileRules}
|
|
1038
|
+
- Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
|
|
1039
|
+
- Implement them the quickest way that works — the naive version. Do not
|
|
1040
|
+
restructure, abstract or refactor around the change; a later step judges
|
|
1041
|
+
the shape of your diff and a reviewer makes it fit
|
|
1042
|
+
- Add tests as you go, and leave \`.gtd/\` untouched
|
|
1043
|
+
- Leave everything uncommitted and finish your turn — do not commit
|
|
1044
|
+
${
|
|
1045
|
+
rejection === undefined
|
|
1046
|
+
? ""
|
|
1047
|
+
: `
|
|
1048
|
+
Your previous attempt was rejected: the judge answered \`reject\` (p=${rejection.p}),
|
|
1049
|
+
meaning the naive pass solved the task the wrong way — discard it and
|
|
1050
|
+
re-implement. It changed these files outside the expected set:
|
|
1051
|
+
${listed(rejection.strays)}
|
|
1052
|
+
Start over and choose a different approach; do not rebuild the same diff.
|
|
1053
|
+
`
|
|
1054
|
+
}`
|
|
1055
|
+
|
|
1056
|
+
export const implementBuildingPrompt = (reference: string): string =>
|
|
1057
|
+
`${stateFileRules}
|
|
1058
|
+
- Read \`.gtd/REQUIREMENTS.md\`: the settled requirements for this change
|
|
1059
|
+
- Implement them properly, fitting the codebase's seams and conventions
|
|
1060
|
+
- Reference only: \`git show ${reference}\` shows an earlier naive attempt.
|
|
1061
|
+
Read it for ideas; never apply, cherry-pick or check it out — write
|
|
1062
|
+
the change fresh
|
|
1063
|
+
- Put the new behaviour behind a new release flag, following the
|
|
1064
|
+
repository's flag conventions
|
|
1065
|
+
- Write the tests first, then the code that makes them pass
|
|
1066
|
+
- Leave \`.gtd/REQUIREMENTS.md\` in place and everything uncommitted, then
|
|
1067
|
+
finish your turn
|
|
1068
|
+
`
|
|
1069
|
+
|
|
1070
|
+
export const implementFixPrompt = (): string =>
|
|
1071
|
+
`${stateFileRules}
|
|
1072
|
+
${fixFeedbackPrompt}
|
|
1073
|
+
- Leave everything uncommitted and finish your turn — do not commit
|
|
1074
|
+
`
|
|
1075
|
+
|
|
1076
|
+
export const seamSteerMessage = (rejection: SeamRejection): string =>
|
|
1077
|
+
`The naive pass was rejected three times and the retries are spent.
|
|
1078
|
+
The last attempt changed these files outside the expected set:
|
|
1079
|
+
${listed(rejection.strays)}
|
|
1080
|
+
|
|
1081
|
+
Two ways forward (then run \`gtd land\`):
|
|
1082
|
+
- **Steer** — edit \`.gtd/REQUIREMENTS.md\` to redirect the change; the naive
|
|
1083
|
+
pass runs again with a fresh retry budget
|
|
1084
|
+
- **Overrule** — land a clean tree to accept the last attempt's approach:
|
|
1085
|
+
the implementation is built against it as reference, with no seam gate
|
|
1086
|
+
and no refactor. Risk: this ships the seam the judge objected to,
|
|
1087
|
+
unaddressed — the choice is recorded in history.
|
|
1088
|
+
|
|
1089
|
+
An edit together with an approval counts as steer. Only
|
|
1090
|
+
\`.gtd/REQUIREMENTS.md\` is meant to change here: any other edit lands on the
|
|
1091
|
+
branch unjudged.
|
|
1092
|
+
`
|
|
1093
|
+
|
|
1094
|
+
export const seamByHandMessage = (
|
|
1095
|
+
strays: readonly string[],
|
|
1096
|
+
unreadable: readonly string[],
|
|
1097
|
+
reference: string,
|
|
1098
|
+
): string =>
|
|
1099
|
+
`The judge could not read some of these files, so the diff was never
|
|
1100
|
+
fully judged and no refactor runs. Unreadable ones are marked:
|
|
1101
|
+
${listed(strays, unreadable)}
|
|
1102
|
+
|
|
1103
|
+
See them with \`git show ${reference} -- <path>\` and review by hand. Land a
|
|
1104
|
+
clean tree to go on to the implementation, which runs next.
|
|
1105
|
+
`
|
|
@@ -12,6 +12,10 @@ export const defaults: Readonly<Record<string, string>> = {
|
|
|
12
12
|
reviewNoteActionable: "0.7",
|
|
13
13
|
reviewSeverityMinP: "0.7",
|
|
14
14
|
refactorFlagPattern: "\\bGTD_FLAG_\\w+",
|
|
15
|
+
seamAllow:
|
|
16
|
+
"**/*.test.*, **/*.spec.*, **/*.feature, **/tests/**, **/test/**, **/__tests__/**, .gtd/**",
|
|
17
|
+
// GTD_FLAG_NAIVE_PASS, recorded at process start; only "1" is on.
|
|
18
|
+
flag_naive_pass: "0",
|
|
15
19
|
// Decides the turn count: one `build.quality.<lens>` scope per entry, each
|
|
16
20
|
// keyed in `./skills.ts`.
|
|
17
21
|
qualityReviews:
|
|
@@ -1,7 +1,45 @@
|
|
|
1
|
-
import { Given } from "quickpickle"
|
|
1
|
+
import { After, Given, Then } from "quickpickle"
|
|
2
2
|
import { CONFIG_SCAFFOLD } from "../../install/index.js"
|
|
3
|
+
import assert from "node:assert"
|
|
3
4
|
import type { TestkitWorld } from "../../testkit/index.js"
|
|
4
5
|
|
|
5
6
|
Given("the shipped workflows, as gtd install scaffolds them", (world: TestkitWorld) => {
|
|
6
7
|
world.configure(CONFIG_SCAFFOLD)
|
|
7
8
|
})
|
|
9
|
+
|
|
10
|
+
// Ambient, not explicit: the repository's own env is fixed when it is created,
|
|
11
|
+
// and only a real environment variable can change between two steps of one process.
|
|
12
|
+
const restores: (() => void)[] = []
|
|
13
|
+
|
|
14
|
+
Given(
|
|
15
|
+
"the ambient environment variable {string} is {string}",
|
|
16
|
+
(_: TestkitWorld, name: string, value: string) => {
|
|
17
|
+
const before = process.env[name]
|
|
18
|
+
restores.push(() => {
|
|
19
|
+
if (before === undefined) delete process.env[name]
|
|
20
|
+
else process.env[name] = before
|
|
21
|
+
})
|
|
22
|
+
process.env[name] = value
|
|
23
|
+
},
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
After(() => {
|
|
27
|
+
while (restores.length > 0) restores.pop()!()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
Then("the last commit body contains {string}", (world: TestkitWorld, text: string) => {
|
|
31
|
+
const message = world.repo().messages().at(-1) ?? ""
|
|
32
|
+
assert.ok(message.includes(text), message)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
Then(
|
|
36
|
+
"the commit {string} has a body containing {string}",
|
|
37
|
+
(world: TestkitWorld, subject: string, text: string) => {
|
|
38
|
+
const message = world
|
|
39
|
+
.repo()
|
|
40
|
+
.messages()
|
|
41
|
+
.find((m) => m.split("\n")[0] === subject)
|
|
42
|
+
assert.ok(message !== undefined, `no commit "${subject}"`)
|
|
43
|
+
assert.ok(message.includes(text), message)
|
|
44
|
+
},
|
|
45
|
+
)
|