@pmelab/gtd 59.0.1 → 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.
Files changed (83) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +37 -33
  3. package/claude/hooks/doors.ts +8 -4
  4. package/dist/chunks/flows-DENt0uWV.mjs +707 -0
  5. package/dist/chunks/{steering-CCNvngSH.mjs → steering-DF_BMadH.mjs} +292 -289
  6. package/dist/chunks/{testkit-07Z_ENch.mjs → testkit-BvecVjQ0.mjs} +570 -390
  7. package/dist/chunks/{wire-BTUhRGXj.mjs → wire-DZ8R1RU9.mjs} +8 -1
  8. package/dist/flows.mjs +2 -676
  9. package/dist/gtd.bundle.mjs +751 -645
  10. package/dist/steering.mjs +2 -2
  11. package/dist/testkit/gherkin.mjs +245 -36
  12. package/dist/testkit.mjs +3 -2
  13. package/dist/web/main.js +234 -232
  14. package/dist/wire.mjs +2 -2
  15. package/dist/workflow-kit.mjs +3 -3
  16. package/package.json +1 -1
  17. package/skills/authoring/SKILL.md +66 -60
  18. package/src/cli/Cli.test.ts +183 -182
  19. package/src/cli/Cli.ts +164 -186
  20. package/src/cli/History.ts +9 -4
  21. package/src/cli/Judges.ts +1 -1
  22. package/src/cli/Output.test.ts +35 -0
  23. package/src/cli/Output.ts +31 -1
  24. package/src/cli/Program.test.ts +65 -79
  25. package/src/cli/Program.ts +157 -118
  26. package/src/edge/Edge.test.ts +64 -56
  27. package/src/edge/Edge.ts +3 -3
  28. package/src/flows/factory.test.ts +14 -0
  29. package/src/flows/factory.ts +22 -5
  30. package/src/flows/helpers.test.ts +35 -0
  31. package/src/flows/helpers.ts +19 -10
  32. package/src/flows/index.test.ts +66 -0
  33. package/src/flows/index.ts +109 -5
  34. package/src/flows/internal.ts +7 -0
  35. package/src/flows/runtime.test.ts +192 -1
  36. package/src/flows/runtime.ts +188 -54
  37. package/src/gherkin/git-control.feature +6 -6
  38. package/src/gherkin/index.test.ts +127 -0
  39. package/src/gherkin/index.ts +370 -51
  40. package/src/gherkin/messages.test.ts +38 -4
  41. package/src/gherkin/messages.ts +21 -4
  42. package/src/gherkin/more-steps.feature +170 -0
  43. package/src/gherkin/scenario-end.feature +101 -0
  44. package/src/gherkin/testkit.feature +83 -7
  45. package/src/install/Install.test.ts +7 -7
  46. package/src/install/Install.ts +8 -8
  47. package/src/replay/Replay.test.ts +13 -2
  48. package/src/replay/Replay.ts +19 -30
  49. package/src/replay/judgeDiffShape.test.ts +1 -1
  50. package/src/steering/Footnotes.test.ts +1 -1
  51. package/src/steering/SteeringFormats.test.ts +1 -1
  52. package/src/steering/freeform.test.ts +1 -1
  53. package/src/steering/index.test.ts +27 -1
  54. package/src/steering/index.ts +6 -104
  55. package/src/steering/internal.ts +11 -0
  56. package/src/steering/qa.test.ts +7 -2
  57. package/src/steering/reads.test.ts +42 -0
  58. package/src/steering/reads.ts +43 -0
  59. package/src/steering/registry.ts +93 -0
  60. package/src/steering/review.test.ts +2 -2
  61. package/src/step/planStart.test.ts +18 -16
  62. package/src/step/planStart.ts +9 -9
  63. package/src/testkit/TestRepo.test.ts +245 -7
  64. package/src/testkit/TestRepo.ts +271 -40
  65. package/src/testkit/imports.test.ts +58 -0
  66. package/src/testkit/imports.ts +102 -0
  67. package/src/testkit/index.test.ts +28 -0
  68. package/src/testkit/index.ts +14 -2
  69. package/src/twins/EmittedScriptRecognizer.ts +2 -6
  70. package/src/twins/EmittedScripts.test.ts +4 -2
  71. package/src/twins/InMemRepo.ts +1 -0
  72. package/src/wire/Script.ts +11 -0
  73. package/src/wire/WireSchema.test.ts +3 -0
  74. package/src/wire/golden/1/script.json +4 -0
  75. package/src/wire/index.ts +1 -0
  76. package/src/workflow/SteeringFormats.test.ts +1 -1
  77. package/src/workflow/compile.ts +2 -2
  78. package/src/workflow/discovery.test.ts +3 -3
  79. package/src/workflow/load.test.ts +256 -116
  80. package/src/workflow/load.ts +169 -146
  81. package/src/workflow-kit/index.test.ts +25 -0
  82. package/src/workflow-kit/index.ts +37 -4
  83. package/src/workflow-kit/review.ts +2 -2
