@pmelab/gtd 58.2.0 → 59.0.1

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 (150) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +120 -395
  3. package/dist/chunks/{Schema-B3o6eRNU.mjs → testkit-07Z_ENch.mjs} +13503 -3460
  4. package/dist/chunks/{wire-C12dahrH.mjs → wire-BTUhRGXj.mjs} +1 -1
  5. package/dist/flows.mjs +1 -1
  6. package/dist/gtd.bundle.mjs +484 -497
  7. package/dist/testkit/gherkin.mjs +145 -65
  8. package/dist/testkit.mjs +1 -1
  9. package/dist/wire.mjs +1 -1
  10. package/package.json +3 -42
  11. package/skills/authoring/SKILL.md +34 -27
  12. package/src/cli/Cli.test.ts +148 -71
  13. package/src/cli/Cli.ts +384 -463
  14. package/src/cli/Help.test.ts +47 -0
  15. package/src/cli/Help.ts +70 -0
  16. package/src/cli/Program.test.ts +127 -33
  17. package/src/cli/Program.ts +70 -25
  18. package/src/edge/Edge.ts +4 -4
  19. package/src/emit/Emit.test.ts +1 -1
  20. package/src/emit/Emit.ts +1 -1
  21. package/src/flags/index.test.ts +0 -13
  22. package/src/flags/index.ts +1 -3
  23. package/src/flows/factory.ts +3 -1
  24. package/src/gherkin/index.ts +135 -71
  25. package/src/gherkin/messages.test.ts +90 -0
  26. package/src/gherkin/messages.ts +84 -0
  27. package/src/install/Install.test.ts +14 -11
  28. package/src/install/Install.ts +38 -15
  29. package/src/install/index.ts +1 -2
  30. package/src/replay/Replay.test.ts +1 -1
  31. package/src/replay/index.ts +1 -1
  32. package/src/step/planStart.ts +6 -1
  33. package/src/testkit/TestRepo.test.ts +47 -2
  34. package/src/testkit/TestRepo.ts +51 -6
  35. package/src/twins/EmittedScriptRecognizer.ts +5 -8
  36. package/src/twins/FlowScripts.ts +1 -1
  37. package/src/wire/Land.ts +1 -1
  38. package/src/workflow/index.ts +1 -0
  39. package/src/workflow/load.test.ts +138 -113
  40. package/src/workflow/load.ts +7 -7
  41. package/src/workflow/skills.test.ts +5 -5
  42. package/src/workflow/skills.ts +1 -1
  43. package/src/workflow/vars.test.ts +30 -3
  44. package/src/workflow/vars.ts +34 -2
  45. package/src/workflow-kit/index.ts +1 -1
  46. package/dist/chunks/reserved-lMYCaapY.mjs +0 -1990
  47. package/dist/chunks/review-Bfaror38.mjs +0 -427
  48. package/dist/chunks/tail-Bd7L940p.mjs +0 -355
  49. package/dist/chunks/testkit-JDtpNKyi.mjs +0 -10410
  50. package/dist/workflows/cleanup.mjs +0 -36
  51. package/dist/workflows/feature.mjs +0 -1018
  52. package/dist/workflows/fix.mjs +0 -41
  53. package/dist/workflows/review.mjs +0 -45
  54. package/dist/workflows/rollout.mjs +0 -48
  55. package/dist/workflows.mjs +0 -40651
  56. package/src/install/ConfigScaffold.ts +0 -22
  57. package/src/workflows/cleanup/cleanup-path.feature +0 -94
  58. package/src/workflows/cleanup/cleanup.feature +0 -315
  59. package/src/workflows/cleanup/cleanup.test.ts +0 -276
  60. package/src/workflows/cleanup/cleanup.ts +0 -52
  61. package/src/workflows/cleanup/index.ts +0 -6
  62. package/src/workflows/feature/acceptance-criteria-tier.feature +0 -25
  63. package/src/workflows/feature/acceptance-criteria.feature +0 -389
  64. package/src/workflows/feature/criteria-coverage.feature +0 -102
  65. package/src/workflows/feature/criteria.test.ts +0 -147
  66. package/src/workflows/feature/criteria.ts +0 -267
  67. package/src/workflows/feature/fast-path.feature +0 -93
  68. package/src/workflows/feature/feature.test.ts +0 -338
  69. package/src/workflows/feature/feature.ts +0 -156
  70. package/src/workflows/feature/green.ts +0 -19
  71. package/src/workflows/feature/index.ts +0 -18
  72. package/src/workflows/feature/lens-models.feature +0 -69
  73. package/src/workflows/feature/lens-review.feature +0 -566
  74. package/src/workflows/feature/naive-acceptance.feature +0 -147
  75. package/src/workflows/feature/naive-test.feature +0 -263
  76. package/src/workflows/feature/one-step-land.feature +0 -93
  77. package/src/workflows/feature/planning.ts +0 -41
  78. package/src/workflows/feature/refactor-absorbs.feature +0 -131
  79. package/src/workflows/feature/refactor-first.feature +0 -158
  80. package/src/workflows/feature/refactor-lens-models.feature +0 -79
  81. package/src/workflows/feature/refactor-rebuild.feature +0 -273
  82. package/src/workflows/feature/refactor-review.feature +0 -1528
  83. package/src/workflows/feature/refactor.test.ts +0 -1220
  84. package/src/workflows/feature/refactor.ts +0 -368
  85. package/src/workflows/feature/release-flag.feature +0 -167
  86. package/src/workflows/feature/release-flag.test.ts +0 -49
  87. package/src/workflows/feature/release-flag.ts +0 -46
  88. package/src/workflows/feature/review-gate.feature +0 -241
  89. package/src/workflows/feature/reviewer-walkthrough.feature +0 -142
  90. package/src/workflows/feature/risk-tier.feature +0 -351
  91. package/src/workflows/feature/seam-rejection.feature +0 -146
  92. package/src/workflows/feature/seam.feature +0 -297
  93. package/src/workflows/feature/seam.test.ts +0 -527
  94. package/src/workflows/feature/seam.ts +0 -426
  95. package/src/workflows/feature/standards.feature +0 -354
  96. package/src/workflows/feature/steps.test.ts +0 -65
  97. package/src/workflows/feature/steps.ts +0 -45
  98. package/src/workflows/feature/tier-pin.feature +0 -66
  99. package/src/workflows/feature/tier.test.ts +0 -166
  100. package/src/workflows/feature/tier.ts +0 -56
  101. package/src/workflows/fix/fix-lens-origin.feature +0 -114
  102. package/src/workflows/fix/fix-quality.feature +0 -212
  103. package/src/workflows/fix/fix.feature +0 -79
  104. package/src/workflows/fix/fix.test.ts +0 -74
  105. package/src/workflows/fix/fix.ts +0 -52
  106. package/src/workflows/fix/index.ts +0 -6
  107. package/src/workflows/index.ts +0 -47
  108. package/src/workflows/review/index.ts +0 -6
  109. package/src/workflows/review/review-door.feature +0 -199
  110. package/src/workflows/review/review.test.ts +0 -27
  111. package/src/workflows/review/review.ts +0 -60
  112. package/src/workflows/rollout/index.ts +0 -6
  113. package/src/workflows/rollout/rollout.feature +0 -510
  114. package/src/workflows/rollout/rollout.test.ts +0 -263
  115. package/src/workflows/rollout/rollout.ts +0 -82
  116. package/src/workflows/shared/__fixtures__/feature-collecting-prompt.txt +0 -98
  117. package/src/workflows/shared/access.test.ts +0 -62
  118. package/src/workflows/shared/access.ts +0 -24
  119. package/src/workflows/shared/config.fixture.ts +0 -12
  120. package/src/workflows/shared/fences.ts +0 -24
  121. package/src/workflows/shared/health.test.ts +0 -148
  122. package/src/workflows/shared/health.ts +0 -191
  123. package/src/workflows/shared/learn.test.ts +0 -140
  124. package/src/workflows/shared/learn.ts +0 -201
  125. package/src/workflows/shared/lenses.test.ts +0 -39
  126. package/src/workflows/shared/lenses.ts +0 -294
  127. package/src/workflows/shared/options.test.ts +0 -157
  128. package/src/workflows/shared/options.ts +0 -92
  129. package/src/workflows/shared/progress.feature +0 -124
  130. package/src/workflows/shared/prose.ts +0 -263
  131. package/src/workflows/shared/recheck.feature +0 -60
  132. package/src/workflows/shared/registry.test.ts +0 -345
  133. package/src/workflows/shared/registry.ts +0 -183
  134. package/src/workflows/shared/reserved.ts +0 -11
  135. package/src/workflows/shared/review.test.ts +0 -1062
  136. package/src/workflows/shared/review.ts +0 -446
  137. package/src/workflows/shared/skills.test.ts +0 -121
  138. package/src/workflows/shared/skills.ts +0 -49
  139. package/src/workflows/shared/standards.test.ts +0 -90
  140. package/src/workflows/shared/standards.ts +0 -156
  141. package/src/workflows/shared/steps.test.ts +0 -219
  142. package/src/workflows/shared/steps.ts +0 -201
  143. package/src/workflows/shared/tail.test.ts +0 -454
  144. package/src/workflows/shared/tail.ts +0 -264
  145. package/src/workflows/shared/text.fixture.ts +0 -128
  146. package/src/workflows/shared/text.test.ts +0 -1430
  147. package/src/workflows/shared/text.ts +0 -1700
  148. package/src/workflows/shared/threads.test.ts +0 -129
  149. package/src/workflows/shared/vars.ts +0 -47
  150. package/src/workflows/shared/workflows.steps.ts +0 -54
