@pmelab/gtd 24.4.0 → 25.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 (33) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +13 -18
  3. package/dist/chunks/{reserved-B9pG93Fc.mjs → reserved-6pAgFGgB.mjs} +69 -125
  4. package/dist/gtd.bundle.mjs +1 -1
  5. package/dist/testkit.mjs +3 -3
  6. package/dist/workflow-kit.mjs +47 -90
  7. package/dist/workflows/feature.mjs +25 -47
  8. package/dist/workflows/fix.mjs +1 -1
  9. package/dist/workflows/review.mjs +1 -1
  10. package/dist/workflows.mjs +135 -261
  11. package/package.json +1 -2
  12. package/src/replay/Replay.ts +2 -1
  13. package/src/workflow-kit/scenarios.test.ts +116 -0
  14. package/src/workflow-kit/scenarios.ts +53 -113
  15. package/src/workflows/feature/feature.ts +2 -5
  16. package/src/workflows/feature/packages.test.ts +17 -34
  17. package/src/workflows/feature/packages.ts +13 -52
  18. package/src/workflows/feature/planning.ts +1 -1
  19. package/src/workflows/feature/review.test.ts +0 -47
  20. package/src/workflows/feature/review.ts +6 -10
  21. package/src/workflows/index.ts +0 -2
  22. package/src/workflows/shared/health.test.ts +1 -21
  23. package/src/workflows/shared/health.ts +4 -39
  24. package/src/workflows/shared/options.ts +0 -1
  25. package/src/workflows/shared/steps.ts +16 -11
  26. package/src/workflows/shared/tail.test.ts +1 -41
  27. package/src/workflows/shared/tail.ts +14 -30
  28. package/src/workflows/shared/text.fixture.ts +1 -0
  29. package/src/workflows/shared/text.test.ts +23 -41
  30. package/src/workflows/shared/text.ts +54 -74
  31. package/src/workflows/shared/vars.ts +0 -1
  32. package/src/workflows/shared/scenarios.test.ts +0 -256
  33. package/src/workflows/shared/scenarios.ts +0 -35
@@ -1,6 +1,6 @@
1
1
  import { answered, changes, changesSince, glob, hasThreadFor, head, human, judge, numeric, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restoreScript, revertScript, run, scope, start, vars } from "../flows.mjs";
2
- import { freezeScenarios, review as review$1 } from "../workflow-kit.mjs";
3
- import { $ as startGateBlockedMessage, A as architectureAuthorPrompt, B as designGateAnswerMessage, C as fixCheck, D as reviewing, E as reviewMissing, F as buildReviewCollectingPrompt, G as idleMessage, H as designTriagePrompt, I as buildReviewFixNitsPrompt, J as reviewEditNotesCapture, K as packagesItemBuildingPrompt, L as buildReviewTriageMessage, M as architectureGateAnswerMessage, N as buildReviewAnswerQuestionsPrompt, O as agentWithSkills, P as buildReviewAwaitAfterDeferMessage, Q as reviewerSystem, R as builderSystem, S as coder, T as planner, V as designSystem, Y as reviewEditsCapture, Z as reviewNotesCapture, _ as ARCHITECTURE, b as REVIEW, c as refusePendingHandOff, d as shipped, et as unwindFailedMessage, f as defaults, g as healthy, h as gate, i as skills, j as architectureDecomposePrompt, k as architectSystem, l as tail, n as summary, o as closeReview, p as envDefaults, q as packagesItemFixSuitePrompt, r as access, t as steering, tt as unwindFailure, u as guarded, v as FEEDBACK, w as fixRisks, x as awaitReview, y as REQUIREMENTS, z as deferNotesScript } from "../chunks/reserved-B9pG93Fc.mjs";
2
+ import { review as review$1 } from "../workflow-kit.mjs";
3
+ import { $ as unwindFailedMessage, A as architectureDecomposePrompt, B as designSystem, C as fixRisks, D as agentWithSkills, E as reviewing, F as buildReviewFixNitsPrompt, G as packagesItemBuildingPrompt, I as buildReviewTriageMessage, J as reviewEditsCapture, K as packagesItemFixSuitePrompt, L as builderSystem, M as buildReviewAnswerQuestionsPrompt, N as buildReviewAwaitAfterDeferMessage, O as architectSystem, P as buildReviewCollectingPrompt, Q as startGateBlockedMessage, R as deferNotesScript, S as fixCheck, T as reviewMissing, V as designTriagePrompt, W as idleMessage, X as reviewNotesCapture, Z as reviewerSystem, _ as FEEDBACK, b as awaitReview, c as refusePendingHandOff, d as defaults, et as unwindFailure, f as envDefaults, g as ARCHITECTURE, h as healthy, i as skills, j as architectureGateAnswerMessage, k as architectureAuthorPrompt, l as tail, m as gate, n as summary, o as closeReview, q as reviewEditNotesCapture, r as access, t as steering, u as shipped, v as REQUIREMENTS, w as planner, x as coder, y as REVIEW, z as designGateAnswerMessage } from "../chunks/reserved-6pAgFGgB.mjs";
4
4
  //#region src/workflows/feature/steps.ts