@@ -11,23 +11,27 @@ 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 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.
14
+ repository root exports **workflows** — each an async flow wrapped in
15
+ `workflow(flow, options?)` (or a `defineWorkflow` factory), named by its export,
16
+ awaiting **steps** built from `@pmelab/gtd/flows`; the `default` export is the
17
+ ordinary start and may be a plain flow. Any other exported function is a plain
18
+ helper. A workflow's options — `defaults` (process settings), `envDefaults`
19
+ (environment settings), `summary`, `base`, `steering`, `skills`, `access`,
20
+ `doors` — go in `workflow(flow, { … })` or a factory build, never in file-level
21
+ exports: exporting one of those names fails the load. `workflow()` returns a new
22
+ function and leaves `flow` untouched, so an already-wrapped flow (one a library
23
+ exports) can be wrapped again: `workflow(libraryFlow, { … })` — its options
24
+ replace the library's, nothing is merged. Every step is a commit; gtd finds
25
+ where a process rests by **replaying** the flow over the episode's commits, so
26
+ the git history IS the state and nothing is stored anywhere else.
23
27
 
24
28
  Your job is to produce or edit that module so it loads cleanly and does what the
25
29
  user wants. Driving a workflow once it exists is a separate concern — that is
26
30
  what a driver does.
27
31
 
28
32
  **Trust:** gtd evaluates `gtd.config.ts` on every command that resolves workflow
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.
33
+ state (`gtd next`, `gtd lsp` and `gtd start` included). It is code the user's
34
+ repository runs; write it with the same care as a build script.
31
35
 
32
36
  ## Golden rule: start from an existing workflow, edit incrementally
33
37
 
@@ -38,36 +42,28 @@ pinned on the machine — `bin/gtd-wf pin` there, then `bin/gtd-wf config defaul
38
42
  prints a `gtd.config.ts` importing its bundle by absolute path. Every phase and
39
43
  single step that bundle is built from is a named export too, so a config can
40
44
  import what it keeps and write only what changes. Import phases by name; **never
41
- `export *` from it** — every exported function of your file would become a
42
- startable workflow:
45
+ `export *` from it** — re-exported option names fail the load:
43
46
 
44
47
  ```ts
45
- import { start } from "@pmelab/gtd/flows"
48
+ import { start, workflow } from "@pmelab/gtd/flows"
46
49
  import {
47
50
  afterTail,
48
51
  buildTail,
49
- feature,
50
- } from "/home/me/.local/share/gtd/workflows/dist/default.mjs"
51
-
52
- export {
53
52
  defaults,
54
53
  envDefaults,
55
- summary,
56
- base,
57
- steering,
54
+ feature,
58
55
  skills,
59
56
  } from "/home/me/.local/share/gtd/workflows/dist/default.mjs"
60
57
 
61
58
  export default feature()
62
59
 