@@ -1,9 +1,66 @@
1
- import { n as testRepo, t as runsItsScript } from "../chunks/testkit-JDtpNKyi.mjs";
1
+ import { n as testRepo, t as runsItsScript } from "../chunks/testkit-07Z_ENch.mjs";
2
2
  import { resolve } from "node:path";
3
3
  import assert from "node:assert";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { Given, QuickPickleWorld, Then, When, setWorldConstructor } from "quickpickle";
6
+ //#region src/gherkin/messages.ts
7
+ const CONTENT_LINES = 5;
8
+ const reason = (error) => error instanceof Error ? error.message : String(error);
9
+ const beatLines = async (repo) => {
10
+ try {
11
+ const beat = await repo.next();
12
+ const content = beat.content.trimEnd().split("\n");
13
+ const more = content.length - CONTENT_LINES;
14
+ return [
15
+ `step "${beat.state}" (${beat.kind}, ${beat.actor}${beat.idle ? ", idle" : ""})`,
16
+ "content:",
17
+ ...content.slice(0, CONTENT_LINES).map((line) => ` | ${line}`),
18
+ ...more > 0 ? [` | … ${more} more lines`] : []
19
+ ];
20
+ } catch (error) {
21
+ return [`unknown — gtd next failed: ${reason(error)}`];
22
+ }
23
+ };
24
+ /** Where the process rests, for a failed outcome to end with. Never throws: it describes a failure. */
25
+ const restDescription = async (repo, neverUsed) => {
26
+ let lines;
27
+ try {
28
+ const r = repo();
29
+ lines = [
30
+ ...await beatLines(r),
31
+ "last commits:",
32
+ ...r.subjects().slice(-5).map((subject) => ` ${subject}`)
33
+ ];
34
+ } catch (error) {
35
+ lines = [`unknown — ${reason(error)}`];
36
+ }
37
+ if (neverUsed.length > 0) lines.push(`actors never used: ${neverUsed.map((step) => `"${step}"`).join(", ")}`);
38
+ return ["where the process rests:", ...lines.map((line) => ` ${line}`)].join("\n");
39
+ };
40
+ /** Both sides of a failed `contains`: the text looked for, and what was there. */
41
+ const containsFailure = (where, text, content, expected) => content === void 0 ? `${where} does not exist${expected ? `, so it cannot contain "${text}"` : ""}` : `${where} ${expected ? "does not contain" : "contains"} "${text}"; it holds:\n${content}`;
42
+ /** What `attempt` was refused with; fails when it was not refused at all. */
43
+ const refusal = async (attempt, expectation) => {
44
+ try {
45
+ await attempt;
46
+ } catch (error) {
47
+ return error instanceof Error ? error.message : String(error);
48
+ }
49
+ assert.fail(`expected a refusal ${expectation}, but nothing was refused`);
50
+ };
51
+ const refusedWith = async (attempt, message) => {
52
+ const got = await refusal(attempt, `with "${message}"`);
53
+ if (!got.includes(message)) assert.fail(`expected a refusal with "${message}", but it was refused with:\n${got}`);
54
+ };
55
+ const assertContains = (where, text, content) => {
56
+ if (content === void 0 || !content.includes(text)) assert.fail(containsFailure(where, text, content, true));
57
+ };
58
+ const assertLacks = (where, text, content) => {
59
+ if (content === void 0 || content.includes(text)) assert.fail(containsFailure(where, text, content, false));
60
+ };
61
+ //#endregion
6
62
  //#region src/gherkin/index.ts
