@pmelab/gtd 45.2.0 → 46.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.
@@ -17197,11 +17197,6 @@ const testFileGlobs = [
17197
17197
  "**/__tests__/**"
17198
17198
  ];
17199
17199
  const defaultFlagRegistry = "src/flags/index.ts";
17200
- /** The `tests` workflow's own process settings: where it looks for skipped tests, and how a skip reads. */
17201
- const testsDefaults = {
17202
- testFiles: testFileGlobs.join(", "),
17203
- skipPattern: "^\\s*(?:(?:describe|context|suite|it|test|specify)\\.(?:skip|todo)\\b|x(?:describe|context|it|test|specify)\\s*\\(|(?:@\\S+\\s+)*@(?:skip|ignore)(?:\\s|$))"
17204
- };
17205
17200
  /** Process settings. */
17206
17201
  const defaults = {
17207
17202
  reviewBase: "",
@@ -17534,7 +17529,7 @@ const composedReview = (base, report) => {
17534
17529
  return name;
17535
17530
  };
17536
17531
  const chunk = (title, body) => `## ${title}\n\n${body.trim()}`;
17537
- const answerBoxes = ([fix, wontFix] = [ANSWER_FIX, ANSWER_WONT_FIX]) => `${ANSWER_LEAD}\n\n- [ ] ${fix}\n- [ ] ${wontFix}\n- [ ] ${FREE_TEXT_PLACEHOLDER}`;
17532
+ const answerBoxes = `${ANSWER_LEAD}\n\n- [ ] ${ANSWER_FIX}\n- [ ] ${ANSWER_WONT_FIX}\n- [ ] ${FREE_TEXT_PLACEHOLDER}`;
17538
17533
  const conflicts = (report.standards?.undecided ?? []).map((c) => chunk(unique(conflictTitle(c)), conflictBody(c)));
17539
17534
  const added = report.standards?.added ?? [];
17540
17535
  const notes = report.notes.map((n) => ({
@@ -17546,7 +17541,7 @@ const composedReview = (base, report) => {
17546
17541
  ...report.nitFix === void 0 ? [] : [chunk("Fixed from your notes", `Your nits and fix-as-suggested notes were worked on in ${short(report.nitFix)}; the notes you answered are left out — check the diff.`)],
17547
17542
  ...weakened.length === 0 ? [] : [chunk("Weakened scenarios", `These scenarios lost an assertion the review base had:\n\n${weakened.join("\n")}`)],
17548
17543
  ...conflicts,
17549
- ...notes.map((n) => chunk(n.title, `${n.unresolved === true ? `_The fixer left this unresolved._\n\n${unboxed(n.body)}` : n.unclear === true ? `_A fix turn removed this while renaming another finding — check whether it was fixed._\n\n${unboxed(n.body)}` : unboxed(n.body) || "_No detail given._"}\n\n${answerBoxes(n.answers)}`)),
17544
+ ...notes.map((n) => chunk(n.title, `${n.unresolved === true ? `_The fixer left this unresolved._\n\n${unboxed(n.body)}` : n.unclear === true ? `_A fix turn removed this while renaming another finding — check whether it was fixed._\n\n${unboxed(n.body)}` : unboxed(n.body) || "_No detail given._"}\n\n${answerBoxes}`)),
17550
17545
  ...report.fixed.length === 0 ? [] : [chunk("Fixed automatically", report.fixed.map((f) => `- ${f.title} (${short(f.commit)})`).join("\n"))],
17551
17546
  ...added.length === 0 ? [] : [chunk("Standards added", `Learned from your answers and kept in \`${STANDARDS}\`:\n\n${added.map((s) => `- ${s}`).join("\n")}`)],
17552
17547
  ...report.noise.length === 0 ? [] : [chunk("Dropped as noise", report.noise.map((f) => `- ${f.title} (p=${f.p})`).join("\n"))]
@@ -38233,24 +38228,6 @@ const flagShipped = (flow, name) => defineWorkflow(standardSchemaV1(FlagOptions,
38233
38228
  base: void 0
38234
38229
  };
38235
38230
  });
38236
- const TestsOptions = Struct({
38237
- ...ShippedOptions.fields,
38238
- testFiles: setting$1,
38239
- skipPattern: setting$1
38240
- });
38241
- const testsShipped = (flow) => defineWorkflow(standardSchemaV1(TestsOptions, {
38242
- onExcessProperty: "error",
38243
- errors: "all"
38244
- }), ({ contextLimit, ...options }) => ({
38245
- flow: limited(flow, contextLimit),
38246
- defaults: over({
38247
- ...defaults,
38248
- ...testsDefaults
38249
- }, options),
38250
- envDefaults: over(envDefaults, options),
38251
- doors: { tests: {} },
38252
- base: void 0
38253
- }));
38254
38231
  //#endregion
38255
38232
  //#region src/workflows/shared/lenses.ts
38256
38233
  const NO_FINDINGS = {
@@ -38259,7 +38236,7 @@ const NO_FINDINGS = {
38259
38236
  noise: []
38260
38237
  };
38261
38238
  const UNTITLED = "Quality findings";
38262
- const below$1 = (section) => section.split("\n").slice(1).join("\n").trim();
38239
+ const below = (section) => section.split("\n").slice(1).join("\n").trim();
38263
38240
  const strayProse = (text, sections) => sections.reduce((rest, body) => rest.replace(body, "\n"), text).split("\n").filter((line) => !/^#\s/.test(line)).join("\n").trim();
38264
38241
  /** The findings in `text`, one per `## ` section, in file order, numbered `Q<n>`. */
38265
38242
  const findingsOf = (text) => {
@@ -38270,7 +38247,7 @@ const findingsOf = (text) => {
38270
38247
  body: stray
38271
38248
  }], ...sections.map((s) => ({
38272
38249
  title: s.title,
38273
- body: below$1(s.body)
38250
+ body: below(s.body)
38274
38251
  }))].map((f, i) => ({
38275
38252
  id: `Q${i + 1}`,
38276
38253
  ...f
@@ -39048,7 +39025,7 @@ const composing = (base, report, carry, nitFix, answered = /* @__PURE__ */ new S
39048
39025
  * praise dropped.
39049
39026
  */
39050
39027
  const humanReview = (base, escalations = { rounds: 0 }, options = {}) => {
39051
- const { guard, guardRests, fixAccess, rules, awaitMessage } = options;
39028
+ const { guard, guardRests, fixAccess, rules } = options;
39052
39029
  const feedback = options.feedback ?? {
39053
39030
  file: ".gtd/REQUIREMENTS.md",
39054
39031
  collecting,
@@ -39073,7 +39050,7 @@ const humanReview = (base, escalations = { rounds: 0 }, options = {}) => {
39073
39050
  guardRests,
39074
39051
  rules
39075
39052
  }),
39076
- awaitReview: (b) => awaitReview(b, awaitMessage?.(b)),
39053
+ awaitReview,
39077
39054
  reviewMissing,
39078
39055
  collecting: feedback.collecting,
39079
39056
  triage: triage$1(severities),
@@ -39133,17 +39110,6 @@ const callForm = (first, second, [third, fourth, ...extra]) => {
39133
39110
  if (typeof second === "string") return legacyForm(first, second, third, fourth);
39134
39111
  if (typeof first === "string") return currentForm(first, second, third, fourth);
39135
39112
  };
39136
- /** Build the concerns a review round collected into `.gtd/REQUIREMENTS.md`, then remove it. */
39137
- const fixFeedback = async () => {
39138
- await agentWithSkills("feedback.fixing", reviewFeedbackFixPrompt(), {
39139
- label: "Fixing your review feedback",
39140
- file: REQUIREMENTS,
39141
- model: coder(),
39142
- system: finisherSystem(),
39143
- allowEmpty: true
39144
- });
39145
- await run("feedback.closing", removeScript([REQUIREMENTS]), { label: "Closing the feedback" });
39146
- };
39147
39113
  /**
39148
39114
  * Undo the human's review-round code edits. A path changed since the human
39149
39115
  * left it is not overwritten; requireRevert names it instead.
@@ -40157,8 +40123,6 @@ const skills = (vars) => {
40157
40123
  design: ["spec-driven-development", "planning-and-task-breakdown"],
40158
40124
  notes: ["incremental-implementation", "code-simplification"],
40159
40125
  feedback: ["test-driven-development", "incremental-implementation"],
40160
- tests: ["test-audit", "test-driven-development"],
40161
- "tests.health": ["debugging-and-error-recovery"],
40162
40126
  ...build,
40163
40127
  ...Object.fromEntries(Object.entries(build).map(([name, list]) => [`refactor.${name}`, list])),
40164
40128
  refactor: ["ponytail"],
@@ -40192,6 +40156,16 @@ const steering = {
40192
40156
  };
40193
40157
  //#endregion
40194
40158
  //#region src/workflows/review/review.ts
40159
+ const fixFeedback = async () => {
40160
+ await agentWithSkills("feedback.fixing", reviewFeedbackFixPrompt(), {
40161
+ label: "Fixing your review feedback",
40162
+ file: REQUIREMENTS,
40163
+ model: coder(),
40164
+ system: finisherSystem(),
40165
+ allowEmpty: true
40166
+ });
40167
+ await run("feedback.closing", removeScript([REQUIREMENTS]), { label: "Closing the feedback" });
40168
+ };
40195
40169
  /**
40196
40170
  * The feature's review of everything since `reviewBase` — a green gate, the
40197
40171
  * triaged quality lap, then the human review — with no design phase: a
@@ -40325,191 +40299,6 @@ const cleanupFlow = async () => {
40325
40299
  };
40326
40300
  const cleanup = flagShipped(cleanupFlow, "cleanup");
40327
40301
  //#endregion
40328
- //#region src/workflows/tests/tests.ts
40329
- const TESTS = ".gtd/TESTS.md";
40330
- const RETIRE = ".gtd/RETIRE.md";
40331
- /** The first two answer boxes of a deletion proposal; the third stays the human's own answer. */
40332
- const RETIRE_ANSWERS = ["delete it", "keep it skipped"];
40333
- testsDefaults.skipPattern;
40334
- /** `skipPattern` as a per-line expression; refuses one that is blank, invalid, or matches every line. */
40335
- const skipExpression = (pattern) => {
40336
- if (pattern.trim() === "") return refuse("gtd: skipPattern must not be blank");
40337
- let expression;
40338
- try {
40339
- expression = new RegExp(pattern);
40340
- } catch (error) {
40341
- return refuse(`gtd: skipPattern is not a valid regular expression: ${String(error)}`);
40342
- }
40343
- if (expression.test("")) return refuse("gtd: skipPattern must not match the empty string");
40344
- return expression;
40345
- };
40346
- /** Every line of `files` the expression calls skipped, in file then line order. */
40347
- const skipsIn = (files, expression) => files.flatMap(({ path, text }) => text.split("\n").flatMap((line, i) => expression.test(line) ? [{
40348
- path,
40349
- line: i + 1,
40350
- text: line.trim()
40351
- }] : []));
40352
- const testPaths = () => {
40353
- const globs = (vars.testFiles ?? testsDefaults.testFiles).split(",").map((g) => g.trim()).filter((g) => g !== "");
40354
- return [...new Set(globs.flatMap((g) => glob(g)))].filter(isCode$1).sort();
40355
- };
40356
- const scan = (paths, expression) => skipsIn(paths.map((path) => ({
40357
- path,
40358
- text: read(path) ?? ""
40359
- })), expression);
40360
- /** A section title as a repository path: backticks and a leading `./` dropped. */
40361
- const pathOf = (title) => title.replace(/`/g, "").trim().replace(/^\.\//, "");
40362
- const below = (section) => section.split("\n").slice(1).join("\n").trim();
40363
- const sweepingPrompt = (skips) => `Sweep this repository's tests for ones to revive.
40364
-
40365
- Skipped tests gtd found:
40366
-
40367
- ${skips.length === 0 ? "- none" : skips.map((s) => `- ${s.path}:${s.line} — ${s.text}`).join("\n")}
40368
-
40369
- - Audit every test in the repository with the test-audit skill — the whole
40370
- suite, not a recent change
40371
- - Write \`${TESTS}\`: \`# Tests\`, then one \`## <path>\` section per test file
40372
- that needs work — every file listed above, and each file the audit found
40373
- worth rewriting — saying which tests are skipped and what the audit found
40374
- - Change nothing else; write nothing when there is nothing to do
40375
- - Leave everything uncommitted and finish your turn`;
40376
- const rewritingPrompt = (path, found) => `Rewrite the tests in \`${path}\` until none is skipped and the suite is green.
40377
-
40378
- What the sweep found:
40379
-
40380
- ${found || "_Nothing more than the skipped tests._"}
40381
-
40382
- - Unskip every skipped test in the file and make it pass, rewriting it where
40383
- it rotted, and fix what the audit found
40384
- - A test that is obsolete — what it tests is gone, or another test already
40385
- covers it — stays skipped: never delete it. Propose its deletion in
40386
- \`${RETIRE}\` instead: a \`## <title>\` section naming \`${path}\`, the test,
40387
- and why it should go. A person decides. Propose a line gtd misread as
40388
- skipped (fixture text, a comment) the same way, saying so
40389
- - Delete no test file
40390
- - Leave everything uncommitted and finish your turn`;
40391
- const gateMessage = () => `The test baseline is red — gtd will not revive skipped tests on a broken suite.
40392
- \`.gtd/FEEDBACK.md\` holds the failing output.
40393
-
40394
- What each change does next (then run \`gtd land\`):
40395
- - **Retry check** — edit the code and/or \`.gtd/FEEDBACK.md\` to fix the failing tests (**tests-gate.check**).
40396
- `;
40397
- const awaitMessage = (base) => `\`.gtd/REVIEW.md\` holds one chunk per test the sweep would delete. Each ends in
40398
- three boxes — tick exactly one: \`delete it\` (the test is deleted, then the
40399
- suite runs), \`keep it skipped\` (dropped, recorded in the commit), or write your
40400
- own answer over \`_your answer_\` and tick it (judged like a note). Every
40401
- landing here is refused until each proposal holds exactly one tick. An agent
40402
- turn that deleted a test file was refused; check the diff for tests removed
40403
- inside a file.
40404
-
40405
- Review the revived tests yourself:
40406
-
40407
- git diff ${base}
40408
-
40409
- Then run \`gtd land\`. A note or code edit of your own is read as feedback and
40410
- built before you review again.
40411
- `;
40412
- /** A proposal's own text, made a request the nit fixer acts on once the human ticks `delete it`. */
40413
- const proposals = (text) => sectionBodies(text).map((s, i) => ({
40414
- id: `R${i + 1}`,
40415
- title: s.title,
40416
- body: `Delete this skipped test.\n\n${below(s.body)}`,
40417
- answers: RETIRE_ANSWERS
40418
- }));
40419
- /** Sections of `text` that name `path` as a whole token — a trailing full stop still counts. */
40420
- const naming = (text, path) => {
40421
- const token = new RegExp(`(?<![\\w./-])${path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w/-])`);
40422
- return sectionBodies(text).filter((s) => token.test(s.body.replace(/(^|[^\w.])\.\//g, "$1"))).length;
40423
- };
40424
- /** Refuses a test file a turn deleted, and a skip left in the file being rewritten that `.gtd/RETIRE.md` does not name. */
40425
- const guardFor = (known, expression, path) => () => {
40426
- const deleted = changes().filter((c) => c.status === "deleted" && known.has(c.path)).map((c) => c.path);
40427
- if (deleted.length > 0) refuse(`gtd land: tests: ${deleted.join(", ")} deleted — a test goes only once a person ticks "delete it" at the review`);
40428
- if (path === void 0) return;
40429
- const left = scan([path], expression).slice(naming(read(RETIRE) ?? "", path));
40430
- if (left.length > 0) refuse(`gtd land: tests: ${left.map((s) => `${s.path}:${s.line}`).join(", ")} still skipped — revive it, or propose its deletion in ${RETIRE}`);
40431
- };
40432
- /** The sweep's packages, refused when it changed more than its plan or the plan misses a file. */
40433
- const planned = (skips, known) => {
40434
- const strays = changes().filter((c) => c.path !== TESTS);
40435
- if (strays.length > 0) refuse(`gtd land: tests: the sweep changed ${strays.map((c) => c.path).join(", ")} — it writes only ${TESTS}`);
40436
- const packages = sectionBodies(read(TESTS) ?? "").map((s) => ({
40437
- path: pathOf(s.title),
40438
- found: below(s.body)
40439
- }));
40440
- const missing = [...packages.filter((p) => !known.has(p.path)).map((p) => `"${p.path}" is no test file`), ...[...new Set(skips.map((s) => s.path))].filter((path) => !packages.some((p) => p.path === path)).map((path) => `${path} has no section`)];
40441
- if (missing.length > 0) refuse(`gtd land: tests: in ${TESTS}, ${missing.join("; ")}`);
40442
- return packages;
40443
- };
40444
- /**
40445
- * Revive or retire skipped tests: a sweep plans one package per test file, each
40446
- * is rewritten until unskipped and green, and what an agent calls obsolete
40447
- * reaches the human review as a deletion proposal only a person can tick.
40448
- */
40449
- const testsFlow = async () => {
40450
- refusePendingHandOff();
40451
- for (const file of [
40452
- REQUIREMENTS,
40453
- TESTS,
40454
- RETIRE
40455
- ]) if (read(file) !== void 0) refuse(`gtd: ${file} is left over — finish or remove it first.`);
40456
- const expression = skipExpression(vars.skipPattern ?? testsDefaults.skipPattern);
40457
- await gate("tests-gate", gateMessage());
40458
- const base = start();
40459
- const known = new Set(testPaths());
40460
- const guard = guardFor(known, expression);
40461
- const report = await scope("tests", async () => {
40462
- const skips = scan([...known], expression);
40463
- await agentWithSkills("sweeping", sweepingPrompt(skips), {
40464
- label: "Sweeping the tests",
40465
- file: TESTS,
40466
- model: coder(),
40467
- system: finisherSystem(),
40468
- allowEmpty: true
40469
- });
40470
- guard();
40471
- const packages = planned(skips, known);
40472
- for (const { path, found } of packages) {
40473
- const guarded = guardFor(known, expression, path);
40474
- await agentWithSkills("rewriting", rewritingPrompt(path, found), {
40475
- label: `Rewriting ${path}`,
40476
- file: RETIRE,
40477
- model: coder(),
40478
- system: finisherSystem(),
40479
- allowEmpty: true
40480
- });
40481
- guarded();
40482
- if (!changes().some((c) => isCode$1(c.path))) continue;
40483
- await healthy(() => fixCheck(), {
40484
- guard: guarded,
40485
- guardRests: false
40486
- });
40487
- }
40488
- const retired = proposals(read(RETIRE) ?? "");
40489
- if (read(TESTS) !== void 0 || read(RETIRE) !== void 0) await run("closing", removeScript([TESTS, RETIRE]), { label: "Closing the sweep" });
40490
- const changed = changesSince(base).paths.some(isCode$1);
40491
- return retired.length === 0 && !changed ? void 0 : {
40492
- notes: retired,
40493
- fixed: [],
40494
- noise: []
40495
- };
40496
- });
40497
- if (report === void 0) return;
40498
- let outcome = await tail((escalations) => humanReview(base, escalations, {
40499
- report,
40500
- awaitMessage: report.notes.length > 0 ? awaitMessage : void 0
40501
- }), {
40502
- lenses: [],
40503
- green: true
40504
- });
40505
- while (outcome.verdict === "feedback") {
40506
- await reUnwind(outcome);
40507
- await fixFeedback();
40508
- outcome = await tail((escalations) => humanReview(base, escalations), { lenses: [] });
40509
- }
40510
- };
40511
- const tests = testsShipped(testsFlow);
40512
- //#endregion
40513
40302
  //#region src/workflows/index.ts
40514
40303
  var workflows_default = feature;
40515
40304
  /**
@@ -40519,4 +40308,4 @@ var workflows_default = feature;
40519
40308
  */
40520
40309
  const doors = {};
40521
40310
  //#endregion
40522
- export { COMMENT, ESCALATION, FEEDBACK, FIX_CAP, FlagOptions, HANDOFF, QUALITY, REFACTOR, REFACTOR_NOTE, REFACTOR_PACKAGE, REQUIREMENTS, REVIEW, REVIEW_ORIG, SLICES, ShippedOptions, TestsOptions, acceptanceFault, access, afterTail, agentWithSkills, answerProductQuestions, answerReviewQuestions, awaitReview, base, baseline, buildFeature, buildTail, builtInLenses, cleanup, cleanupFlow, closeOut, closeReview, coder, collecting, criteriaOf, criterionIds, workflows_default as default, defaults, describeEscalation, design, doors, envDefaults, escalate, escalation, escalationExhausted, feature, fix, fixCheck, fixFlow, fixNits, fixQuality, fixQualityFindings, fixReviewNotes, fixRisks, gate, guarded, handOffReview, handOffTail, healthy, holdsCallSites, implement, implementing, isTestFile, judgeSeam, lensEntriesOf, lensOf, lensesOf, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, planner, qualityLap, reUnwind, recheck, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireCriteriaIfEdited, requireNoNewFlags, requireNoTestFiles, requireSlices, requireStableIds, requireTestCallSitesOnly, requireWithin, retryQuestion, review, reviewFlow, reviewMissing, reviewQuality, reviewedRefactor, reviewing, rollout, rolloutFlow, seamBuild, skills, skipDesign, sliceCap, sliceCriteria, sliceIds, slicesOf, slicing, statedCriteria, steering, summary, tail, tests, testsFlow, threads, triage, unstableIds, unwind };
40311
+ export { COMMENT, ESCALATION, FEEDBACK, FIX_CAP, FlagOptions, HANDOFF, QUALITY, REFACTOR, REFACTOR_NOTE, REFACTOR_PACKAGE, REQUIREMENTS, REVIEW, REVIEW_ORIG, SLICES, ShippedOptions, acceptanceFault, access, afterTail, agentWithSkills, answerProductQuestions, answerReviewQuestions, awaitReview, base, baseline, buildFeature, buildTail, builtInLenses, cleanup, cleanupFlow, closeOut, closeReview, coder, collecting, criteriaOf, criterionIds, workflows_default as default, defaults, describeEscalation, design, doors, envDefaults, escalate, escalation, escalationExhausted, feature, fix, fixCheck, fixFlow, fixNits, fixQuality, fixQualityFindings, fixReviewNotes, fixRisks, gate, guarded, handOffReview, handOffTail, healthy, holdsCallSites, implement, implementing, isTestFile, judgeSeam, lensEntriesOf, lensOf, lensesOf, naiveAcceptance, naivePass, ordinaryStart, planAndBuild, planner, qualityLap, reUnwind, recheck, refactorCollecting, refactorFirst, refactorNote, refactorStrays, requireAcceptance, requireAcceptanceKept, requireCriteria, requireCriteriaFrozen, requireCriteriaIfEdited, requireNoNewFlags, requireNoTestFiles, requireSlices, requireStableIds, requireTestCallSitesOnly, requireWithin, retryQuestion, review, reviewFlow, reviewMissing, reviewQuality, reviewedRefactor, reviewing, rollout, rolloutFlow, seamBuild, skills, skipDesign, sliceCap, sliceCriteria, sliceIds, slicesOf, slicing, statedCriteria, steering, summary, tail, threads, triage, unstableIds, unwind };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pmelab/gtd",
3
- "version": "45.2.0",
3
+ "version": "46.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": {
@@ -62,11 +62,6 @@
62
62
  "source": "./src/workflows/cleanup/index.ts",
63
63
  "default": "./dist/workflows/cleanup.mjs"
64
64
  },
65
- "./workflows/tests": {
66
- "types": "./src/workflows/tests/index.ts",
67
- "source": "./src/workflows/tests/index.ts",
68
- "default": "./dist/workflows/tests.mjs"
69
- },
70
65
  "./testkit": {
71
66
  "types": "./src/testkit/index.ts",
72
67
  "source": "./src/testkit/index.ts",
@@ -6,8 +6,7 @@ export const CONFIG_SCAFFOLD = `// The workflows gtd runs here: gtd's shipped fe
6
6
  // its own doors: \`gtd door review [base]\` and \`gtd door fix\`. Export \`rollout\`
7
7
  // from \`@pmelab/gtd/workflows/rollout\` and \`cleanup\` from
8
8
  // \`@pmelab/gtd/workflows/cleanup\` to add \`gtd door rollout <flag>\` and
9
- // \`gtd door cleanup <flag>\`, and \`tests\` from \`@pmelab/gtd/workflows/tests\`
10
- // to add \`gtd door tests\`.
9
+ // \`gtd door cleanup <flag>\`.
11
10
  export {
12
11
  feature as default,
13
12
  feature,
@@ -516,11 +516,10 @@ rather than starting the review; \`gtd-door fix\` on a GREEN suite is a no-op
516
516
  straight back to \`idle\` — the exec'd \`gtd-build\` exits immediately on it.
517
517
  \`gtd doors\` lists the doors a repository offers (those of the workflows its
518
518
  config exports — the bundled \`fix\` and \`review [base]\`, plus the opt-in
519
- \`rollout <flag>\` (turn a release flag default-on), \`cleanup <flag>\`
520
- (remove it) and \`tests\` (revive skipped tests), which a config adds by
521
- exporting \`rollout\` from \`@pmelab/gtd/workflows/rollout\`, \`cleanup\` from
522
- \`@pmelab/gtd/workflows/cleanup\` and \`tests\` from
523
- \`@pmelab/gtd/workflows/tests\` — plus any it declares). Refuses with
519
+ \`rollout <flag>\` (turn a release flag default-on) and \`cleanup <flag>\`
520
+ (remove it), which a config adds by exporting \`rollout\` from
521
+ \`@pmelab/gtd/workflows/rollout\` and \`cleanup\` from
522
+ \`@pmelab/gtd/workflows/cleanup\` — plus any it declares). Refuses with
524
523
  \`usage: gtd-door <name> [args...]\` on stderr and exit \`2\` when the name is
525
524
  missing — nothing was even attempted. Default path: \`~/.local/bin/gtd-door\`.
526
525
 
@@ -5,7 +5,7 @@ import { feature } from "./feature/feature.js"
5
5
 
6
6
  export { threads, type ThreadInfo } from "../flows/index.js"
7
7
  export * from "./shared/reserved.js"
8
- export { FlagOptions, ShippedOptions, TestsOptions } from "./shared/options.js"
8
+ export { FlagOptions, ShippedOptions } from "./shared/options.js"
9
9
  export { agentWithSkills, type ReviewRange } from "./shared/text.js"
10
10
  export * from "./shared/steps.js"
11
11
  export * from "./shared/health.js"
@@ -38,7 +38,6 @@ export { base, review, reviewFlow } from "./review/review.js"
38
38
  export { fix, fixFlow, fixReviewNotes } from "./fix/fix.js"
39
39
  export { rollout, rolloutFlow } from "./rollout/rollout.js"
40
40
  export { cleanup, cleanupFlow } from "./cleanup/cleanup.js"
41
- export { tests, testsFlow } from "./tests/tests.js"
42
41
 
43
42
  export default feature
44
43
 
@@ -1,11 +1,23 @@
1
- import { read, refuse, start, type WorkflowBase } from "../../flows/index.js"
2
- import { buildTail, fixFeedback, reUnwind } from "../shared/review.js"
1
+ import { read, refuse, removeScript, run, start, type WorkflowBase } from "../../flows/index.js"
2
+ import { buildTail, reUnwind } from "../shared/review.js"
3
3
  import { gate } from "../shared/health.js"
4
4
  import { shipped } from "../shared/options.js"
5
- import { REQUIREMENTS } from "../shared/steps.js"
5
+ import { coder, REQUIREMENTS } from "../shared/steps.js"
6
6
  import { refusePendingHandOff } from "../shared/tail.js"
7
7
  import * as t from "../shared/text.js"
8
8
 
9
+ const fixFeedback = async (): Promise<void> => {
10
+ await t.agentWithSkills("feedback.fixing", t.reviewFeedbackFixPrompt(), {
11
+ label: "Fixing your review feedback",
12
+ file: REQUIREMENTS,
13
+ model: coder(),
14
+ system: t.finisherSystem(),
15
+ // An earlier nit fix may already have built the concern.
16
+ allowEmpty: true,
17
+ })
18
+ await run("feedback.closing", removeScript([REQUIREMENTS]), { label: "Closing the feedback" })
19
+ }
20
+
9
21
  /**
10
22
  * The feature's review of everything since `reviewBase` — a green gate, the
11
23
  * triaged quality lap, then the human review — with no design phase: a
@@ -29,11 +29,7 @@ type FindingKind = "mechanical" | "noise" | "design"
29
29
  /** What the triaged quality lap did with its findings, for the review to list. */
30
30
  export interface LensReport {
31
31
  /** Design findings, then the mechanical ones the fix cap left. */
32
- readonly notes: readonly (Finding & {
33
- readonly unresolved?: true
34
- readonly unclear?: true
35
- readonly answers?: readonly [string, string]
36
- })[]
32
+ readonly notes: readonly (Finding & { readonly unresolved?: true; readonly unclear?: true })[]
37
33
  readonly fixed: readonly (Finding & { readonly commit: string })[]
38
34
  readonly noise: readonly (Finding & { readonly p: number })[]
39
35
  }
@@ -9,7 +9,7 @@ import {
9
9
  scope,
10
10
  vars,
11
11
  } from "../../flows/index.js"
12
- import { defaultFlagRegistry, defaults, envDefaults, testsDefaults } from "./vars.js"
12
+ import { defaultFlagRegistry, defaults, envDefaults } from "./vars.js"
13
13
 
14
14
  const setting = Schema.optionalWith(Schema.String, { exact: true })
15
15
 
@@ -107,23 +107,3 @@ export const flagShipped = (
107
107
  }
108
108
  },
109
109
  )
110
-
111
- export const TestsOptions = Schema.Struct({
112
- ...ShippedOptions.fields,
113
- testFiles: setting,
114
- skipPattern: setting,
115
- })
116
-
117
- export type TestsOptions = Schema.Schema.Encoded<typeof TestsOptions>
118
-
119
- export const testsShipped = (flow: Flow): WorkflowFactory<TestsOptions> =>
120
- defineWorkflow(
121
- Schema.standardSchemaV1(TestsOptions, { onExcessProperty: "error", errors: "all" }),
122
- ({ contextLimit, ...options }) => ({
123
- flow: limited(flow, contextLimit),
124
- defaults: over({ ...defaults, ...testsDefaults }, options),
125
- envDefaults: over(envDefaults, options),
126
- doors: { tests: {} },
127
- base: undefined,
128
- }),
129
- )
@@ -5,7 +5,6 @@ import {
5
5
  judge,
6
6
  numeric,
7
7
  read,
8
- removeScript,
9
8
  requireRevert,
10
9
  restoreScript,
11
10
  run,
@@ -23,7 +22,6 @@ import { NO_FINDINGS, type LensReport } from "./lenses.js"
23
22
  import {
24
23
  answerReviewQuestions,
25
24
  awaitReview,
26
- coder,
27
25
  collecting,
28
26
  fixCheck,
29
27
  fixNits,
@@ -220,7 +218,7 @@ export const humanReview = (
220
218
  escalations: EscalationCount = { rounds: 0 },
221
219
  options: Omit<BuildTailOptions, "fixFirst" | "green"> = {},
222
220
  ): Promise<kit.ReviewOutcome> => {
223
- const { guard, guardRests, fixAccess, rules, awaitMessage } = options
221
+ const { guard, guardRests, fixAccess, rules } = options
224
222
  const feedback: FeedbackTarget = options.feedback ?? {
225
223
  file: REQUIREMENTS,
226
224
  collecting,
@@ -243,7 +241,7 @@ export const humanReview = (
243
241
  },
244
242
  fixRisks: guarded((notes) => fixRisks(notes, fixAccess, rules), guard),
245
243
  keepGreen: () => healthy(() => fixCheck(rules), { escalations, guard, guardRests, rules }),
246
- awaitReview: (b) => awaitReview(b, awaitMessage?.(b)),
244
+ awaitReview,
247
245
  reviewMissing,
248
246
  collecting: feedback.collecting,
249
247
  triage: triage(severities),
@@ -305,8 +303,6 @@ export interface BuildTailOptions extends TailOptions {
305
303
  readonly lap?: t.ReviewRange | undefined
306
304
  /** What the triaged quality lap left for the review; absent: nothing. */
307
305
  readonly report?: LensReport | undefined
308
- /** The review gate's message for the review base; absent: the quality lap's. */
309
- readonly awaitMessage?: ((base: string) => string) | undefined
310
306
  }
311
307
 
312
308
  /** The build tail: fix (when entered red), keep green unless `green`, the triaged quality lap, then human review since `base`. */
@@ -372,19 +368,6 @@ const callForm = (
372
368
  return undefined
373
369
  }
374
370
 
375
- /** Build the concerns a review round collected into `.gtd/REQUIREMENTS.md`, then remove it. */
376
- export const fixFeedback = async (): Promise<void> => {
377
- await t.agentWithSkills("feedback.fixing", t.reviewFeedbackFixPrompt(), {
378
- label: "Fixing your review feedback",
379
- file: REQUIREMENTS,
380
- model: coder(),
381
- system: t.finisherSystem(),
382
- // An earlier nit fix may already have built the concern.
383
- allowEmpty: true,
384
- })
385
- await run("feedback.closing", removeScript([REQUIREMENTS]), { label: "Closing the feedback" })
386
- }
387
-
388
371
  /**
389
372
  * Undo the human's review-round code edits. A path changed since the human
390
373
  * left it is not overwritten; requireRevert names it instead.
@@ -22,8 +22,6 @@ describe("the bundled workflow's skills export", () => {
22
22
  "build.review.fix.risks",
23
23
  "notes",
24
24
  "feedback",
25
- "tests",
26
- "tests.health",
27
25
  "refactor",
28
26
  "refactor.fix.suite",
29
27
  "refactor.health",
@@ -24,9 +24,6 @@ export const skills = (
24
24
  notes: ["incremental-implementation", "code-simplification"],
25
25
  // `review`'s turn building what a review round collected.
26
26
  feedback: ["test-driven-development", "incremental-implementation"],
27
- // The `tests` workflow's sweep and rewrites.
28
- tests: ["test-audit", "test-driven-development"],
29
- "tests.health": ["debugging-and-error-recovery"],
30
27
  // The refactor's own turns load `ponytail`; `refactor.build.*` carry the `build.*` lists.
31
28
  ...build,
32
29
  ...Object.fromEntries(Object.entries(build).map(([name, list]) => [`refactor.${name}`, list])),
@@ -124,8 +124,6 @@ export interface ComposedReview {
124
124
  readonly unresolved?: true
125
125
  /** A fix turn removed its chunk while renaming another. */
126
126
  readonly unclear?: true
127
- /** Labels for the first two boxes; absent: fix as suggested, won't fix. Read by position, so only the wording changes. */
128
- readonly answers?: readonly [string, string]
129
127
  }[]
130
128
  readonly fixed: readonly { readonly title: string; readonly commit: string }[]
131
129
  readonly noise: readonly { readonly title: string; readonly p: number }[]
@@ -172,8 +170,7 @@ export const composedReview = (base: string, report: ComposedReview): string =>
172
170
  }
173
171
  const chunk = (title: string, body: string): string => `## ${title}\n\n${body.trim()}`
174
172
  // The lead-in line keeps a body that ends in a list from absorbing the boxes.
175
- const answerBoxes = ([fix, wontFix]: readonly [string, string] = [ANSWER_FIX, ANSWER_WONT_FIX]) =>
176
- `${ANSWER_LEAD}\n\n- [ ] ${fix}\n- [ ] ${wontFix}\n- [ ] ${FREE_TEXT_PLACEHOLDER}`
173
+ const answerBoxes = `${ANSWER_LEAD}\n\n- [ ] ${ANSWER_FIX}\n- [ ] ${ANSWER_WONT_FIX}\n- [ ] ${FREE_TEXT_PLACEHOLDER}`
177
174
  const conflicts = (report.standards?.undecided ?? []).map((c) =>
178
175
  chunk(unique(conflictTitle(c)), conflictBody(c)),
179
176
  )
@@ -211,7 +208,7 @@ export const composedReview = (base: string, report: ComposedReview): string =>
211
208
  : n.unclear === true
212
209
  ? `_A fix turn removed this while renaming another finding — check whether it was fixed._\n\n${unboxed(n.body)}`
213
210
  : unboxed(n.body) || "_No detail given._"
214
- }\n\n${answerBoxes(n.answers)}`,
211
+ }\n\n${answerBoxes}`,
215
212
  ),
216
213
  ),
217
214
  ...(report.fixed.length === 0
@@ -16,14 +16,6 @@ export const testFileGlobs: readonly string[] = [
16
16
 
17
17
  export const defaultFlagRegistry = "src/flags/index.ts"
18
18
 
19
- /** The `tests` workflow's own process settings: where it looks for skipped tests, and how a skip reads. */
20
- export const testsDefaults: Readonly<Record<string, string>> = {
21
- testFiles: testFileGlobs.join(", "),
22
- // JS/TS runners' `.skip`/`.todo` and `x`-prefixed blocks, and cucumber's skip tags.
23
- skipPattern:
24
- "^\\s*(?:(?:describe|context|suite|it|test|specify)\\.(?:skip|todo)\\b|x(?:describe|context|it|test|specify)\\s*\\(|(?:@\\S+\\s+)*@(?:skip|ignore)(?:\\s|$))",
25
- }
26
-
27
19
  /** Process settings. */
28
20
  export const defaults: Readonly<Record<string, string>> = {
29
21
  reviewBase: "",