@pmelab/gtd 19.0.0 → 20.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 +41 -39
- package/claude/hooks/doors.ts +34 -0
- package/claude/hooks/drive.ts +1 -0
- package/claude/hooks/register.tsx +12 -13
- package/claude/hooks/ship.ts +3 -2
- package/dist/gtd.bundle.mjs +516 -268
- package/package.json +2 -2
- package/skills/authoring/SKILL.md +39 -27
- package/src/flows/runtime.ts +22 -17
- package/src/workflows/access.test.ts +3 -3
- package/src/workflows/{unified.ts → bundled.ts} +30 -27
- package/src/workflows/index.ts +1 -1
- package/src/workflows/review.test.ts +3 -3
- package/src/workflows/review.ts +5 -5
- package/src/workflows/skills.test.ts +2 -2
- package/src/workflows/steps.test.ts +2 -2
- package/src/workflows/steps.ts +1 -1
- package/src/workflows/text.ts +1 -1
- package/claude/hooks/entry.ts +0 -34
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pmelab/gtd",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "20.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Git-aware CLI that emits the next prompt for an autonomous coding agent based on the current repository state",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"exports": {
|
|
10
10
|
"./flows": "./src/flows/index.ts",
|
|
11
|
-
"./workflow": "./src/workflows/
|
|
11
|
+
"./workflow": "./src/workflows/bundled.ts"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"dist/",
|
|
@@ -11,33 +11,38 @@ description: >-
|
|
|
11
11
|
# Authoring a gtd workflow
|
|
12
12
|
|
|
13
13
|
A gtd workflow is **plain async TypeScript**: a `gtd.config.ts` at the
|
|
14
|
-
repository root
|
|
15
|
-
**steps** built from `@pmelab/gtd/flows`;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
repository root exports **workflows** — every exported async function is one,
|
|
15
|
+
named by its export, awaiting **steps** built from `@pmelab/gtd/flows`; the
|
|
16
|
+
`default` export is the ordinary start. Optional reserved exports (`defaults`
|
|
17
|
+
process settings, `envDefaults` environment settings, `summary`, `base`,
|
|
18
|
+
`steering`, `skills`, `doors`) are shared by the file's workflows. **Never
|
|
19
|
+
export a helper function you do not want startable** — it becomes a workflow.
|
|
20
|
+
Every step is a commit; gtd finds where a process rests by **replaying** the
|
|
21
|
+
flow over the episode's commits, so the git history IS the state and nothing is
|
|
22
|
+
stored anywhere else.
|
|
21
23
|
|
|
22
24
|
Your job is to produce or edit that module so it loads cleanly and does what the
|
|
23
25
|
user wants. Driving a workflow once it exists is a separate concern — that is
|
|
24
26
|
what a driver does.
|
|
25
27
|
|
|
26
28
|
**Trust:** gtd evaluates `gtd.config.ts` on every command that resolves workflow
|
|
27
|
-
state (`gtd next` and `gtd
|
|
28
|
-
runs; write it with the same care as a build script.
|
|
29
|
+
state (`gtd next`, `gtd lsp`, `gtd door` and `gtd doors` included). It is code
|
|
30
|
+
the user's repository runs; write it with the same care as a build script.
|
|
29
31
|
|
|
30
32
|
## Golden rule: start from the bundled default, edit incrementally
|
|
31
33
|
|
|
32
34
|
Do **not** write a workflow from a blank page unless the user wants something
|
|
33
|
-
tiny. gtd ships
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
tiny. gtd ships three startable workflows (`feature`, `review`, `fix`) as named
|
|
36
|
+
exports of `@pmelab/gtd/workflow` and runs them when no `gtd.config.ts` is
|
|
37
|
+
found. Its default export is `feature` (the ordinary start), and every phase and
|
|
38
|
+
single step they are built from is a named export too. Start by importing what
|
|
39
|
+
you keep and writing only what changes. Import phases by name; **never
|
|
40
|
+
`export *` from it** — every exported function of your file would become a
|
|
41
|
+
startable workflow:
|
|
37
42
|
|
|
38
43
|
```ts
|
|
39
44
|
import { start } from "@pmelab/gtd/flows"
|
|
40
|
-
import
|
|
45
|
+
import { afterTail, buildTail, feature } from "@pmelab/gtd/workflow"
|
|
41
46
|
|
|
42
47
|
export {
|
|
43
48
|
defaults,
|
|
@@ -45,14 +50,20 @@ export {
|
|
|
45
50
|
summary,
|
|
46
51
|
base,
|
|
47
52
|
steering,
|
|
53
|
+
skills,
|
|
48
54
|
} from "@pmelab/gtd/workflow"
|
|
49
55
|
|
|
50
|
-
export default
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
export default feature
|
|
57
|
+
|
|
58
|
+
export async function hotfix() {
|
|
59
|
+
return afterTail(await buildTail(true, start()))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const doors = { hotfix: { workflow: "hotfix" } }
|
|
54
63
|
```
|
|
55
64
|
|
|
65
|
+
`gtd --workflow hotfix` (or `gtd door hotfix`) starts it.
|
|
66
|
+
|
|
56
67
|
To change a phase itself, read its source in the npm package
|
|
57
68
|
(`node_modules/@pmelab/gtd/src/workflows/`, or under `$(npm root -g)` for a
|
|
58
69
|
global install) and write your own version in `gtd.config.ts`, reusing its
|
|
@@ -64,8 +75,8 @@ it in place.
|
|
|
64
75
|
|
|
65
76
|
Prefer the bundled workflow's own parts over re-implementing them — `healthy`,
|
|
66
77
|
`escalation`, `gate`, `design`, `architecture`, `packages`, `qualityLap`,
|
|
67
|
-
`
|
|
68
|
-
|
|
78
|
+
`buildTail`, and single steps like `triage` or `fixCheck`. Their full step names
|
|
79
|
+
are versioned API.
|
|
69
80
|
|
|
70
81
|
Make one small change, **verify it loads** (see "Verify"), then make the next. A
|
|
71
82
|
workflow that fails to load breaks every gtd command in the repository.
|
|
@@ -132,13 +143,14 @@ the pending landing — call it right after the step whose turn you reject.
|
|
|
132
143
|
- An episode ends when the flow returns or calls `restart()`; the next starts at
|
|
133
144
|
the flow's first step on an ordinary start — that step is where a finished
|
|
134
145
|
process waits (the bundled one is `human("idle", …)`).
|
|
135
|
-
- `gtd --
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
`
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
146
|
+
- `gtd --workflow <name>` starts a process on the exported workflow `<name>` (an
|
|
147
|
+
unknown name is a usage error); `gtd door <name> [args]` starts one through a
|
|
148
|
+
`doors` entry `{ workflow, args?, vars? }` (`gtd doors` lists them; bundled:
|
|
149
|
+
`fix`, `review [base]`; yours merge over them). An
|
|
150
|
+
`export const base = (workflow, vars) => commitish | undefined` fixes a
|
|
151
|
+
started process's diff base (blank = default-branch merge-base).
|
|
152
|
+
`--var <name>=<value>` only pins process settings: names the workflow's
|
|
153
|
+
`defaults` or `.gtdrc` `vars:` declare — never an environment setting.
|
|
142
154
|
|
|
143
155
|
## Landing rules you are designing for
|
|
144
156
|
|
package/src/flows/runtime.ts
CHANGED
|
@@ -259,7 +259,7 @@ export const changesSince = (hash: string, pattern?: string): Changes => {
|
|
|
259
259
|
/** The commit the process stands on at this point of the flow. */
|
|
260
260
|
export const head = (): string => ctx().head()
|
|
261
261
|
|
|
262
|
-
/** The process's diff base: the commit before it began, or the base `gtd --
|
|
262
|
+
/** The process's diff base: the commit before it began, or the base `gtd --workflow` fixed. */
|
|
263
263
|
export const start = (): string => ctx().start()
|
|
264
264
|
|
|
265
265
|
/** The skill list `localName` (scoped from here, same as `agent()`) resolves to — for a prompt preamble. */
|
|
@@ -348,17 +348,8 @@ export const openQuestions = (text: string): readonly OpenQuestion[] => ctx().op
|
|
|
348
348
|
|
|
349
349
|
// ── The workflow ────────────────────────────────────────────────────────────
|
|
350
350
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
readonly entry: string | undefined
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* A workflow's one flow. A flow that never reads `entry` accepts no
|
|
358
|
-
* `--entry`; one that does decides for itself which names it honours,
|
|
359
|
-
* `refuse()`-ing the rest.
|
|
360
|
-
*/
|
|
361
|
-
export type Flow = (args: FlowArgs) => Promise<void>
|
|
351
|
+
/** A workflow's one flow. */
|
|
352
|
+
export type Flow = () => Promise<void>
|
|
362
353
|
|
|
363
354
|
export interface SummaryContext {
|
|
364
355
|
readonly entryCommit: string
|
|
@@ -375,11 +366,25 @@ export interface SummaryContext {
|
|
|
375
366
|
export type Summary = (context: SummaryContext) => string
|
|
376
367
|
|
|
377
368
|
/**
|
|
378
|
-
* A workflow
|
|
379
|
-
*
|
|
380
|
-
* none. Runs when the process is
|
|
369
|
+
* A workflow file's optional `base` export: the commitish that fixes the diff
|
|
370
|
+
* base of a process `gtd --workflow <workflow>` starts, or `undefined` for
|
|
371
|
+
* none. Runs when the process is started, with the vars `--var` sets.
|
|
381
372
|
*/
|
|
382
|
-
export type
|
|
383
|
-
|
|
373
|
+
export type WorkflowBase = (
|
|
374
|
+
workflow: string,
|
|
384
375
|
vars: Readonly<Record<string, string>>,
|
|
385
376
|
) => string | undefined
|
|
377
|
+
|
|
378
|
+
/** A named shortcut: `gtd door <name> [args…]` starts `workflow` with the positional args mapped to process settings. */
|
|
379
|
+
export interface Door {
|
|
380
|
+
readonly workflow: string
|
|
381
|
+
/** Static, so `gtd doors --json` can list them. */
|
|
382
|
+
readonly args?: readonly { readonly name: string; readonly optional?: boolean }[]
|
|
383
|
+
/** A pure function of the args: no git runs at declaration time. */
|
|
384
|
+
readonly vars?: (
|
|
385
|
+
args: Readonly<Record<string, string | undefined>>,
|
|
386
|
+
) => Readonly<Record<string, string>>
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/** A workflow module's reserved `doors` export. */
|
|
390
|
+
export type Doors = Readonly<Record<string, Door>>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
2
|
import { access } from "./access.js"
|
|
3
|
-
import {
|
|
3
|
+
import { bundled as workflow } from "./index.js"
|
|
4
4
|
|
|
5
|
-
const { defaults } =
|
|
5
|
+
const { defaults } = workflow
|
|
6
6
|
|
|
7
7
|
const bundled = access(defaults)
|
|
8
8
|
|
|
@@ -34,7 +34,7 @@ describe("the bundled workflow's access export", () => {
|
|
|
34
34
|
})
|
|
35
35
|
|
|
36
36
|
it("keys only scopes that run a turn (every key is a skills key)", () => {
|
|
37
|
-
const known = Object.keys(
|
|
37
|
+
const known = Object.keys(workflow.skills(defaults))
|
|
38
38
|
for (const key of Object.keys(bundled)) expect(known).toContain(key)
|
|
39
39
|
})
|
|
40
40
|
})
|
|
@@ -2,14 +2,13 @@ import {
|
|
|
2
2
|
changes,
|
|
3
3
|
head,
|
|
4
4
|
human,
|
|
5
|
-
refuse,
|
|
6
5
|
requireRevert,
|
|
7
6
|
restoreScript,
|
|
8
7
|
revertScript,
|
|
9
8
|
run,
|
|
10
9
|
start,
|
|
11
|
-
type
|
|
12
|
-
type
|
|
10
|
+
type Doors,
|
|
11
|
+
type WorkflowBase,
|
|
13
12
|
type Summary,
|
|
14
13
|
} from "../flows/index.js"
|
|
15
14
|
import { baseline, gate } from "./health.js"
|
|
@@ -20,13 +19,13 @@ import { buildTail, type ReviewOutcome } from "./review.js"
|
|
|
20
19
|
import { ARCHITECTURE, FEEDBACK, REQUIREMENTS, REVIEW } from "./steps.js"
|
|
21
20
|
import * as t from "./text.js"
|
|
22
21
|
|
|
23
|
-
// gtd's
|
|
22
|
+
// gtd's bundled workflows. Any change to the tree starts a process:
|
|
24
23
|
// `idle` → `unwind` reverts the sketch (its intent survives in history) → a
|
|
25
24
|
// green-baseline gate → design, architecture and one package per concern →
|
|
26
25
|
// the quality lap → human review, which signs off (the episode ends back at
|
|
27
|
-
// `idle`) or sends a full re-plan lap.
|
|
28
|
-
// review
|
|
29
|
-
//
|
|
26
|
+
// `idle`) or sends a full re-plan lap. `feature` is that ordinary start;
|
|
27
|
+
// `gtd --workflow fix` and `gtd --workflow review --var reviewBase=<commitish>`
|
|
28
|
+
// enter the same build tail further in.
|
|
30
29
|
//
|
|
31
30
|
// Every part is exported for other workflows to compose; see the modules
|
|
32
31
|
// re-exported below.
|
|
@@ -107,30 +106,34 @@ export const ordinaryStart = async (): Promise<void> => {
|
|
|
107
106
|
await planAndBuild(start())
|
|
108
107
|
}
|
|
109
108
|
|
|
110
|
-
|
|
109
|
+
/** Repair a red baseline through the build tail, as its own reviewed commit. */
|
|
110
|
+
export const fix = async (): Promise<void> => {
|
|
111
|
+
if (await baseline("fix-precheck")) return
|
|
112
|
+
return afterTail(await buildTail(true, start()))
|
|
113
|
+
}
|
|
111
114
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return afterTail(await buildTail(true, start()))
|
|
117
|
-
}
|
|
118
|
-
if (entry === "review-gate.check") {
|
|
119
|
-
await gate("review-gate", t.reviewGateBlockedMessage())
|
|
120
|
-
return afterTail(await buildTail(false, start()))
|
|
121
|
-
}
|
|
122
|
-
if (entry === "start-gate.check") {
|
|
123
|
-
await gate("start-gate", t.startGateBlockedMessage())
|
|
124
|
-
return planAndBuild(start())
|
|
125
|
-
}
|
|
126
|
-
refuse(
|
|
127
|
-
`"${entry}" is not an enterable state — enterable states:\n${ENTRIES.map((name) => ` ${name}`).join("\n")}`,
|
|
128
|
-
)
|
|
115
|
+
/** Pure review of everything since `reviewBase`. */
|
|
116
|
+
export const review = async (): Promise<void> => {
|
|
117
|
+
await gate("review-gate", t.reviewGateBlockedMessage())
|
|
118
|
+
return afterTail(await buildTail(false, start()))
|
|
129
119
|
}
|
|
130
120
|
|
|
121
|
+
export const feature = ordinaryStart
|
|
122
|
+
|
|
123
|
+
export default feature
|
|
124
|
+
|
|
131
125
|
export const summary: Summary = t.summaryPrompt
|
|
132
126
|
|
|
133
127
|
export const steering = { [REQUIREMENTS]: "qa", [ARCHITECTURE]: "qa", [REVIEW]: "review" }
|
|
134
128
|
|
|
135
|
-
export const base:
|
|
136
|
-
|
|
129
|
+
export const base: WorkflowBase = (workflow, vars) =>
|
|
130
|
+
workflow === "review" ? (vars.reviewBase ?? "") : undefined
|
|
131
|
+
|
|
132
|
+
export const doors: Doors = {
|
|
133
|
+
fix: { workflow: "fix" },
|
|
134
|
+
review: {
|
|
135
|
+
workflow: "review",
|
|
136
|
+
args: [{ name: "base", optional: true }],
|
|
137
|
+
vars: ({ base }) => (base === undefined ? {} : { reviewBase: base }),
|
|
138
|
+
},
|
|
139
|
+
}
|
package/src/workflows/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * as
|
|
1
|
+
export * as bundled from "./bundled.js"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it } from "vitest"
|
|
2
2
|
import { installContext, type Change, type JudgeAnswer, type StepRequest } from "../flows/index.js"
|
|
3
3
|
import { fixQualityFindings, review, type ReviewOutcome } from "./review.js"
|
|
4
|
-
import {
|
|
4
|
+
import { bundled } from "./index.js"
|
|
5
5
|
import { fixtureContext } from "./text.fixture.js"
|
|
6
6
|
|
|
7
7
|
afterEach(() => installContext(undefined))
|
|
@@ -313,7 +313,7 @@ describe("review verdict routing", () => {
|
|
|
313
313
|
|
|
314
314
|
describe("the default quality lenses", () => {
|
|
315
315
|
it("are the six lenses in the settled order", () => {
|
|
316
|
-
expect(
|
|
316
|
+
expect(bundled.defaults.qualityReviews!.split(",").map((l) => l.trim())).toEqual([
|
|
317
317
|
"correctness",
|
|
318
318
|
"owasp-security",
|
|
319
319
|
"ponytail-review",
|
|
@@ -324,7 +324,7 @@ describe("the default quality lenses", () => {
|
|
|
324
324
|
})
|
|
325
325
|
|
|
326
326
|
it("expose builtInLenses through the public workflow module", () => {
|
|
327
|
-
expect(Object.keys(
|
|
327
|
+
expect(Object.keys(bundled.builtInLenses).sort()).toEqual([
|
|
328
328
|
"conventions",
|
|
329
329
|
"correctness",
|
|
330
330
|
"spec-challenge",
|
package/src/workflows/review.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
ARCHITECTURE,
|
|
27
27
|
awaitReview,
|
|
28
28
|
collecting,
|
|
29
|
-
|
|
29
|
+
fixCheck,
|
|
30
30
|
fixNits,
|
|
31
31
|
fixQuality,
|
|
32
32
|
fixRisks,
|
|
@@ -230,7 +230,7 @@ const routeNotes = async (notes: readonly ReviewNote[], r: Round): Promise<Finis
|
|
|
230
230
|
}
|
|
231
231
|
if (nits.length > 0) {
|
|
232
232
|
await guarded(r.frozen, () => fixNits(nits), "review")()
|
|
233
|
-
await healthy(guarded(r.frozen,
|
|
233
|
+
await healthy(guarded(r.frozen, fixCheck, "review"), { escalations: r.escalations })
|
|
234
234
|
}
|
|
235
235
|
if (edits.length > 0) {
|
|
236
236
|
await r.close()
|
|
@@ -291,7 +291,7 @@ const reviewOnce = async (
|
|
|
291
291
|
const risks = reviewRisks(read(REVIEW) ?? "")
|
|
292
292
|
if (risks.length === 0) return
|
|
293
293
|
await guarded(frozen, () => fixRisks(risks), "review")()
|
|
294
|
-
await healthy(guarded(frozen,
|
|
294
|
+
await healthy(guarded(frozen, fixCheck, "review"), { escalations })
|
|
295
295
|
await reviewing(base, carry)
|
|
296
296
|
}
|
|
297
297
|
|
|
@@ -335,12 +335,12 @@ export const buildTail = (
|
|
|
335
335
|
scope("build", async () => {
|
|
336
336
|
const frozen = built?.frozen
|
|
337
337
|
const escalations: EscalationCount = { rounds: 0 }
|
|
338
|
-
const guardedFix = guarded(frozen, () =>
|
|
338
|
+
const guardedFix = guarded(frozen, () => fixCheck())
|
|
339
339
|
let redFirst = fixFirst
|
|
340
340
|
// fixFirst's own fix + healthy below already is the full run.
|
|
341
341
|
if (!fixFirst) {
|
|
342
342
|
await healthy(
|
|
343
|
-
guarded(frozen, () =>
|
|
343
|
+
guarded(frozen, () => fixCheck(built)),
|
|
344
344
|
{ escalations, sweepOnGreen: [ARCHITECTURE] },
|
|
345
345
|
)
|
|
346
346
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest"
|
|
2
2
|
import { skills } from "./skills.js"
|
|
3
|
-
import {
|
|
3
|
+
import { bundled as workflow } from "./index.js"
|
|
4
4
|
|
|
5
|
-
const { defaults } =
|
|
5
|
+
const { defaults } = workflow
|
|
6
6
|
|
|
7
7
|
const bundled = skills(defaults)
|
|
8
8
|
|
|
@@ -41,7 +41,7 @@ describe("the bundled workflow's steps declare skills — a bundled step's rende
|
|
|
41
41
|
})
|
|
42
42
|
|
|
43
43
|
it("fix carries build.fix's bundled skills", async () => {
|
|
44
|
-
const prompt = agentPrompt(await capture(() => steps.
|
|
44
|
+
const prompt = agentPrompt(await capture(() => steps.fixCheck(), "build"))
|
|
45
45
|
expect(prompt).toContain("debugging-and-error-recovery")
|
|
46
46
|
})
|
|
47
47
|
|
|
@@ -110,7 +110,7 @@ describe("the bundled workflow's steps declare skills — a bundled step's rende
|
|
|
110
110
|
})
|
|
111
111
|
|
|
112
112
|
it("an empty configured list leaves that step's skills empty, and its rendered prompt bare", async () => {
|
|
113
|
-
const request = await capture(() => steps.
|
|
113
|
+
const request = await capture(() => steps.fixCheck(), "build", { build: [] })
|
|
114
114
|
if (request.kind !== "agent") throw new Error("unreachable")
|
|
115
115
|
expect(request.prompt).not.toContain("Load whatever's listed here")
|
|
116
116
|
})
|
package/src/workflows/steps.ts
CHANGED
|
@@ -81,7 +81,7 @@ export const fixSuite = (): Promise<void> =>
|
|
|
81
81
|
|
|
82
82
|
// ── Keeping the suite green ─────────────────────────────────────────────────
|
|
83
83
|
|
|
84
|
-
export const
|
|
84
|
+
export const fixCheck = (built?: t.BuildContext): Promise<void> =>
|
|
85
85
|
t.agentWithSkills("fix", t.buildFixPrompt(built), {
|
|
86
86
|
label: "Fixing the check",
|
|
87
87
|
file: FEEDBACK,
|
package/src/workflows/text.ts
CHANGED
|
@@ -87,7 +87,7 @@ export const startGateBlockedMessage = (): string =>
|
|
|
87
87
|
\`.gtd/FEEDBACK.md\` holds the failing output.
|
|
88
88
|
|
|
89
89
|
What each change does next (then run \`gtd land\`):
|
|
90
|
-
- **Retry check** — edit the code and/or \`.gtd/FEEDBACK.md\` to fix the failing tests (**start-gate.check**). To repair the baseline as its own separate reviewed commit instead, abandon this start and run \`gtd --
|
|
90
|
+
- **Retry check** — edit the code and/or \`.gtd/FEEDBACK.md\` to fix the failing tests (**start-gate.check**). To repair the baseline as its own separate reviewed commit instead, abandon this start and run \`gtd --workflow fix\` from a clean \`idle\`.
|
|
91
91
|
`
|
|
92
92
|
|
|
93
93
|
export const reviewGateBlockedMessage = (): string =>
|
package/claude/hooks/entry.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// The workflow's side doors: `fix` repairs a red baseline as its own reviewed
|
|
2
|
-
// commit, `review` reviews everything since a base, straight to the review
|
|
3
|
-
// tail. `gtd --entry` prints the script that starts the process; running it
|
|
4
|
-
// is the driver's job, as with every git write gtd plans.
|
|
5
|
-
|
|
6
|
-
import type { ShipIo, Shipped } from "./ship"
|
|
7
|
-
|
|
8
|
-
const fail = (text: string): Shipped => ({ ok: false, text })
|
|
9
|
-
|
|
10
|
-
export async function enter(io: ShipIo, door: "fix" | "review", base?: string): Promise<Shipped> {
|
|
11
|
-
const args = door === "fix" ? ["--entry", "fix-precheck"] : await reviewArgs(io, base)
|
|
12
|
-
if (typeof args === "string") return fail(args)
|
|
13
|
-
const planned = await io.run(["gtd", ...args])
|
|
14
|
-
if (planned.code !== 0)
|
|
15
|
-
return fail(planned.err.trim() || `gtd ${args.join(" ")} exited ${planned.code}`)
|
|
16
|
-
const started = await io.run(["sh", "-c", planned.out])
|
|
17
|
-
if (started.code !== 0) return fail(`The entry script failed.\n${started.err.trim()}`)
|
|
18
|
-
return {
|
|
19
|
-
ok: true,
|
|
20
|
-
text: door === "fix" ? "Started a fix." : `Started a review since ${args.at(-1)?.slice(-7)}.`,
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Reviews the branch since it left `base`, the default branch unless named.
|
|
25
|
-
async function reviewArgs(io: ShipIo, base: string | undefined) {
|
|
26
|
-
const git = async (...a: string[]) => (await io.run(["git", ...a])).out.trim()
|
|
27
|
-
const from =
|
|
28
|
-
base || (await git("symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD")) || "main"
|
|
29
|
-
const mergeBase = await git("merge-base", from, "HEAD")
|
|
30
|
-
if (!mergeBase) return `There is no common ancestor of ${from} and HEAD to review from.`
|
|
31
|
-
if (mergeBase === (await git("rev-parse", "HEAD")))
|
|
32
|
-
return `Nothing to review: HEAD has no commits beyond ${from}.`
|
|
33
|
-
return ["--entry", "review-gate.check", "--var", `reviewBase=${mergeBase}`]
|
|
34
|
-
}
|