63
+ const usedActors = /* @__PURE__ */ new WeakMap();
7
64
  var TestkitWorld = class extends QuickPickleWorld {
8
65
  constructor(context, info) {
9
66
  super(context, info);
@@ -51,6 +108,8 @@ var TestkitWorld = class extends QuickPickleWorld {
51
108
  }
52
109
  async drive() {
53
110
  const actors = {};
111
+ const used = usedActors.get(this) ?? /* @__PURE__ */ new Set();
112
+ usedActors.set(this, used);
54
113
  for (const [step, actions] of this.actions) actors[step] = async (beat, repo) => {
55
114
  for (const action of actions) await action(beat, repo);
56
115
  };
@@ -67,12 +126,35 @@ var TestkitWorld = class extends QuickPickleWorld {
67
126
  return hit[1];
68
127
  };
69
128
  }
70
- await this.repo().drive(actors);
129
+ const tracked = {};
130
+ for (const [step, actor] of Object.entries(actors)) tracked[step] = (beat, repo) => {
131
+ used.add(step);
132
+ return actor(beat, repo);
133
+ };
134
+ await this.repo().drive(tracked);
71
135
  }
72
136
  };
73
137
  setWorldConstructor(TestkitWorld);
74
138
  const withNewline = (content) => content.endsWith("\n") ? content : `${content}\n`;