63
- export async function hotfix() {
64
- return afterTail(await buildTail(start(), { fixFirst: true }))
65
- }
66
-
67
- export const doors = { hotfix: { workflow: "hotfix" } }
60
+ export const hotfix = workflow(
61
+ async () => afterTail(await buildTail(start(), { fixFirst: true })),
62
+ { defaults, envDefaults, skills, doors: { hotfix: {} } },
63
+ )
68
64
  ```
69
65
 
70
- `gtd --workflow hotfix` (or `gtd door hotfix`) starts it. Use the checkout's
66
+ `gtd start hotfix` starts it (the door of the same name too). Use the checkout's
71
67
  real path; `gtd-wf status` shows it.
72
68
 
73
69
  To change a phase itself, read its source in the checkout (`workflows/default/`)
@@ -89,47 +85,53 @@ workflow that fails to load breaks every gtd command in the repository.
89
85
 
90
86
  ## The step API
91
87
 
92
- | Call | Actor | Rest content | Resolves to |
93
- | ---------------------------- | ------- | ------------ | -------------------------------------- |
94
- | `agent(name, prompt, opts?)` | `agent` | `prompt` | `void`, once an agent turn landed |
95
- | `human(name, opts?)` | `human` | `message` | `void`, once a person landed |
96
- | `run(name, body, opts?)` | `check` | `script` | `void`, once the run's tree landed |
97
- | `judge(name, spec)` | `judge` | `message` | `{ answers, truncated }` |
98
- | `restart()` | — | — | never: ends the episode from any depth |
88
+ | Call | Actor | Rest content | Resolves to |
89
+ | ------------------------------ | ------- | ------------ | -------------------------------------- |
90
+ | `agent(name, prompt, opts?)` | `agent` | `prompt` | `void`, once an agent turn landed |
91
+ | `human(name, message?, opts?)` | `human` | `message` | `void`, once a person landed |
92
+ | `run(name, body, opts?)` | `check` | `script` | `void`, once the run's tree landed |
93
+ | `judge(name, spec)` | `judge` | `message` | `{ answers, truncated }` |
94
+ | `restart()` | — | — | never: ends the episode from any depth |
99
95
 
100
96
  - `run` body: a POSIX `sh` string the driver runs verbatim. Decide in flow code,
101
97
  then render the script from those values — `check(name, command, …)` and the
102
98
  exported `checkScript`, `revertScript`, `restoreScript`, `removeScript`,
103
99
  `moveScript` and `quote` do that for the common cases. **A run's outcome is
104
100
  what it leaves in the tree** — read it back with `changes()` and `read()`.
105
- - `judge` takes `{ questions, evidence, message?, label? }`. Questions are
106
- `{ id, primitive: "noul" | "choice" | "score", instructions, criteria }`;
107
- `evidence` is an object of strings, the `judgeBudgetBytes` var split evenly
108
- across its keys. It resolves to `answers` — one `{ answer, p }` per question
109
- id (a `noul` reads back as `"yes"`/`"no"`), `undefined` when the verdict left
110
- it out — and `truncated`, the evidence keys the budget cut. Compare answers
111
- with plain `if`s; landing with no verdict leaves every answer `undefined`, so
112
- make `undefined` take the conservative branch.
101
+ - `judge` takes
102
+ `{ questions, evidence, message?, label?, file?, mode?, base? }`. Questions
103
+ are
104
+ `{ id, primitive: "noul" | "yesno" | "choice" | "score", instructions, criteria }`
105
+ (`yesno` is `noul`); `evidence` is an object of strings, the
106
+ `judgeBudgetBytes` var split evenly across its keys. It resolves to `answers`
107
+ — one `{ answer, p }` per question id (a `noul` reads back as `"yes"`/`"no"`),
108
+ `undefined` when the verdict left it out — and `truncated`, the evidence keys
109
+ the budget cut. Compare answers with plain `if`s; landing with no verdict
110
+ leaves every answer `undefined`, so make `undefined` take the conservative
111
+ branch.
113
112
  - A person or a driver sees a rest as one of the five content kinds `capture` (a
114
113
  dirty tree at a human step), `message`, `script`, `prompt`, `stalled`. Every
115
114
  step you add must fit one of them; there is no sixth.
116
115
 
117
116
  Options (all optional): `label`, `file` (a `.gtd/` path), `mode` (needs `file`;
118
117
  `qa`, `review`, `findings`, or a `config.modes` name), `message` (human/judge),
119
- `model`, `system` (agent), `allowEmpty` (agent), `acceptClean` (human), `base`
120
- (the commit the step reviews since — what `gtd base` prints).
118
+ `model`, `system` (agent), `allowEmpty` (agent), `keep` (agent, not with
119
+ `allowEmpty`), `acceptClean` (human), `approve` (human, not with `file`/`mode`),
120
+ `base` (the commit the step reviews since — what `gtd base` prints). The types
121
+ reject the combinations replay refuses.
121
122
 
122
123
  Helpers — pure reads of the commit replay stands on (the tree the last step
123
124
  left, never the live working tree): `read(path)`, `glob(pattern)`,
124
125
  `changes(glob?)` (what the last step changed: `{ path, status, before, after }`
125
126
  per path, `status` one of `"added"`/`"modified"`/`"deleted"`, plus `paths` and
126
127
  `get(path)`), `sections(text)` (`## ` headings), `openQuestions(text)` (a `qa`