5
5
  /** Turn the sketch since `base` into `.gtd/REQUIREMENTS.md`. */
6
6
  const triage = (base) => agentWithSkills("triage", designTriagePrompt(base), {
@@ -78,8 +78,6 @@ const deferNotes = (notes, round) => run("review.defer", deferNotesScript(notes,
78
78
  });
79
79
  //#endregion
80
80
  //#region src/workflows/feature/packages.ts
81
- /** Package 0: the e2e scenarios, written red. */
82
- const SCENARIO_PACKAGE = ".gtd/packages/00-e2e-scenarios.md";
83
81
  const TEST_LINE = /^\s*-\s+(unit|e2e):\s+`([^`]+)`/;
84
82
  /** The `## Tests` entries of a package file; malformed lines are ignored. */
85
83
  const declaredTests = (packageText) => {
@@ -109,47 +107,29 @@ const requireDeclaredTests = (pkg, since) => {
109
107
  if (missing.length === 0) return;
110
108
  refuse(`gtd land: declared-tests: ${pkg} declares tests that are ${satisfied ? "missing from the tree" : "not in the package's diff"} — write them first:\n${missing.map((path) => ` - ${path}`).join("\n")}`);
111
109
  };
112
- /** Build `pkg`, check its declared tests, keep the fast suite green, and close it out, which removes it. */
113
- const packageItem = async (pkg, frozen) => {
110
+ /**
111
+ * Build `pkg`, check its declared tests, keep the full suite green, and close
112
+ * it out, which removes it — and `.gtd/ARCHITECTURE.md` with the last one.
113
+ */
114
+ const packageItem = async (pkg) => {
114
115
  const from = head();
115
116
  const declared = (turn) => async () => {
116
117
  await turn();
117
118
  requireDeclaredTests(pkg, from);
118
119
  };
119
- await guarded(frozen, declared(() => build(pkg)))();
120
- await healthy(guarded(frozen, declared(fixSuite)), { suite: "fast" });
121
- await run("closing", removeScript([pkg, ".gtd/SATISFIED.md"]), { label: "Closing out the package" });
122
- return {
120
+ await declared(() => build(pkg))();
121
+ await healthy(declared(fixSuite));
122
+ await run("closing", removeScript([
123
123
  pkg,
124
- from,
125
- to: head()
126
- };
124
+ ".gtd/SATISFIED.md",
125
+ ...glob(".gtd/packages/*.md").length === 1 ? [ARCHITECTURE] : []
126
+ ]), { label: "Closing out the package" });
127
127
  };
128
128
  /** The first queued package — the one a package step works on. */
129
129
  const nextPackage = () => [...glob(".gtd/packages/*.md")].sort()[0];
130
- const outsideGtd = (path) => path !== ".gtd" && !path.startsWith(".gtd/");
131
130
  /** Build every package file under `.gtd/packages/`, in name order. */
132
- const packages = (carried) => scope("packages", async () => {
133
- let frozen = carried;
134
- const ranges = [];
135
- let touched = [];
136
- for (let pkg = nextPackage(); pkg !== void 0; pkg = nextPackage()) {
137
- const range = await scope("item", () => packageItem(pkg, pkg === ".gtd/packages/00-e2e-scenarios.md" ? void 0 : frozen));
138
- ranges.push(range);
139
- if (pkg === ".gtd/packages/00-e2e-scenarios.md") {
140
- touched = changesSince(range.from).filter((c) => outsideGtd(c.path) && c.status !== "deleted");
141
- frozen = freezeScenarios(touched.filter((c) => c.path.endsWith(".feature")).map((c) => c.path), frozen);
142
- }
143
- }
144
- const paths = (status) => touched.filter((c) => c.status === status).map((c) => c.path);
145
- return {
146
- ranges,
147
- scenarios: {
148
- added: paths("added"),
149
- changed: paths("modified")
150
- },
151
- frozen
152
- };
131
+ const packages = () => scope("packages", async () => {
132
+ for (let pkg = nextPackage(); pkg !== void 0; pkg = nextPackage()) await scope("item", () => packageItem(pkg));
153
133
  });
154
134
  //#endregion
155
135
  //#region src/workflows/feature/planning.ts
@@ -182,7 +162,7 @@ const architecture = () => scope("architecture", async () => {
182
162
  } while (await noteGate(ARCHITECTURE, answerTechnicalQuestions));
183
163
  await decompose();
184
164
  if (changes(".gtd/packages/**").length === 0) refuse("gtd land: decompose: write at least one package under .gtd/packages/");
185
- if (changes(".gtd/ARCHITECTURE.md").some((c) => c.status === "deleted")) refuse("gtd land: decompose: keep .gtd/ARCHITECTURE.md — the build tail's full run needs it; it is removed only once that run is green");
165
+ if (changes(".gtd/ARCHITECTURE.md").some((c) => c.status === "deleted")) refuse("gtd land: decompose: keep .gtd/ARCHITECTURE.md — the packages are built from it; it is removed with the last package");
186
166
  });
187
167
  //#endregion
188
168
  //#region src/workflows/feature/review.ts
@@ -308,7 +288,7 @@ const isCode = (path) => path !== ".gtd" && !path.startsWith(".gtd/");
308
288
  * another planning lap, questions are answered in place, nits fixed in one
309
289
  * batch, praise dropped.
310
290
  */
311
- const review = (base, escalations = { rounds: 0 }, frozen) => {
291
+ const review = (base, escalations = { rounds: 0 }) => {
312
292
  const severities = /* @__PURE__ */ new Map();
313
293
  let deferred = [];
314
294
  return review$1(base, {
@@ -319,8 +299,8 @@ const review = (base, escalations = { rounds: 0 }, frozen) => {
319
299
  deferred = [];
320
300
  return reviewing(b, carry);
321
301
  },
322
- fixRisks: (risks) => guarded(frozen, () => fixRisks(risks), "review")(),
323
- keepGreen: () => healthy(guarded(frozen, fixCheck, "review"), { escalations }),
302
+ fixRisks,
303
+ keepGreen: () => healthy(fixCheck, { escalations }),
324
304
  awaitReview: (b) => {
325
305
  const notes = deferred;
326
306
  deferred = [];
@@ -339,7 +319,7 @@ const review = (base, escalations = { rounds: 0 }, frozen) => {
339
319
  return deferNotes(deferred, head());
340
320
  },
341
321
  answerQuestions: answerReviewQuestions,
342
- fixNits: (notes) => guarded(frozen, () => fixNits(notes), "review")(),
322
+ fixNits,
343
323
  close: closeReview,
344
324
  captures: {
345
325
  edits: reviewEditsCapture,
@@ -361,8 +341,8 @@ const review = (base, escalations = { rounds: 0 }, frozen) => {
361
341
  }
362
342
  });
363
343
  };
364
- /** The build tail: fix (when entered red), keep green, the quality lap, then human review since `base`. */
365
- const buildTail = (fixFirst, base, built) => tail(fixFirst, (escalations, frozen) => review(base, escalations, frozen), built);
344
+ /** The build tail: fix (when entered red), keep green unless `green`, the quality lap, then human review since `base`. */
345
+ const buildTail = (fixFirst, base, green = false) => tail(fixFirst, (escalations) => review(base, escalations), green);
366
346
  //#endregion
367
347
  //#region src/workflows/feature/feature.ts
368
348
  /** Revert the sketch that started the process out of the working tree; its intent survives in history. */
@@ -389,13 +369,11 @@ const reUnwind = async (feedback) => {
389
369
  /** Plan, build and review until a review round signs off; feedback re-plans from scratch. */
390
370
  const planAndBuild = async (firstBase) => {
391
371
  let base = firstBase;
392
- let carried;
393
372
  for (;;) {
394
373
  await design(base);
395
374
  await architecture();
396
- const built = await packages(carried);
397
- carried = built.frozen;
398
- const outcome = await buildTail(false, base, built);
375
+ await packages();
376
+ const outcome = await buildTail(false, base, true);
399
377
  if (outcome.verdict === "signoff") return;
400
378
  await reUnwind(outcome);
401
379
  base = outcome.base;
@@ -429,4 +407,4 @@ const feature = shipped(ordinaryStart);
429
407
  //#region src/workflows/feature/index.ts
430
408
  var feature_default = feature;
431
409
  //#endregion
432
- export { SCENARIO_PACKAGE, access, afterTail, answerProductQuestions, answerReviewQuestions, answerTechnicalQuestions, architecture, author, build, buildTail, collecting, declaredTests, decompose, feature_default as default, defaults, deferNotes, design, envDefaults, feature, fixNits, fixSuite, nextPackage, ordinaryStart, packageItem, packages, planAndBuild, reUnwind, requireDeclaredTests, skills, steering, summary, triage, unwind };
410
+ export { access, afterTail, answerProductQuestions, answerReviewQuestions, answerTechnicalQuestions, architecture, author, build, buildTail, collecting, declaredTests, decompose, feature_default as default, defaults, deferNotes, design, envDefaults, feature, fixNits, fixSuite, nextPackage, ordinaryStart, packageItem, packages, planAndBuild, reUnwind, requireDeclaredTests, skills, steering, summary, triage, unwind };
@@ -1,6 +1,6 @@
1
1
  import { read, refuse, removeScript, run, scope, start } from "../flows.mjs";
2
2
  import { d as reviewNotes, f as reviewRisks } from "../chunks/steering-DVILanVf.mjs";
3
- import { O as agentWithSkills, S as coder, U as finisherSystem, W as fixReviewNotesPrompt, a as REVIEW_ORIG, b as REVIEW, d as shipped, f as defaults, i as skills, m as baseline, n as summary, p as envDefaults, r as access, s as handOffTail, t as steering } from "../chunks/reserved-B9pG93Fc.mjs";
3
+ import { D as agentWithSkills, H as finisherSystem, U as fixReviewNotesPrompt, a as REVIEW_ORIG, d as defaults, f as envDefaults, i as skills, n as summary, p as baseline, r as access, s as handOffTail, t as steering, u as shipped, x as coder, y as REVIEW } from "../chunks/reserved-6pAgFGgB.mjs";
4
4
  //#region src/workflows/fix/fix.ts
5
5
  /**
6
6
  * Fix every note a human left in a handed-off `.gtd/REVIEW.md` and every
@@ -1,5 +1,5 @@
1
1
  import { start } from "../flows.mjs";
2
- import { X as reviewGateBlockedMessage, c as refusePendingHandOff, d as shipped, f as defaults, h as gate, i as skills, n as summary, p as envDefaults, r as access, s as handOffTail, t as steering } from "../chunks/reserved-B9pG93Fc.mjs";
2
+ import { Y as reviewGateBlockedMessage, c as refusePendingHandOff, d as defaults, f as envDefaults, i as skills, m as gate, n as summary, r as access, s as handOffTail, t as steering, u as shipped } from "../chunks/reserved-6pAgFGgB.mjs";
3
3
  //#region src/workflows/review/review.ts
4
4
  /**
5
5
  * Pure review of everything since `reviewBase`: a green gate, the quality