@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,33 @@
|
|
|
1
|
+
import { start, type WorkflowBase } from "../../flows/index.js"
|
|
2
|
+
import { gate } from "../shared/health.js"
|
|
3
|
+
import { shipped } from "../shared/options.js"
|
|
4
|
+
import { handOffTail, refusePendingHandOff } from "../shared/tail.js"
|
|
5
|
+
import * as t from "../shared/text.js"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Pure review of everything since `reviewBase`: a green gate, the quality
|
|
9
|
+
* lap, then the human review. A note ends the process with `.gtd/REVIEW.md`
|
|
10
|
+
* left for `fix`; it never re-plans.
|
|
11
|
+
*/
|
|
12
|
+
export const reviewFlow = async (): Promise<void> => {
|
|
13
|
+
refusePendingHandOff()
|
|
14
|
+
await gate("review-gate", t.reviewGateBlockedMessage())
|
|
15
|
+
await handOffTail(false, start())
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const review = shipped(
|
|
19
|
+
reviewFlow,
|
|
20
|
+
{
|
|
21
|
+
review: {
|
|
22
|
+
args: [{ name: "base", optional: true }],
|
|
23
|
+
// Annotated: inferred, the literal union only fits under exactOptionalPropertyTypes.
|
|
24
|
+
vars: ({ base }): Readonly<Record<string, string>> =>
|
|
25
|
+
base === undefined ? {} : { reviewBase: base },
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
(_workflow, vars) => vars.reviewBase || "",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
/** @deprecated The `review` factory carries its own base; kept so a hand-written `review` re-exporting it still maps `reviewBase`. */
|
|
32
|
+
export const base: WorkflowBase = (workflow, vars) =>
|
|
33
|
+
workflow === "review" ? (vars.reviewBase ?? "") : undefined
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ScopeAccess } from "
|
|
2
|
-
import { lensesOf } from "
|
|
1
|
+
import type { ScopeAccess } from "../../flows/index.js"
|
|
2
|
+
import { lensesOf, lensScopes } from "../../workflow-kit/index.js"
|
|
3
3
|
|
|
4
4
|
// The bundled workflow's default file access, keyed by scope full name — the
|
|
5
5
|
// name a `config.access` entry addresses. Only planning and review scopes
|
|
@@ -16,7 +16,5 @@ export const access = (
|
|
|
16
16
|
// `{}` reopens what `build.review` restricted: the fixes edit code.
|
|
17
17
|
"build.review.fix.nits": {},
|
|
18
18
|
"build.review.fix.risks": {},
|
|
19
|
-
...
|
|
20
|
-
lensesOf(vars.qualityReviews ?? "").map((lens) => [`build.quality.${lens}`, { write: [] }]),
|
|
21
|
-
),
|
|
19
|
+
...lensScopes("build.quality", lensesOf(vars.qualityReviews ?? ""), () => ({ write: [] })),
|
|
22
20
|
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** A `.gtd/REVIEW.md` on the fixture calculator, one hunk per note. */
|
|
2
|
+
export const reviewDoc = (...notes: string[]): string =>
|
|
3
|
+
[
|
|
4
|
+
"# Review: abc1234",
|
|
5
|
+
"",
|
|
6
|
+
"<!-- base: 0000000 -->",
|
|
7
|
+
"",
|
|
8
|
+
"## calc",
|
|
9
|
+
"",
|
|
10
|
+
...notes.flatMap((note, i) => [`- [ ] ./src/calc.ts#${i + 1}-${i + 1}`, ` ${note}`]),
|
|
11
|
+
"",
|
|
12
|
+
].join("\n")
|
|
@@ -8,10 +8,10 @@ import {
|
|
|
8
8
|
numeric,
|
|
9
9
|
quote,
|
|
10
10
|
read,
|
|
11
|
-
scope,
|
|
12
11
|
vars,
|
|
13
12
|
type JudgeQuestion,
|
|
14
|
-
} from "
|
|
13
|
+
} from "../../flows/index.js"
|
|
14
|
+
import * as kit from "../../workflow-kit/index.js"
|
|
15
15
|
import { describeEscalation, escalate, escalationExhausted, ESCALATION, FEEDBACK } from "./steps.js"
|
|
16
16
|
import * as t from "./text.js"
|
|
17
17
|
|
|
@@ -35,21 +35,23 @@ export const fastSuiteGuard = (): string[] =>
|
|
|
35
35
|
/** Fix turns a run of red checks gets before it escalates. */
|
|
36
36
|
export const FIX_CAP = 3
|
|
37
37
|
|
|
38
|
+
const unconfigured = (): boolean => read(SETUP) !== undefined
|
|
39
|
+
|
|
38
40
|
/** Run the suite as step `name`; resolves `true` when it passed. A failure is in `.gtd/FEEDBACK.md`. */
|
|
39
|
-
export const baseline =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
report: FEEDBACK,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
})
|
|
46
|
-
if (read(SETUP) === undefined) return green
|
|
47
|
-
}
|
|
48
|
-
}
|
|
41
|
+
export const baseline = (name: string, label = "Checking the baseline"): Promise<boolean> =>
|
|
42
|
+
kit.untilSettled(
|
|
43
|
+
() =>
|
|
44
|
+
check(name, env.testCommand ?? "", { report: FEEDBACK, label, preamble: fastSuiteGuard() }),
|
|
45
|
+
unconfigured,
|
|
46
|
+
)
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
export type { EscalationCount } from "../../workflow-kit/index.js"
|
|
49
|
+
|
|
50
|
+
const escalationSteps: kit.EscalationSteps = {
|
|
51
|
+
limit: 2,
|
|
52
|
+
describe: describeEscalation,
|
|
53
|
+
stop: escalate,
|
|
54
|
+
exhausted: escalationExhausted,
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
/**
|
|
@@ -57,15 +59,8 @@ export interface EscalationCount {
|
|
|
57
59
|
* agent writes `.gtd/ESCALATION.md`, a human narrows it. From the third round
|
|
58
60
|
* on, the human is told the budget is spent instead.
|
|
59
61
|
*/
|
|
60
|
-
export const escalation =
|
|
61
|
-
|
|
62
|
-
count.rounds++
|
|
63
|
-
await describeEscalation()
|
|
64
|
-
await escalate()
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const STAMP = /\n<!-- gtd check [0-9a-f]+ -->\n?$/
|
|
68
|
-
const stripStamp = (text: string): string => text.replace(STAMP, "\n")
|
|
62
|
+
export const escalation = (count: kit.EscalationCount): Promise<void> =>
|
|
63
|
+
kit.escalation(count, escalationSteps)
|
|
69
64
|
|
|
70
65
|
/** "Is this red round the same failure as the last one?" — asked of two reports, or of nothing when one is empty. */
|
|
71
66
|
export const retryQuestion = (comparable: boolean): JudgeQuestion =>
|
|
@@ -107,7 +102,7 @@ export interface HealthOptions {
|
|
|
107
102
|
/** Fix turns already spent before this call. */
|
|
108
103
|
readonly fixesSoFar?: number
|
|
109
104
|
/** Escalation rounds shared with other callers in the same run of red checks. */
|
|
110
|
-
readonly escalations?: EscalationCount
|
|
105
|
+
readonly escalations?: kit.EscalationCount
|
|
111
106
|
/** `fast` runs `fastTestCommand` (default `full`: `testCommand`). */
|
|
112
107
|
readonly suite?: "full" | "fast"
|
|
113
108
|
/** Paths removed on a green run. */
|
|
@@ -130,37 +125,25 @@ const runSuite = (options: HealthOptions): Promise<boolean> => {
|
|
|
130
125
|
* first once `FIX_CAP` fixes are spent, or a judge calls the failure
|
|
131
126
|
* identical to the previous round's.
|
|
132
127
|
*/
|
|
133
|
-
export const healthy =
|
|
134
|
-
fix
|
|
135
|
-
options: HealthOptions = {},
|
|
136
|
-
): Promise<void> => {
|
|
137
|
-
const escalations = options.escalations ?? { rounds: 0 }
|
|
138
|
-
let fixes = options.fixesSoFar ?? 0
|
|
139
|
-
let previous: string | undefined
|
|
140
|
-
for (;;) {
|
|
141
|
-
const green = await runSuite(options)
|
|
128
|
+
export const healthy = (fix: () => Promise<void>, options: HealthOptions = {}): Promise<void> =>
|
|
129
|
+
kit.healthy(fix, {
|
|
142
130
|
// Unconfigured: straight back to the check, no fix turn or count.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
fixes++
|
|
156
|
-
await fix()
|
|
157
|
-
}
|
|
158
|
-
}
|
|
131
|
+
check:
|
|
132
|
+
options.suite === "fast"
|
|
133
|
+
? () => kit.untilSettled(() => runSuite(options), unconfigured)
|
|
134
|
+
: () => runSuite(options),
|
|
135
|
+
report: FEEDBACK,
|
|
136
|
+
fixCap: FIX_CAP,
|
|
137
|
+
fixesSoFar: options.fixesSoFar ?? 0,
|
|
138
|
+
sameFailure,
|
|
139
|
+
escalations: options.escalations ?? { rounds: 0 },
|
|
140
|
+
escalation: escalationSteps,
|
|
141
|
+
})
|
|
159
142
|
|
|
160
143
|
/** Hold the process at `<name>.blocked` until the suite is green. */
|
|
161
144
|
export const gate = (name: string, message: string): Promise<void> =>
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
|
|
145
|
+
kit.gate(
|
|
146
|
+
name,
|
|
147
|
+
() => baseline("check"),
|
|
148
|
+
() => human("blocked", { message, label: "Baseline is red", file: FEEDBACK }),
|
|
149
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
|
-
import { configure } from "
|
|
3
|
-
import * as workflows from "
|
|
2
|
+
import { configure } from "../../flows/index.js"
|
|
3
|
+
import * as workflows from "../index.js"
|
|
4
4
|
|
|
5
5
|
const { defaults, envDefaults, feature, fix, review, ShippedOptions } = workflows
|
|
6
6
|
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { Schema } from "effect"
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
defineWorkflow,
|
|
4
|
+
type Flow,
|
|
5
|
+
type WorkflowBase,
|
|
6
|
+
type WorkflowDoors,
|
|
7
|
+
type WorkflowFactory,
|
|
8
|
+
} from "../../flows/index.js"
|
|
3
9
|
import { defaults, envDefaults } from "./vars.js"
|
|
4
10
|
|
|
5
11
|
const setting = Schema.optionalWith(Schema.String, { exact: true })
|
|
@@ -27,12 +33,18 @@ const over = (
|
|
|
27
33
|
})
|
|
28
34
|
|
|
29
35
|
/** A shipped flow as a factory: its options override the settings it declares, each in its own kind. */
|
|
30
|
-
export const shipped = (
|
|
36
|
+
export const shipped = (
|
|
37
|
+
flow: Flow,
|
|
38
|
+
doors?: WorkflowDoors,
|
|
39
|
+
base?: WorkflowBase,
|
|
40
|
+
): WorkflowFactory<ShippedOptions> =>
|
|
31
41
|
defineWorkflow(
|
|
32
42
|
Schema.standardSchemaV1(ShippedOptions, { onExcessProperty: "error", errors: "all" }),
|
|
33
43
|
(options) => ({
|
|
34
44
|
flow,
|
|
35
45
|
defaults: over(defaults, options),
|
|
36
46
|
envDefaults: over(envDefaults, options),
|
|
47
|
+
doors,
|
|
48
|
+
base,
|
|
37
49
|
}),
|
|
38
50
|
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Summary } from "../../flows/index.js"
|
|
2
|
+
import * as t from "./text.js"
|
|
3
|
+
import { ARCHITECTURE, REQUIREMENTS, REVIEW } from "./steps.js"
|
|
4
|
+
|
|
5
|
+
export { defaults, envDefaults } from "./vars.js"
|
|
6
|
+
export { skills } from "./skills.js"
|
|
7
|
+
export { access } from "./access.js"
|
|
8
|
+
|
|
9
|
+
export const summary: Summary = t.summaryPrompt
|
|
10
|
+
|
|
11
|
+
export const steering = { [REQUIREMENTS]: "qa", [ARCHITECTURE]: "qa", [REVIEW]: "review" }
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { human, scope } from "../../flows/index.js"
|
|
2
|
+
import * as kit from "../../workflow-kit/index.js"
|
|
3
|
+
import * as t from "./text.js"
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
driftedScenarios,
|
|
7
|
+
freezeScenarios,
|
|
8
|
+
scenarioText,
|
|
9
|
+
wordingDrift,
|
|
10
|
+
type FrozenScenarios,
|
|
11
|
+
} from "../../workflow-kit/index.js"
|
|
12
|
+
|
|
13
|
+
/** After a turn: stop at `scenario-wording` while wording drifted; accept moves the snapshot. */
|
|
14
|
+
export const holdWording = (frozen: kit.FrozenScenarios | undefined): Promise<void> =>
|
|
15
|
+
kit.holdWording(frozen, (at, drifted) =>
|
|
16
|
+
human("scenario-wording", {
|
|
17
|
+
message: t.scenarioWordingMessage(at, drifted),
|
|
18
|
+
label: "Scenario wording changed",
|
|
19
|
+
acceptClean: true,
|
|
20
|
+
base: at,
|
|
21
|
+
}),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
/** `turn`, then `holdWording`; `within` names the scope the gate rests in. */
|
|
25
|
+
export const guarded =
|
|
26
|
+
(
|
|
27
|
+
frozen: kit.FrozenScenarios | undefined,
|
|
28
|
+
turn: () => Promise<void>,
|
|
29
|
+
within?: string,
|
|
30
|
+
): (() => Promise<void>) =>
|
|
31
|
+
async () => {
|
|
32
|
+
await turn()
|
|
33
|
+
if (within === undefined) await holdWording(frozen)
|
|
34
|
+
else await scope(within, () => holdWording(frozen))
|
|
35
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
2
|
import { skills } from "./skills.js"
|
|
3
|
-
import * as workflow from "
|
|
3
|
+
import * as workflow from "../index.js"
|
|
4
4
|
|
|
5
5
|
const { defaults } = workflow
|
|
6
6
|
|
|
@@ -25,6 +25,7 @@ describe("the bundled workflow's skills export", () => {
|
|
|
25
25
|
"build.review",
|
|
26
26
|
"build.review.fix.nits",
|
|
27
27
|
"build.review.fix.risks",
|
|
28
|
+
"notes",
|
|
28
29
|
"build.quality.correctness",
|
|
29
30
|
"build.quality.owasp-security",
|
|
30
31
|
"build.quality.ponytail-review",
|
|
@@ -55,6 +56,7 @@ describe("the bundled workflow's skills export", () => {
|
|
|
55
56
|
"build.review": ["code-review-and-quality"],
|
|
56
57
|
"build.review.fix.nits": ["incremental-implementation", "code-simplification"],
|
|
57
58
|
"build.review.fix.risks": ["debugging-and-error-recovery", "incremental-implementation"],
|
|
59
|
+
notes: ["incremental-implementation", "code-simplification"],
|
|
58
60
|
})
|
|
59
61
|
})
|
|
60
62
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { builtInLenses } from "./steps.js"
|
|
2
|
-
import { lensesOf } from "
|
|
2
|
+
import { lensesOf, lensScopes } from "../../workflow-kit/index.js"
|
|
3
3
|
|
|
4
4
|
// The bundled workflow's default skills, one list per SCOPE, keyed by the
|
|
5
5
|
// scope's full name — the same name a `config.skills` entry addresses. A
|
|
@@ -23,10 +23,11 @@ export const skills = (
|
|
|
23
23
|
"build.review": ["code-review-and-quality"],
|
|
24
24
|
"build.review.fix.nits": ["incremental-implementation", "code-simplification"],
|
|
25
25
|
"build.review.fix.risks": ["debugging-and-error-recovery", "incremental-implementation"],
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
// `fix`'s turn over the notes a review handed on.
|
|
27
|
+
notes: ["incremental-implementation", "code-simplification"],
|
|
28
|
+
...lensScopes(
|
|
29
|
+
"build.quality",
|
|
30
|
+
lensesOf(vars.qualityReviews ?? ""),
|
|
31
|
+
(lens) => builtInLenses[lens]?.skills ?? [lens],
|
|
31
32
|
),
|
|
32
33
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
|
-
import type { StepRequest } from "
|
|
2
|
+
import type { StepRequest } from "../../flows/index.js"
|
|
3
3
|
import { captureStep } from "./text.fixture.js"
|
|
4
4
|
import * as steps from "./steps.js"
|
|
5
5
|
|
|
@@ -15,31 +15,6 @@ const agentPrompt = (request: StepRequest): string => {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
describe("the bundled workflow's steps declare skills — a bundled step's rendered prompt carries its bundled skills.ts preamble", () => {
|
|
18
|
-
it("triage carries design.triage's bundled skills", async () => {
|
|
19
|
-
const prompt = agentPrompt(await capture(() => steps.triage("base"), "design"))
|
|
20
|
-
expect(prompt).toContain("spec-driven-development, planning-and-task-breakdown")
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
it("author carries architecture.author's bundled skills", async () => {
|
|
24
|
-
const prompt = agentPrompt(await capture(() => steps.author(), "architecture"))
|
|
25
|
-
expect(prompt).toContain("api-and-interface-design, documentation-and-adrs, ponytail")
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
it("decompose carries architecture.decompose's bundled skills", async () => {
|
|
29
|
-
const prompt = agentPrompt(await capture(() => steps.decompose(), "architecture"))
|
|
30
|
-
expect(prompt).toContain("incremental-implementation, planning-and-task-breakdown")
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
it("build carries packages.item.building's bundled skills", async () => {
|
|
34
|
-
const prompt = agentPrompt(await capture(() => steps.build("pkg"), "packages.item"))
|
|
35
|
-
expect(prompt).toContain("test-driven-development, incremental-implementation")
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it("fixSuite carries packages.item.fix.suite's bundled skills", async () => {
|
|
39
|
-
const prompt = agentPrompt(await capture(() => steps.fixSuite(), "packages.item"))
|
|
40
|
-
expect(prompt).toContain("debugging-and-error-recovery")
|
|
41
|
-
})
|
|
42
|
-
|
|
43
18
|
it("fix carries build.fix's bundled skills", async () => {
|
|
44
19
|
const prompt = agentPrompt(await capture(() => steps.fixCheck(), "build"))
|
|
45
20
|
expect(prompt).toContain("debugging-and-error-recovery")
|
|
@@ -104,52 +79,12 @@ describe("the bundled workflow's steps declare skills — a bundled step's rende
|
|
|
104
79
|
expect(prompt).toContain("code-review-and-quality")
|
|
105
80
|
})
|
|
106
81
|
|
|
107
|
-
it("collecting carries build.review.collecting's bundled skills", async () => {
|
|
108
|
-
const prompt = agentPrompt(await capture(() => steps.collecting("capture"), "build"))
|
|
109
|
-
expect(prompt).toContain("code-review-and-quality")
|
|
110
|
-
})
|
|
111
|
-
|
|
112
82
|
it("an empty configured list leaves that step's skills empty, and its rendered prompt bare", async () => {
|
|
113
83
|
const request = await capture(() => steps.fixCheck(), "build", { build: [] })
|
|
114
84
|
if (request.kind !== "agent") throw new Error("unreachable")
|
|
115
85
|
expect(request.prompt).not.toContain("Load whatever's listed here")
|
|
116
86
|
})
|
|
117
87
|
|
|
118
|
-
it("answerReviewQuestions answers inline in .gtd/REVIEW.md as the reviewer, with its bundled skills", async () => {
|
|
119
|
-
const request = await capture(
|
|
120
|
-
() =>
|
|
121
|
-
steps.answerReviewQuestions([{ id: "note-1", anchor: "calc ./a.ts#1-1", text: "why?" }]),
|
|
122
|
-
"build",
|
|
123
|
-
)
|
|
124
|
-
if (request.kind !== "agent") throw new Error("unreachable")
|
|
125
|
-
expect(request.name).toBe("review.answer-review-questions")
|
|
126
|
-
expect(request.options).toMatchObject({
|
|
127
|
-
file: ".gtd/REVIEW.md",
|
|
128
|
-
mode: "review",
|
|
129
|
-
})
|
|
130
|
-
expect(request.prompt).toContain("code-review-and-quality")
|
|
131
|
-
expect(request.prompt).toContain("note-1 — calc ./a.ts#1-1")
|
|
132
|
-
expect(request.prompt).toContain("`A: ` line")
|
|
133
|
-
expect(request.prompt).toContain("gtd check review .gtd/REVIEW.md")
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
it("fixNits fixes every nit in one turn and leaves .gtd/REVIEW.md alone", async () => {
|
|
137
|
-
const request = await capture(
|
|
138
|
-
() =>
|
|
139
|
-
steps.fixNits([
|
|
140
|
-
{ id: "note-1", anchor: "calc ./a.ts#1-1", text: "typo" },
|
|
141
|
-
{ id: "note-2", anchor: "calc ./a.ts#2-2", text: "semicolon" },
|
|
142
|
-
]),
|
|
143
|
-
"build",
|
|
144
|
-
)
|
|
145
|
-
if (request.kind !== "agent") throw new Error("unreachable")
|
|
146
|
-
expect(request.name).toBe("review.fix.nits.fixing")
|
|
147
|
-
expect(request.prompt).toContain("incremental-implementation, code-simplification")
|
|
148
|
-
expect(request.prompt).toContain("typo")
|
|
149
|
-
expect(request.prompt).toContain("semicolon")
|
|
150
|
-
expect(request.prompt).toContain("Leave `.gtd/REVIEW.md` untouched")
|
|
151
|
-
})
|
|
152
|
-
|
|
153
88
|
it("fixRisks fixes every risk, tolerates an empty turn, and leaves .gtd/REVIEW.md alone", async () => {
|
|
154
89
|
const request = await capture(
|
|
155
90
|
() => steps.fixRisks([{ id: "risk-1", anchor: "calc ./a.ts#1-1", text: "Risk: drops it" }]),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { env, human } from "
|
|
1
|
+
import { env, human } from "../../flows/index.js"
|
|
2
2
|
import * as t from "./text.js"
|
|
3
3
|
|
|
4
4
|
// The bundled workflow's single steps. A step's name is relative to the
|
|
@@ -13,71 +13,8 @@ export const ARCHITECTURE = ".gtd/ARCHITECTURE.md"
|
|
|
13
13
|
export const REVIEW = ".gtd/REVIEW.md"
|
|
14
14
|
export const QUALITY = ".gtd/QUALITY.md"
|
|
15
15
|
|
|
16
|
-
const planner = (): string => env.plannerModel ?? ""
|
|
17
|
-
const coder = (): string => env.coderModel ?? ""
|
|
18
|
-
|
|
19
|
-
// ── Planning ────────────────────────────────────────────────────────────────
|
|
20
|
-
|
|
21
|
-
/** Turn the sketch since `base` into `.gtd/REQUIREMENTS.md`. */
|
|
22
|
-
export const triage = (base: string): Promise<void> =>
|
|
23
|
-
t.agentWithSkills("triage", t.designTriagePrompt(base), {
|
|
24
|
-
label: "Triaging the change",
|
|
25
|
-
file: REQUIREMENTS,
|
|
26
|
-
mode: "qa",
|
|
27
|
-
model: planner(),
|
|
28
|
-
system: t.designSystem(),
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
export const author = (): Promise<void> =>
|
|
32
|
-
t.agentWithSkills("author", t.architectureAuthorPrompt(), {
|
|
33
|
-
label: "Refining the technical plan",
|
|
34
|
-
file: ARCHITECTURE,
|
|
35
|
-
mode: "qa",
|
|
36
|
-
model: planner(),
|
|
37
|
-
system: t.architectSystem(),
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
export const decompose = (): Promise<void> =>
|
|
41
|
-
t.agentWithSkills("decompose.decomposing", t.architectureDecomposePrompt(), {
|
|
42
|
-
label: "Decomposing into packages",
|
|
43
|
-
model: planner(),
|
|
44
|
-
system: t.architectSystem(),
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
export const answerProductQuestions = (): Promise<void> =>
|
|
48
|
-
human("gate.answer", {
|
|
49
|
-
message: t.designGateAnswerMessage(),
|
|
50
|
-
label: "Awaiting your product answers",
|
|
51
|
-
file: REQUIREMENTS,
|
|
52
|
-
mode: "qa",
|
|
53
|
-
acceptClean: true,
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
export const answerTechnicalQuestions = (): Promise<void> =>
|
|
57
|
-
human("gate.answer", {
|
|
58
|
-
message: t.architectureGateAnswerMessage(),
|
|
59
|
-
label: "Awaiting your technical answers",
|
|
60
|
-
file: ARCHITECTURE,
|
|
61
|
-
mode: "qa",
|
|
62
|
-
acceptClean: true,
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
// ── Packages ────────────────────────────────────────────────────────────────
|
|
66
|
-
|
|
67
|
-
export const build = (pkg: string): Promise<void> =>
|
|
68
|
-
t.agentWithSkills("building", t.packagesItemBuildingPrompt(pkg), {
|
|
69
|
-
label: "Building",
|
|
70
|
-
model: coder(),
|
|
71
|
-
system: t.builderSystem(),
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
export const fixSuite = (): Promise<void> =>
|
|
75
|
-
t.agentWithSkills("fix.suite.fixing", t.packagesItemFixSuitePrompt(), {
|
|
76
|
-
label: "Fixing the check",
|
|
77
|
-
file: FEEDBACK,
|
|
78
|
-
model: coder(),
|
|
79
|
-
system: t.builderSystem(),
|
|
80
|
-
})
|
|
16
|
+
export const planner = (): string => env.plannerModel ?? ""
|
|
17
|
+
export const coder = (): string => env.coderModel ?? ""
|
|
81
18
|
|
|
82
19
|
// ── Keeping the suite green ─────────────────────────────────────────────────
|
|
83
20
|
|
|
@@ -162,25 +99,6 @@ export const reviewing = (base: string, carry?: string): Promise<void> =>
|
|
|
162
99
|
base,
|
|
163
100
|
})
|
|
164
101
|
|
|
165
|
-
/** Answer every `question` note inline in `.gtd/REVIEW.md`. */
|
|
166
|
-
export const answerReviewQuestions = (notes: readonly t.NoteInput[]): Promise<void> =>
|
|
167
|
-
t.agentWithSkills("review.answer-review-questions", t.buildReviewAnswerQuestionsPrompt(notes), {
|
|
168
|
-
label: "Answering your questions",
|
|
169
|
-
file: REVIEW,
|
|
170
|
-
mode: "review",
|
|
171
|
-
model: planner(),
|
|
172
|
-
system: t.reviewerSystem(),
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
/** Fix every `nit` note in one turn. */
|
|
176
|
-
export const fixNits = (notes: readonly t.NoteInput[]): Promise<void> =>
|
|
177
|
-
t.agentWithSkills("review.fix.nits.fixing", t.buildReviewFixNitsPrompt(notes), {
|
|
178
|
-
label: "Fixing your nits",
|
|
179
|
-
file: REVIEW,
|
|
180
|
-
model: planner(),
|
|
181
|
-
system: t.reviewerSystem(),
|
|
182
|
-
})
|
|
183
|
-
|
|
184
102
|
/** Fix the `Risk:`-marked notes the reviewer named; an empty turn means the risk was judged false. */
|
|
185
103
|
export const fixRisks = (notes: readonly t.NoteInput[]): Promise<void> =>
|
|
186
104
|
t.agentWithSkills("review.fix.risks.fixing", t.buildReviewFixRisksPrompt(notes), {
|
|
@@ -191,9 +109,12 @@ export const fixRisks = (notes: readonly t.NoteInput[]): Promise<void> =>
|
|
|
191
109
|
allowEmpty: true,
|
|
192
110
|
})
|
|
193
111
|
|
|
194
|
-
export const awaitReview = (
|
|
112
|
+
export const awaitReview = (
|
|
113
|
+
base: string,
|
|
114
|
+
message = t.buildReviewAwaitReviewMessage(base),
|
|
115
|
+
): Promise<void> =>
|
|
195
116
|
human("review.await-review", {
|
|
196
|
-
message
|
|
117
|
+
message,
|
|
197
118
|
label: "Awaiting your review",
|
|
198
119
|
file: REVIEW,
|
|
199
120
|
mode: "review",
|
|
@@ -206,14 +127,3 @@ export const reviewMissing = (round: string): Promise<void> =>
|
|
|
206
127
|
message: t.buildReviewReviewMissingMessage(round),
|
|
207
128
|
label: "Nothing to review",
|
|
208
129
|
})
|
|
209
|
-
|
|
210
|
-
/** Turn the review round `capture` describes into `.gtd/REQUIREMENTS.md`, or change nothing. */
|
|
211
|
-
export const collecting = (capture: string): Promise<void> =>
|
|
212
|
-
t.agentWithSkills("review.collecting", t.buildReviewCollectingPrompt(capture), {
|
|
213
|
-
label: "Collecting your feedback",
|
|
214
|
-
file: REQUIREMENTS,
|
|
215
|
-
mode: "qa",
|
|
216
|
-
model: planner(),
|
|
217
|
-
system: t.reviewerSystem(),
|
|
218
|
-
allowEmpty: true,
|
|
219
|
-
})
|