127
- document's unanswered questions), `vars` (process settings, pinned at process
128
- start — safe to branch on), `env` (environment settings, read live — only for
129
- prompt text, step options and `run()` bodies, never a branch), `head()` (the
130
- commit the flow stands on) and `start()` (the process's diff base). State a flow
131
- needs across steps — a counter, the previous report, a review round's base —
132
- lives in local variables; replay rebuilds them.
128
+ document's unanswered questions), `settings` (process settings, pinned at
129
+ process start — safe to branch on), `envSettings` (environment settings, read
130
+ live — only for prompt text, step options and `run()` bodies, never a branch) —
131
+ `settingsOf(defaults)` types them by the declared names — `head()` (the commit
132
+ the flow stands on) and `start()` (the process's diff base). State a flow needs
133
+ across steps — a counter, the previous report, a review round's base — lives in
134
+ local variables; replay rebuilds them.
133
135
 
134
136
  Composition: `scope(name, fn)` prefixes step names (`build.fix`) and sets their
135
137
  **memory scope** (one scope = one agent conversation = one model/system/skills —
@@ -139,7 +141,12 @@ inside. Skills are declared per scope, never on `agent()`, and a driver
139
141
  restricts each turn to its scope's list where it can. `refuse(message)` refuses
140
142
  the pending landing — call it right after the step whose turn you reject.
141
143
  `@pmelab/gtd/flows` exports `requireProgress(file)`, `requireAnswers(file)` and
142
- `requireRevert(edited, base)` among its ready-made checks.
144
+ `requireRevert(edited, base)` among its ready-made guards, and
145
+ `check(name, command, report)` runs a command as a `run` step.
146
+ `@pmelab/gtd/workflow-kit` adds larger building blocks (`healthy`, `qualityLap`,
147
+ `review`, …; see its reference, `docs/workflow-kit.md`). `sections`, `threads`
148
+ and `openQuestions` throw outside a replay; `@pmelab/gtd/steering` exports pure
149
+ copies under the same names.
143
150
 
144
151
  ## Names, commits and history
145
152
 
@@ -149,14 +156,13 @@ the pending landing — call it right after the step whose turn you reject.
149
156
  - An episode ends when the flow returns or calls `restart()`; the next starts at
150
157
  the flow's first step on an ordinary start — that step is where a finished
151
158
  process waits (gtd-workflows' default uses `human("idle", …)`).
152
- - `gtd --workflow <name>` starts a process on the exported workflow `<name>` (an
153
- unknown name is a usage error); `gtd door <name> [args]` starts one through a
154
- `doors` entry `{ workflow, args?, vars? }` (`gtd doors` lists them). A
155
- factory's build may return `doors` without `workflow`; exporting
156
- gtd-workflows' `fix`, `review`, `rollout` and `cleanup` brings `fix`,
157
- `review [base]`, `rollout <flag>` and `cleanup <flag>`, and yours merge over
158
- them. An `export const base = (workflow, vars) => commitish | undefined` fixes
159
- a started process's diff base (blank = default-branch merge-base).
159
+ - `gtd start <name>` starts a process on the exported workflow `<name>` (an
160
+ unknown name is a usage error), or through a door `<name> [args]`; bare
161
+ `gtd start` lists both. A workflow's `doors` option maps a door name to
162
+ `{ args?, vars? }`; exporting gtd-workflows' `fix`, `review`, `rollout` and
163
+ `cleanup` brings `fix`, `review [base]`, `rollout <flag>` and
164
+ `cleanup <flag>`. A `base: (workflow, vars) => commitish | undefined` option
165
+ fixes a started process's diff base (blank = default-branch merge-base).
160
166
  `--var <name>=<value>` only pins process settings: names the workflow's
161
167
  `defaults` declare — never an environment setting.
162
168
 
@@ -178,7 +184,7 @@ Branch on what the step left, not on who acted:
178
184
  ```ts
179
185
  await run(
180
186
  "check",
181
- `${env.testCommand} > .gtd/FEEDBACK.md 2>&1 && rm -f .gtd/FEEDBACK.md`,
187
+ `${envSettings.testCommand} > .gtd/FEEDBACK.md 2>&1 && rm -f .gtd/FEEDBACK.md`,
182
188
  )
183
189
  if (read(".gtd/FEEDBACK.md") !== undefined) {
184
190
  await agent("fix", "Fix what .gtd/FEEDBACK.md reports, then delete it.", {