75
139
  const args = (list) => list.split(" ").filter((arg) => arg !== "");
140
+ const neverUsed = (world) => {
141
+ const used = usedActors.get(world) ?? /* @__PURE__ */ new Set();
142
+ return [.../* @__PURE__ */ new Set([
143
+ ...world.actions.keys(),
144
+ ...world.verdicts.keys(),
145
+ ...world.conditionalVerdicts.keys()
146
+ ])].filter((step) => !used.has(step));
147
+ };
148
+ /** An outcome step: a failure ends with where the process rests. */
149
+ const outcome = (check) => async (world, ...rest) => {
150
+ try {
151
+ await check(world, ...rest);
152
+ } catch (error) {
153
+ const failure = error instanceof Error ? error : new Error(String(error));
154
+ failure.message += `\n\n${await restDescription(() => world.repo(), neverUsed(world))}`;
155
+ throw failure;
156
+ }
157
+ };
76
158
  Given("a gtd config with:", (world, source) => {
77
159
  world.configure(withNewline(source));
78
160
  });
@@ -197,87 +279,85 @@ When("gtd lands", async (world) => {
197
279
  When("the process is driven", async (world) => {
198
280
  await world.drive();
199
281
  });
200
- Then("opening door {string} is refused with {string}", async (world, door, message) => {
201
- await assert.rejects(world.repo().door(door), (error) => error.message.includes(message));
202
- });
203
- Then("starting workflow {string} with {string} is refused with {string}", async (world, workflow, list, message) => {
204
- await assert.rejects(world.repo().start(workflow, ...args(list)), (error) => error.message.includes(message));
205
- });
206
- Then("landing is refused with {string}", async (world, message) => {
207
- await assert.rejects(world.repo().land(), (error) => error.message.includes(message));
208
- });
209
- Then("driving the process is refused with {string}", async (world, message) => {
210
- await assert.rejects(world.drive(), (error) => error.message.includes(message));
211
- });
212
- Then("driving the process is refused without {string}", async (world, message) => {
213
- await assert.rejects(world.drive(), (error) => !error.message.includes(message));
214
- });
215
- Then("the process rests at {string}", async (world, step) => {
282
+ Then("opening door {string} is refused with {string}", outcome(async (world, door, message) => {
283
+ await refusedWith(world.repo().door(door), message);
284
+ }));
285
+ Then("starting workflow {string} with {string} is refused with {string}", outcome(async (world, workflow, list, message) => {
286
+ await refusedWith(world.repo().start(workflow, ...args(list)), message);
287
+ }));
288
+ Then("landing is refused with {string}", outcome(async (world, message) => {
289
+ await refusedWith(world.repo().land(), message);
290
+ }));
291
+ Then("driving the process is refused with {string}", outcome(async (world, message) => {
292
+ await refusedWith(world.drive(), message);
293
+ }));
294
+ Then("driving the process is refused without {string}", outcome(async (world, message) => {
295
+ const got = await refusal(world.drive(), `without "${message}"`);
296
+ if (got.includes(message)) assert.fail(`expected a refusal without "${message}", but it was refused with:\n${got}`);
297
+ }));
298
+ Then("the process rests at {string}", outcome(async (world, step) => {
216
299
  assert.strictEqual((await world.repo().next()).state, step);
217
- });
218
- Then("the resting step's content contains {string}", async (world, text) => {
219
- const { content } = await world.repo().next();
220
- assert.ok(content.includes(text), content);
221
- });
222
- Then("the resting step's content contains the hash of the commit {string}", async (world, subject) => {
300
+ }));
301
+ Then("the resting step's content contains {string}", outcome(async (world, text) => {
302
+ assertContains("the resting step's content", text, (await world.repo().next()).content);
303
+ }));
304
+ Then("the resting step's content contains the hash of the commit {string}", outcome(async (world, subject) => {
223
305
  const repo = world.repo();
224
306
  const hash = repo.hashes()[repo.subjects().lastIndexOf(subject)];
225
307
  assert.ok(hash !== void 0, `no commit "${subject}"`);
226
- const { content } = await repo.next();
227
- assert.ok(content.includes(hash), content);
228
- });
229
- Then("the resting step's content does not contain {string}", async (world, text) => {
230
- const { content } = await world.repo().next();
231
- assert.ok(!content.includes(text), content);
232
- });
233
- Then("the last commit subject is {string}", (world, subject) => {
308
+ assertContains("the resting step's content", hash, (await repo.next()).content);
309
+ }));
310
+ Then("the resting step's content does not contain {string}", outcome(async (world, text) => {
311
+ assertLacks("the resting step's content", text, (await world.repo().next()).content);
312
+ }));
313
+ Then("the last commit subject is {string}", outcome((world, subject) => {
234
314
  assert.strictEqual(world.repo().subjects().at(-1), subject);
235
- });
236
- Then("the commit subjects end with:", (world, list) => {
315
+ }));
316
+ Then("the commit subjects end with:", outcome((world, list) => {
237
317
  const expected = list.split("\n").filter((line) => line.trim() !== "");
238
318
  assert.deepStrictEqual(world.repo().subjects().slice(-expected.length), expected);
239
- });
319
+ }));
240
320
  const outputOf = async (world, list) => {
241
321
  const result = await world.repo().gtd(...args(list));
242
322
  assert.strictEqual(result.exitCode, 0, result.stderr);
243
323
  return result.stdout;
244
324
  };
245
- Then("the output of gtd {string} contains {string}", async (world, list, text) => {
246
- const out = await outputOf(world, list);
247
- assert.ok(out.includes(text), out);
248
- });
249
- Then("the output of gtd {string} does not contain {string}", async (world, list, text) => {
250
- const out = await outputOf(world, list);
251
- assert.ok(!out.includes(text), out);
252
- });
253
- Then("the output lines of gtd {string} start with:", async (world, list, prefixes) => {
325
+ Then("the output of gtd {string} contains {string}", outcome(async (world, list, text) => {
326
+ assertContains(`the output of gtd ${list}`, text, await outputOf(world, list));
327
+ }));
328
+ Then("the output of gtd {string} does not contain {string}", outcome(async (world, list, text) => {
329
+ assertLacks(`the output of gtd ${list}`, text, await outputOf(world, list));
330
+ }));
331
+ Then("the output lines of gtd {string} start with:", outcome(async (world, list, prefixes) => {
254
332
  const out = (await outputOf(world, list)).split("\n").filter((line) => line !== "");
255
333
  const expected = prefixes.split("\n").filter((line) => line.trim() !== "");
256
- assert.strictEqual(out.length, expected.length, out.join("\n"));
257
- out.forEach((line, i) => assert.ok(line.startsWith(expected[i]), out.join("\n")));
258
- });
259
- Then("{string} contains {string}", (world, path, text) => {
260
- assert.ok(world.repo().read(path)?.includes(text), `${path}: ${world.repo().read(path)}`);
261
- });
262
- Then("{string} contains:", (world, path, text) => {
263
- assert.ok(world.repo().read(path)?.includes(text), `${path}: ${world.repo().read(path)}`);
264
- });
265
- Then("{string} contains the short hash of the commit {string}", (world, path, subject) => {
334
+ const sides = `expected lines starting with:\n${expected.join("\n")}\ngot:\n${out.join("\n")}`;
335
+ assert.strictEqual(out.length, expected.length, sides);
336
+ out.forEach((line, i) => assert.ok(line.startsWith(expected[i]), sides));
337
+ }));
338
+ Then("{string} contains {string}", outcome((world, path, text) => {
339
+ assertContains(`"${path}"`, text, world.repo().read(path));
340
+ }));
341
+ Then("{string} contains:", outcome((world, path, text) => {
342
+ assertContains(`"${path}"`, text, world.repo().read(path));
343
+ }));
344
+ Then("{string} contains the short hash of the commit {string}", outcome((world, path, subject) => {
266
345
  const repo = world.repo();
267
346
  const hash = repo.hashes()[repo.subjects().lastIndexOf(subject)];
268
347
  assert.ok(hash !== void 0, `no commit "${subject}"`);
269
- assert.ok(repo.read(path)?.includes(hash.slice(0, 7)), `${path}: ${repo.read(path)}`);
270
- });
271
- Then("{string} contains {string} exactly once", (world, path, text) => {
348
+ assertContains(`"${path}"`, hash.slice(0, 7), repo.read(path));
349
+ }));
350
+ Then("{string} contains {string} exactly once", outcome((world, path, text) => {
272
351
  const content = world.repo().read(path) ?? "";
273
- assert.strictEqual(content.split(text).length - 1, 1, `${path}: ${content}`);
274
- });
275
- Then("{string} does not contain {string}", (world, path, text) => {
352
+ const times = content.split(text).length - 1;
353
+ assert.strictEqual(times, 1, `"${path}" contains "${text}" ${times} times; it holds:\n${content}`);
354
+ }));
355
+ Then("{string} does not contain {string}", outcome((world, path, text) => {
356
+ assertLacks(`"${path}"`, text, world.repo().read(path));
357
+ }));
358
+ Then("{string} does not exist", outcome((world, path) => {
276
359
  const content = world.repo().read(path);
277
- assert.ok(content !== void 0 && !content.includes(text), `${path}: ${content}`);
278
- });
279
- Then("{string} does not exist", (world, path) => {
280
- assert.strictEqual(world.repo().read(path), void 0);
281
- });
360
+ assert.strictEqual(content, void 0, `"${path}" exists; it holds:\n${content}`);
361
+ }));
282
362
  //#endregion
283
363
  export { TestkitWorld };
package/dist/testkit.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { n as testRepo, r as InMemRepo, t as runsItsScript } from "./chunks/testkit-JDtpNKyi.mjs";
1
+ import { n as testRepo, r as InMemRepo, t as runsItsScript } from "./chunks/testkit-07Z_ENch.mjs";
2
2
  export { InMemRepo, runsItsScript, testRepo };
package/dist/wire.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { C as EXIT_SIGTERM, D as UNATTRIBUTED_MODEL, E as ACCESS_REFUSAL, O as WIRE_SCHEMA, S as EXIT_SIGINT, T as EXIT_USAGE_ERROR, _ as demandOf, a as landFields, b as EXIT_OK, c as renderLandJson, d as renderBeatPlain, f as beatDocument, g as beatKindOf, h as BEAT_KINDS, i as judgeJson, k as renderFormat, l as renderLandPlain, m as statusOf, n as resolveSession, o as landProseText, p as renderBeatJson, r as selectPath, s as noopText, t as globMatches, u as DEMAND_BRIEFING, v as stallDiagnosis, w as EXIT_STRAYS_FOUND, x as EXIT_RUNTIME_ERROR, y as EXIT_CODES } from "./chunks/wire-C12dahrH.mjs";
1
+ import { C as EXIT_SIGTERM, D as UNATTRIBUTED_MODEL, E as ACCESS_REFUSAL, O as WIRE_SCHEMA, S as EXIT_SIGINT, T as EXIT_USAGE_ERROR, _ as demandOf, a as landFields, b as EXIT_OK, c as renderLandJson, d as renderBeatPlain, f as beatDocument, g as beatKindOf, h as BEAT_KINDS, i as judgeJson, k as renderFormat, l as renderLandPlain, m as statusOf, n as resolveSession, o as landProseText, p as renderBeatJson, r as selectPath, s as noopText, t as globMatches, u as DEMAND_BRIEFING, v as stallDiagnosis, w as EXIT_STRAYS_FOUND, x as EXIT_RUNTIME_ERROR, y as EXIT_CODES } from "./chunks/wire-BTUhRGXj.mjs";
2
2
  export { ACCESS_REFUSAL, BEAT_KINDS, DEMAND_BRIEFING, EXIT_CODES, EXIT_OK, EXIT_RUNTIME_ERROR, EXIT_SIGINT, EXIT_SIGTERM, EXIT_STRAYS_FOUND, EXIT_USAGE_ERROR, UNATTRIBUTED_MODEL, WIRE_SCHEMA, beatDocument, beatKindOf, demandOf, globMatches, judgeJson, landFields, landProseText, noopText, renderBeatJson, renderBeatPlain, renderFormat, renderLandJson, renderLandPlain, resolveSession, selectPath, stallDiagnosis, statusOf };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmelab/gtd",
3
- "version": "58.2.0",
3
+ "version": "59.0.1",
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": {
@@ -27,41 +27,6 @@
27
27
  "source": "./src/workflow-kit/index.ts",
28
28
  "default": "./dist/workflow-kit.mjs"
29
29
  },
30
- "./workflows": {
31
- "types": "./src/workflows/index.ts",
32
- "source": "./src/workflows/index.ts",
33
- "default": "./dist/workflows.mjs"
34
- },
35
- "./workflows/feature": {
36
- "types": "./src/workflows/feature/index.ts",
37
- "source": "./src/workflows/feature/index.ts",
38
- "default": "./dist/workflows/feature.mjs"
39
- },
40
- "./workflows/review": {
41
- "types": "./src/workflows/review/index.ts",
42
- "source": "./src/workflows/review/index.ts",
43
- "default": "./dist/workflows/review.mjs"
44
- },
45
- "./workflows/fix": {
46
- "types": "./src/workflows/fix/index.ts",
47
- "source": "./src/workflows/fix/index.ts",
48
- "default": "./dist/workflows/fix.mjs"
49
- },
50
- "./workflow": {
51
- "types": "./src/workflows/index.ts",
52
- "source": "./src/workflows/index.ts",
53
- "default": "./dist/workflows.mjs"
54
- },
55
- "./workflows/rollout": {
56
- "types": "./src/workflows/rollout/index.ts",
57
- "source": "./src/workflows/rollout/index.ts",
58
- "default": "./dist/workflows/rollout.mjs"
59
- },
60
- "./workflows/cleanup": {
61
- "types": "./src/workflows/cleanup/index.ts",
62
- "source": "./src/workflows/cleanup/index.ts",
63
- "default": "./dist/workflows/cleanup.mjs"
64
- },
65
30
  "./testkit": {
66
31
  "types": "./src/testkit/index.ts",
67
32
  "source": "./src/testkit/index.ts",
@@ -76,7 +41,6 @@
76
41
  "files": [
77
42
  "dist/",
78
43
  "src/flows/",
79
- "src/workflows/",
80
44
  "src/workflow-kit/",
81
45
  "src/wire/",
82
46
  "src/steering/",
@@ -119,7 +83,7 @@
119
83
  "scripts": {
120
84
  "prepare": "husky",
121
85
  "dev": "node dev/run.mjs",
122
- "build": "tsdown --filter web && npx @tailwindcss/cli -i src/web/styles.css -o dist/web/main.css --minify && node scripts/inline-web-client.mjs && tsdown --filter gtd && tsdown --filter workflows && tsdown --filter entrypoints",
86
+ "build": "tsdown --filter web && npx @tailwindcss/cli -i src/web/styles.css -o dist/web/main.css --minify && node scripts/inline-web-client.mjs && tsdown --filter gtd && tsdown --filter entrypoints",
123
87
  "postbuild": "node scripts/assert-no-test-doubles.mjs",
124
88
  "watch": "tsdown --watch",
125
89
  "bench:replay": "node scripts/bench-replay.mjs",
@@ -141,9 +105,7 @@
141
105
  "analyze": "fallow --summary --quiet",
142
106
  "test:web": "vitest run --project storybook",
143
107
  "storybook": "storybook dev --host 0.0.0.0",
144
- "coverage": "vitest run --project unit --coverage",
145
- "eval": ": \"${GTD_EVALS_URL:?GTD_EVALS_URL is required — the OpenAI-compatible gateway every graded turn and the judge run through}\" && : \"${GTD_EVALS_KEY:?GTD_EVALS_KEY is required — the gateway bearer key}\" && npx turbo run build && node evals/eval.mjs",
146
- "eval:baseline": "node evals/compare-baseline.mjs --record"
108
+ "coverage": "vitest run --project unit --coverage"
147
109
  },
148
110
  "lint-staged": {
149
111
  "*": "oxfmt --no-error-on-unmatched-pattern --write"
@@ -184,7 +146,6 @@
184
146
  "oxlint": "^1.74.0",
185
147
  "playwright": "^1.62.1",
186
148
  "prettier": "^3.8.1",
187
- "promptfoo": "0.122.1",
188
149
  "quickpickle": "1.11.2",
189
150
  "react": "^19.2.0",
190
151
  "react-dom": "^19.2.0",
@@ -29,20 +29,25 @@ what a driver does.
29
29
  state (`gtd next`, `gtd lsp`, `gtd door` and `gtd doors` included). It is code
30
30
  the user's repository runs; write it with the same care as a build script.
31
31
 
32
- ## Golden rule: start from the shipped workflows, edit incrementally
32
+ ## Golden rule: start from an existing workflow, edit incrementally
33
33
 
34
34
  Do **not** write a workflow from a blank page unless the user wants something
35
- tiny. gtd ships three workflows (`feature`, `review`, `fix`) as named exports of
36
- `@pmelab/gtd/workflows`; `gtd install` scaffolds a `gtd.config.ts` re-exporting
37
- them. 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
35
+ tiny. gtd ships no workflow; https://github.com/pmelab/gtd-workflows does. Its
36
+ `default` (feature, review, fix, rollout, cleanup) is used from a checkout
37
+ pinned on the machine — `bin/gtd-wf pin` there, then `bin/gtd-wf config default`
38
+ prints a `gtd.config.ts` importing its bundle by absolute path. Every phase and
39
+ single step that bundle is built from is a named export too, so a config can
40
+ import what it keeps and write only what changes. Import phases by name; **never
40
41
  `export *` from it** — every exported function of your file would become a
41
42
  startable workflow:
42
43
 
43
44
  ```ts
44
45
  import { start } from "@pmelab/gtd/flows"
45
- import { afterTail, buildTail, feature } from "@pmelab/gtd/workflows"
46
+ import {
47
+ afterTail,
48
+ buildTail,
49
+ feature,
50
+ } from "/home/me/.local/share/gtd/workflows/dist/default.mjs"
46
51
 
47
52
  export {
48
53
  defaults,
@@ -51,9 +56,9 @@ export {
51
56
  base,
52
57
  steering,
53
58
  skills,
54
- } from "@pmelab/gtd/workflows"
59
+ } from "/home/me/.local/share/gtd/workflows/dist/default.mjs"
55
60
 
56
- export default feature
61
+ export default feature()
57
62
 
58
63
  export async function hotfix() {
59
64
  return afterTail(await buildTail(start(), { fixFirst: true }))
@@ -62,21 +67,22 @@ export async function hotfix() {
62
67
  export const doors = { hotfix: { workflow: "hotfix" } }
63
68
  ```
64
69
 
65
- `gtd --workflow hotfix` (or `gtd door hotfix`) starts it.
70
+ `gtd --workflow hotfix` (or `gtd door hotfix`) starts it. Use the checkout's
71
+ real path; `gtd-wf status` shows it.
66
72
 
67
- To change a phase itself, read its source in the npm package
68
- (`node_modules/@pmelab/gtd/src/workflows/`, or under `$(npm root -g)` for a
69
- global install) and write your own version in `gtd.config.ts`, reusing its
70
- single steps.
73
+ To change a phase itself, read its source in the checkout (`workflows/default/`)
74
+ and either write your own version in `gtd.config.ts`, reusing its single steps,
75
+ or copy the whole workflow in a clone of gtd-workflows
76
+ (`bin/gtd-wf new <name> --from default`) and change it there, with its tests.
71
77
 
72
78
  There is no `extends`/merge: the innermost `gtd.config.ts` walking up from the
73
79
  current directory is the whole workflow. If one already exists, read it and edit
74
80
  it in place.
75
81
 
76
- Prefer the bundled workflow's own parts over re-implementing them — `healthy`,
82
+ Prefer the workflow's own parts over re-implementing them — `healthy`,
77
83
  `escalation`, `gate`, `design`, `seamBuild`, `implement`, `qualityLap`,
78
84
  `buildTail`, and single steps like `triage` or `fixCheck`. Their full step names
79
- are versioned API.
85
+ are that workflow's versioned API, not gtd's.
80
86
 
81
87
  Make one small change, **verify it loads** (see "Verify"), then make the next. A
82
88
  workflow that fails to load breaks every gtd command in the repository.
@@ -133,7 +139,7 @@ inside. Skills are declared per scope, never on `agent()`, and a driver
133
139
  restricts each turn to its scope's list where it can. `refuse(message)` refuses
134
140
  the pending landing — call it right after the step whose turn you reject.
135
141
  `@pmelab/gtd/flows` exports `requireProgress(file)`, `requireAnswers(file)` and
136
- `requireRevert(edited, base)`, three such checks ready-made.
142
+ `requireRevert(edited, base)` among its ready-made checks.
137
143
 
138
144
  ## Names, commits and history
139
145
 
@@ -142,15 +148,15 @@ the pending landing — call it right after the step whose turn you reject.
142
148
  the last dot). Rename a step and every process resting on it diverges.
143
149
  - An episode ends when the flow returns or calls `restart()`; the next starts at
144
150
  the flow's first step on an ordinary start — that step is where a finished
145
- process waits (the bundled one is `human("idle", …)`).
151
+ process waits (gtd-workflows' default uses `human("idle", …)`).
146
152
  - `gtd --workflow <name>` starts a process on the exported workflow `<name>` (an
147
153
  unknown name is a usage error); `gtd door <name> [args]` starts one through a
148
154
  `doors` entry `{ workflow, args?, vars? }` (`gtd doors` lists them). A
149
- factory's build may return `doors` without `workflow`; exporting the bundled
150
- `fix`, `review`, `rollout` and `cleanup` brings `fix`, `review [base]`,
151
- `rollout <flag>` and `cleanup <flag>`, and yours merge over them. An
152
- `export const base = (workflow, vars) => commitish | undefined` fixes a
153
- started process's diff base (blank = default-branch merge-base).
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).
154
160
  `--var <name>=<value>` only pins process settings: names the workflow's
155
161
  `defaults` declare — never an environment setting.
156
162
 
@@ -200,7 +206,8 @@ divergence later.
200
206
  network or filesystem. Read the tree through the helpers; do IO inside a
201
207
  `run()` body.
202
208
  - Await only a step, `scope()`, or a function that steps. Anything else fails
203
- with `the flow awaited something that is not a step`.
209
+ with `the flow awaited something that is not a step`. gtd does not flag a step
210
+ you forget to await — turn on a `no-floating-promises` lint rule.
204
211
  - One call site per step name — wrap a reused helper in two different
205
212
  `scope()`s.
206
213
  - No `try`/`catch` around a step: `restart()` and refusals travel as exceptions.
@@ -222,7 +229,7 @@ workflow.
222
229
 
223
230
  ## Worked example: add an approval gate before building
224
231
 
225
- In the bundled default, `planAndBuild` runs the design phase, then
232
+ In gtd-workflows' default, `planAndBuild` runs the design phase, then
226
233
  `buildFeature`: the naive pass and its seam judge, then the implementation (the
227
234
  pass itself on `land`), the quality lap and the human review (a review feedback
228
235
  lap re-runs design inside `buildFeature` and does not pass it again). Add a
@@ -260,5 +267,5 @@ process before switching to the edited workflow.
260
267
  - This skill is versioned in the gtd repository, not auto-installed. When gtd is
261
268
  upgraded, re-copy it from the new version's `skills/authoring/SKILL.md`.
262
269
  - Where this file and the code disagree, the code wins: the step API's own doc
263
- comments in `@pmelab/gtd/flows`, and the bundled workflow under
264
- `src/workflows/`.
270
+ comments in `@pmelab/gtd/flows`, and the workflow's source in its own
271
+ repository (gtd-workflows' `workflows/default/`).