@pmelab/gtd 43.0.0 → 45.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 (46) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +33 -13
  3. package/dist/chunks/{reserved-COEMFEpy.mjs → reserved-ty1mtlnS.mjs} +155 -3
  4. package/dist/chunks/review-BmoH-9im.mjs +408 -0
  5. package/dist/chunks/{tail-C_xn8U7U.mjs → tail-CM3sVoAb.mjs} +3 -3
  6. package/dist/workflow-kit.mjs +22 -10
  7. package/dist/workflows/cleanup.mjs +2 -2
  8. package/dist/workflows/feature.mjs +6 -278
  9. package/dist/workflows/fix.mjs +2 -2
  10. package/dist/workflows/review.mjs +27 -7
  11. package/dist/workflows/rollout.mjs +1 -1
  12. package/dist/workflows.mjs +379 -66
  13. package/package.json +1 -1
  14. package/src/steering/FORMATS.md +4 -4
  15. package/src/workflow-kit/review.test.ts +80 -2
  16. package/src/workflow-kit/review.ts +45 -11
  17. package/src/workflows/feature/coverage.ts +1 -1
  18. package/src/workflows/feature/feature.test.ts +8 -0
  19. package/src/workflows/feature/feature.ts +2 -2
  20. package/src/workflows/feature/index.ts +1 -1
  21. package/src/workflows/feature/refactor.ts +1 -1
  22. package/src/workflows/feature/review-answers.feature +12 -3
  23. package/src/workflows/feature/seam.ts +1 -1
  24. package/src/workflows/feature/standards.feature +286 -0
  25. package/src/workflows/feature/steps.test.ts +2 -64
  26. package/src/workflows/feature/steps.ts +5 -41
  27. package/src/workflows/fix/fix.feature +20 -21
  28. package/src/workflows/index.ts +1 -1
  29. package/src/workflows/review/review-door.feature +158 -0
  30. package/src/workflows/review/review.test.ts +1 -73
  31. package/src/workflows/review/review.ts +34 -6
  32. package/src/workflows/shared/learn.test.ts +154 -0
  33. package/src/workflows/shared/learn.ts +206 -0
  34. package/src/workflows/shared/options.ts +1 -0
  35. package/src/workflows/{feature → shared}/review.test.ts +4 -8
  36. package/src/workflows/{feature → shared}/review.ts +23 -10
  37. package/src/workflows/shared/skills.test.ts +2 -0
  38. package/src/workflows/shared/skills.ts +2 -0
  39. package/src/workflows/shared/standards.test.ts +90 -0
  40. package/src/workflows/shared/standards.ts +156 -0
  41. package/src/workflows/shared/steps.test.ts +64 -2
  42. package/src/workflows/shared/steps.ts +40 -1
  43. package/src/workflows/shared/tail.ts +2 -2
  44. package/src/workflows/shared/text.test.ts +58 -1
  45. package/src/workflows/shared/text.ts +39 -1
  46. package/src/workflows/shared/vars.ts +2 -0
@@ -1,7 +1,8 @@
1
- import { agent, answered, applyScript, changes, changesSince, check, codeChanges, env, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, matches, numeric, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restoreScript, revertScript, run, scope, showScript, slice, start, vars, writeScript, wrote } from "../flows.mjs";
2
- import { lensEntriesOf, removedAssertions, review as review$1 } from "../workflow-kit.mjs";
3
- import { $ as naivePassPrompt, A as agentWithSkills, B as coverageQuestion, C as fixCheck, Ct as envDefaults, D as reviewMissing, E as planner, F as builderSystem, G as designTriagePrompt, H as criterionLine, I as clearCriteriaMessage, L as clearCriteriaQuestion, M as buildReviewCollectingPrompt, N as buildReviewFixNitsPrompt, P as buildReviewTriageMessage, Q as naiveApplyFailure, R as composedReview, S as coder, St as defaults, T as fixRisks, U as designGateAnswerMessage, V as coveringPrompt, W as designSystem, X as implementBuildingPrompt, Y as idleMessage, Z as implementFixPrompt, _ as REFACTOR_NOTE, _t as slicingPrompt, a as flagModeOn, at as refactorBuildingPrompt, b as SLICES, bt as unwindFailedMessage, ct as refactorNotePrompt, d as guarded, dt as reviewEditsCapture, et as normaliseCriteriaScript, f as healthy, g as REFACTOR, gt as seamSteerMessage, h as QUALITY, ht as seamByHandMessage, i as skills, it as qualityUnresolvedMessage, j as buildReviewAnswerQuestionsPrompt, lt as refactorRules, m as HANDOFF, mt as reviewerSystem, n as summary, ot as refactorDroppedRefusal, p as FEEDBACK, pt as reviewNotesCapture, r as access, s as shipped, st as refactorFixPrompt, t as steering, tt as oversizedMessage, u as gate, ut as reviewEditNotesCapture, v as REQUIREMENTS, vt as startGateBlockedMessage, wt as testFileGlobs, x as awaitReview, xt as unwindFailure, y as REVIEW, yt as uncoveredMessage, z as coverageMessage } from "../chunks/reserved-COEMFEpy.mjs";
4
- import { a as refusePendingHandOff, i as isOptionalObject, n as closeReview, o as tail, s as NO_FINDINGS } from "../chunks/tail-C_xn8U7U.mjs";
1
+ import { agent, answered, applyScript, changes, changesSince, check, codeChanges, env, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, matches, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireThreadsClosed, revertScript, run, scope, slice, start, vars, wrote } from "../flows.mjs";
2
+ import { lensEntriesOf } from "../workflow-kit.mjs";
3
+ import { $ as naiveApplyFailure, B as coverageMessage, C as coder, Ct as unwindFailure, E as fixNits, Et as testFileGlobs, G as designSystem, H as coveringPrompt, I as builderSystem, K as designTriagePrompt, L as clearCriteriaMessage, N as agentWithSkills, P as buildReviewCollectingPrompt, Q as implementFixPrompt, R as clearCriteriaQuestion, St as unwindFailedMessage, T as fixCheck, Tt as envDefaults, U as criterionLine, V as coverageQuestion, W as designGateAnswerMessage, X as idleMessage, Z as implementBuildingPrompt, _ as REFACTOR_NOTE, _t as seamByHandMessage, a as flagModeOn, at as qualityUnresolvedMessage, b as SLICES, bt as startGateBlockedMessage, ct as refactorFixPrompt, et as naivePassPrompt, f as healthy, g as REFACTOR, gt as reviewerSystem, h as QUALITY, i as skills, k as planner, lt as refactorNotePrompt, m as HANDOFF, n as summary, nt as oversizedMessage, ot as refactorBuildingPrompt, p as FEEDBACK, r as access, s as shipped, st as refactorDroppedRefusal, t as steering, tt as normaliseCriteriaScript, u as gate, ut as refactorRules, v as REQUIREMENTS, vt as seamSteerMessage, w as collecting, wt as defaults, x as answerReviewQuestions, xt as uncoveredMessage, yt as slicingPrompt } from "../chunks/reserved-ty1mtlnS.mjs";
4
+ import { a as refusePendingHandOff, o as tail } from "../chunks/tail-CM3sVoAb.mjs";
5
+ import { i as reUnwind, n as humanReview, r as isCode$1, t as buildTail } from "../chunks/review-BmoH-9im.mjs";
5
6
  import { posix } from "node:path";
6
7
  //#region src/workflows/shared/fences.ts
7
8
  const markFenced = (text) => {
@@ -219,36 +220,6 @@ const answerProductQuestions = () => human("gate.answer", {
219
220
  mode: "qa",
220
221
  acceptClean: true
221
222
  });
222
- /** Answer every `question` note inline in `.gtd/REVIEW.md`. */
223
- const answerReviewQuestions = (notes) => agentWithSkills("review.answer-review-questions", buildReviewAnswerQuestionsPrompt(notes), {
224
- label: "Answering your questions",
225
- file: REVIEW,
226
- mode: "review",
227
- model: planner(),
228
- system: reviewerSystem()
229
- });
230
- /** Fix every `nit` note in one turn. */
231
- const fixNits = (notes, access, rules) => {
232
- const fixing = (name) => agentWithSkills(name, buildReviewFixNitsPrompt(notes, rules), {
233
- label: "Fixing your nits",
234
- file: REVIEW,
235
- model: planner(),
236
- system: reviewerSystem()
237
- });
238
- return access === void 0 ? fixing("review.fix.nits.fixing") : scope({
239
- name: "review.fix.nits",
240
- access
241
- }, () => fixing("fixing"));
242
- };
243
- /** Turn the review round `capture` describes into `.gtd/REQUIREMENTS.md`, or change nothing. */
244
- const collecting = (capture, round) => agentWithSkills("review.collecting", buildReviewCollectingPrompt(capture, void 0, round), {
245
- label: "Collecting your feedback",
246
- file: REQUIREMENTS,
247
- mode: "qa",
248
- model: planner(),
249
- system: reviewerSystem(),
250
- allowEmpty: true
251
- });
252
223
  /** Turn the refactor review round `capture` describes into `.gtd/REFACTOR.md`, or change nothing. */
253
224
  const refactorCollecting = (capture, round, strays) => agentWithSkills("review.collecting", buildReviewCollectingPrompt(capture, {
254
225
  strays,
@@ -286,249 +257,6 @@ const design = (base, guard) => scope("design", async () => {
286
257
  } while (await noteGate(REQUIREMENTS, answerProductQuestions));
287
258
  });
288
259
  //#endregion
289
- //#region src/workflows/feature/review.ts
290
- const VERDICTS = [
291
- "edit",
292
- "question",
293
- "nit",
294
- "praise"
295
- ];
296
- /** One four-way choice per note the human added to `.gtd/REVIEW.md`. */
297
- const verdictQuestion = (note) => ({
298
- id: note.id,
299
- primitive: "choice",
300
- instructions: `Classify the human's note on review item "${note.anchor}" (in .gtd/REVIEW.md). Its evidence holds the anchor, the reviewer's text and the human's text.`,
301
- criteria: "edit: a request to change behaviour, design or scope — anything needing a plan, or any note you are unsure about. question: asks something and wants an answer, with no change requested. nit: a small, local, unambiguous fix (naming, typo, formatting, comment wording) needing no re-plan. praise: an approving remark with nothing to do."
302
- });
303
- const TIER_ORDER = {
304
- critical: 0,
305
- should: 1,
306
- could: 2
307
- };
308
- /** Capture order: critical, should, could; within a tier one-way doors, then new concepts. */
309
- const captureKey = (s) => s === void 0 ? 2 : TIER_ORDER[s.tier] * 3 + s.rank;
310
- const SEVERITY_ANSWERS = {
311
- security: ["yes", "no"],
312
- reversibility: ["one-way", "two-way"],
313
- blastRadius: [
314
- "local",
315
- "module",
316
- "contract"
317
- ],
318
- newConcept: ["yes", "no"]
319
- };
320
- /** Anything unanswered, unrecognised, cut or below `minP` is `critical`, never `could`. */
321
- const severityOf = (note, judgment, minP) => {
322
- const ans = (q) => judgment.answers[`${note.id}.${q}`];
323
- const a = (q) => ans(q)?.answer;
324
- const rank = () => a("reversibility") === "one-way" ? 0 : a("newConcept") === "yes" ? 1 : 2;
325
- if (note.forced) return {
326
- tier: "critical",
327
- reason: "critical: forced by `!`",
328
- rank: 2
329
- };
330
- if (judgment.truncated.includes(note.id) || Object.entries(SEVERITY_ANSWERS).some(([q, allowed]) => !allowed.includes(a(q) ?? "") || !((ans(q)?.p ?? NaN) >= minP))) return {
331
- tier: "critical",
332
- reason: "critical: judge unsure",
333
- rank: 2
334
- };
335
- const critical = [...a("security") === "yes" ? ["security"] : [], ...a("reversibility") === "one-way" ? ["one-way door"] : []];
336
- if (critical.length > 0) return {
337
- tier: "critical",
338
- reason: `critical: ${critical.join(", ")}`,
339
- rank: rank()
340
- };
341
- const should = [...a("blastRadius") === "contract" ? ["contract"] : [], ...a("newConcept") === "yes" ? ["new concept"] : []];
342
- if (should.length > 0) return {
343
- tier: "should",
344
- reason: `should: ${should.join(", ")}`,
345
- rank: rank()
346
- };
347
- return {
348
- tier: "could",
349
- reason: "could",
350
- rank: 2
351
- };
352
- };
353
- const severityQuestions = (note) => [
354
- {
355
- id: `${note.id}.security`,
356
- primitive: "noul",
357
- instructions: `Does the change the human asks for in note "${note.id}" (on "${note.anchor}") touch security — authentication, authorisation, secrets, untrusted input or data exposure?`,
358
- criteria: "yes: it changes or could weaken a security property. no: it does not."
359
- },
360
- {
361
- id: `${note.id}.reversibility`,
362
- primitive: "choice",
363
- instructions: `Once the change in note "${note.id}" ships, can it be undone without breaking anyone who already depends on it?`,
364
- criteria: "one-way: a published API, file format, persisted data, protocol or other contract that cannot be taken back. two-way: can be reverted freely."
365
- },
366
- {
367
- id: `${note.id}.blastRadius`,
368
- primitive: "choice",
369
- instructions: `How far does the change in note "${note.id}" reach?`,
370
- criteria: "local: one function or file. module: a whole module or several files. contract: a public interface other code or users depend on."
371
- },
372
- {
373
- id: `${note.id}.newConcept`,
374
- primitive: "noul",
375
- instructions: `Does the change in note "${note.id}" introduce a concept new to this codebase — a new abstraction, entity or term? Judge from the evidence, never a glossary; reusing or extending an existing concept is not new.`,
376
- criteria: "yes: a genuinely new concept. no: reuses or extends an existing one."
377
- }
378
- ];
379
- /**
380
- * One judge rest: a verdict per unforced note (a forced note is asked
381
- * nothing) plus four severity answers. Dismissing a note is the risky
382
- * direction, so only a confident non-`edit` verdict on uncut evidence counts;
383
- * a cut, unanswered or below-floor note is an `edit`. Severity answers on a
384
- * non-`edit` note are ignored; on an `edit` they order the collecting turn.
385
- */
386
- const triage$1 = (severities) => async (notes) => {
387
- const asked = notes.filter((n) => !n.forced);
388
- const judgment = asked.length === 0 ? {
389
- answers: {},
390
- truncated: []
391
- } : await judge("review.triage", {
392
- questions: asked.flatMap((n) => [verdictQuestion(n), ...severityQuestions(n)]),
393
- evidence: Object.fromEntries(asked.map((n) => [n.id, `Anchor: ${n.anchor}\nReviewer's text: ${n.before}\nHuman's text: ${n.text}`])),
394
- message: buildReviewTriageMessage(),
395
- label: "Judging each note"
396
- });
397
- const { answers, truncated } = judgment;
398
- const minP = numeric(vars.reviewNoteActionable, Infinity);
399
- const severityMinP = numeric(vars.reviewSeverityMinP, Infinity);
400
- severities.clear();
401
- return new Map(notes.map((n) => {
402
- const verdict = n.forced ? void 0 : VERDICTS.find((v) => v !== "edit" && answered(answers[n.id], v, minP));
403
- if (verdict !== void 0 && !truncated.includes(n.id)) return [n.id, verdict];
404
- severities.set(n.id, severityOf(n, judgment, severityMinP));
405
- return [n.id, "edit"];
406
- }));
407
- };
408
- const isCode$1 = (path) => path !== ".gtd" && !path.startsWith(".gtd/");
409
- /**
410
- * gtd writes `.gtd/REVIEW.md` from the quality lap's `report` — no reviewer
411
- * turn — and removes what the fixer left in `.gtd/QUALITY.md`. A re-review
412
- * after fixed nits restores the doc as committed at `carry`, answers kept;
413
- * a fresh one leaves out the notes the human already answered.
414
- */
415
- const composing = (base, report, carry, nitFix, answered = /* @__PURE__ */ new Set()) => run("review.composing", carry === void 0 ? writeScript(REVIEW, composedReview(base, {
416
- ...report,
417
- weakened: removedAssertions(changesSince(base)),
418
- nitFix,
419
- answered
420
- }), [QUALITY]) : showScript(carry, REVIEW, REVIEW), {
421
- label: "Composing the review",
422
- file: REVIEW,
423
- base
424
- });
425
- /**
426
- * The quality lap's notes reach a human over everything since `base`, who
427
- * answers each one — fix as suggested, won't fix, or their own answer — and
428
- * may comment. A fix joins the nit batch, a won't-fix is dropped, and an own
429
- * answer or comment is judged: edits go to another planning lap, questions
430
- * are answered in place, nits fixed in one batch, praise dropped.
431
- */
432
- const review = (base, escalations = { rounds: 0 }, options = {}) => {
433
- const { guard, guardRests, fixAccess, rules } = options;
434
- const feedback = options.feedback ?? {
435
- file: ".gtd/REQUIREMENTS.md",
436
- collecting,
437
- unmatched: "gtd land: no declared pattern matches the pending changes — write .gtd/REQUIREMENTS.md from the feedback, or change nothing when it asks for nothing"
438
- };
439
- const severities = /* @__PURE__ */ new Map();
440
- let nitFix;
441
- const answered = /* @__PURE__ */ new Set();
442
- return review$1(base, {
443
- file: REVIEW,
444
- feedback: feedback.file,
445
- isCode: isCode$1,
446
- reviewing: (b, carry) => {
447
- const fixedAt = nitFix;
448
- nitFix = void 0;
449
- return composing(b, options.report ?? NO_FINDINGS, carry, fixedAt, answered);
450
- },
451
- fixRisks: guarded((notes) => fixRisks(notes, fixAccess, rules), guard),
452
- keepGreen: () => healthy(() => fixCheck(rules), {
453
- escalations,
454
- guard,
455
- guardRests,
456
- rules
457
- }),
458
- awaitReview: (b) => awaitReview(b),
459
- reviewMissing,
460
- collecting: feedback.collecting,
461
- triage: triage$1(severities),
462
- decided: (decisions) => decisions.forEach((d) => answered.add(d.anchor)),
463
- answerQuestions: answerReviewQuestions,
464
- fixNits: async (notes) => {
465
- await guarded((n) => fixNits(n, fixAccess, rules), guard)(notes);
466
- nitFix = head();
467
- },
468
- close: closeReview,
469
- captures: {
470
- edits: reviewEditsCapture,
471
- notes: reviewNotesCapture,
472
- editNotes: (round, edits, answeredAt) => reviewEditNotesCapture(round, edits.map((n, i) => ({
473
- n,
474
- i,
475
- s: severities.get(n.id),
476
- key: captureKey(severities.get(n.id))
477
- })).sort((x, y) => x.key - y.key || x.i - y.i).map(({ n, s }) => ({
478
- id: n.id,
479
- anchor: n.anchor,
480
- text: n.forced ? n.text.replace(/^!\s*/, "") : n.text,
481
- ...s === void 0 ? {} : { label: s.reason }
482
- })), answeredAt)
483
- },
484
- refusals: {
485
- deleted: "gtd land: review-doc: .gtd/REVIEW.md was deleted — restore it, or leave a note (or edit code) to request changes.",
486
- unmatched: feedback.unmatched,
487
- ...feedback.dropped === void 0 ? {} : { dropped: feedback.dropped }
488
- }
489
- });
490
- };
491
- function buildTail(first, second, ...rest) {
492
- const options = callForm(first, second, rest);
493
- if (options === void 0) return Promise.reject(/* @__PURE__ */ new Error("buildTail takes buildTail(base, options?) or the deprecated buildTail(fixFirst, base, green?, options?); this call fits neither"));
494
- const [base, form] = options;
495
- return tail((escalations, report) => review(base, escalations, {
496
- ...form,
497
- report
498
- }), {
499
- ...form,
500
- triage: true
501
- });
502
- }
503
- const isOptionalBoolean = (value) => value == null || typeof value === "boolean";
504
- const legacyForm = (fixFirst, base, green, options) => isOptionalBoolean(fixFirst) && isOptionalBoolean(green) && isOptionalObject(options) ? [base, {
505
- ...options,
506
- fixFirst: fixFirst ?? void 0,
507
- green: green ?? void 0
508
- }] : void 0;
509
- const currentForm = (base, options, third, fourth) => isOptionalObject(options) && third == null && fourth == null ? [base, options ?? {}] : void 0;
510
- const callForm = (first, second, [third, fourth, ...extra]) => {
511
- if (extra.some((value) => value != null)) return void 0;
512
- if (typeof second === "string") return legacyForm(first, second, third, fourth);
513
- if (typeof first === "string") return currentForm(first, second, third, fourth);
514
- };
515
- /**
516
- * Undo the human's review-round code edits. A path changed since the human
517
- * left it is not overwritten; requireRevert names it instead.
518
- */
519
- const reUnwind = async (feedback) => {
520
- const { base, restoreFrom, edited } = feedback;
521
- await run("re-unwind", restoreScript(base, {
522
- restore: edited.filter((c) => c.status !== "added").map((c) => c.path),
523
- remove: edited.filter((c) => c.status === "added").map((c) => c.path)
524
- }, restoreFrom), {
525
- label: "Re-unwinding your review edit",
526
- file: REVIEW,
527
- base
528
- });
529
- requireRevert(edited, base);
530
- };
531
- //#endregion
532
260
  //#region src/workflows/shared/continuation.ts
533
261
  const phase = (name, steered) => ({
534
262
  name,
@@ -1445,7 +1173,7 @@ const settleQuality = async () => {
1445
1173
  if (rested) await healthy(() => fixCheck());
1446
1174
  };
1447
1175
  /** The triaged quality lap, then the human review since `base`. */
1448
- const reviewedTail = (base, lensBase) => tail((escalations, report) => review(base, escalations, { report }), {
1176
+ const reviewedTail = (base, lensBase) => tail((escalations, report) => humanReview(base, escalations, { report }), {
1449
1177
  green: true,
1450
1178
  lensBase,
1451
1179
  triage: true
@@ -1,7 +1,7 @@
1
1
  import { read, refuse, removeScript, run, scope, start } from "../flows.mjs";
2
2
  import { g as reviewRisks, h as reviewNotes } from "../chunks/steering-D4LUQ-av.mjs";
3
- import { A as agentWithSkills, Ct as envDefaults, K as finisherSystem, S as coder, St as defaults, c as baseline, i as skills, n as summary, q as fixReviewNotesPrompt, r as access, s as shipped, t as steering, y as REVIEW } from "../chunks/reserved-COEMFEpy.mjs";
4
- import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-C_xn8U7U.mjs";
3
+ import { C as coder, J as fixReviewNotesPrompt, N as agentWithSkills, Tt as envDefaults, c as baseline, i as skills, n as summary, q as finisherSystem, r as access, s as shipped, t as steering, wt as defaults, y as REVIEW } from "../chunks/reserved-ty1mtlnS.mjs";
4
+ import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-CM3sVoAb.mjs";
5
5
  //#region src/workflows/fix/fix.ts
6
6
  /**
7
7
  * Fix every note a human left in a handed-off `.gtd/REVIEW.md` and every
@@ -1,16 +1,36 @@
1
- import { start } from "../flows.mjs";
2
- import { Ct as envDefaults, St as defaults, ft as reviewGateBlockedMessage, i as skills, n as summary, r as access, s as shipped, t as steering, u as gate } from "../chunks/reserved-COEMFEpy.mjs";
3
- import { a as refusePendingHandOff, r as handOffTail } from "../chunks/tail-C_xn8U7U.mjs";
1
+ import { read, refuse, removeScript, run, start } from "../flows.mjs";
2
+ import { C as coder, N as agentWithSkills, Tt as envDefaults, i as skills, mt as reviewGateBlockedMessage, n as summary, pt as reviewFeedbackFixPrompt, q as finisherSystem, r as access, s as shipped, t as steering, u as gate, v as REQUIREMENTS, wt as defaults } from "../chunks/reserved-ty1mtlnS.mjs";
3
+ import { a as refusePendingHandOff } from "../chunks/tail-CM3sVoAb.mjs";
4
+ import { i as reUnwind, t as buildTail } from "../chunks/review-BmoH-9im.mjs";
4
5
  //#region src/workflows/review/review.ts
6
+ /** Build the concerns a review round collected into `.gtd/REQUIREMENTS.md`, then remove it. */
7
+ const fixFeedback = async () => {
8
+ await agentWithSkills("feedback.fixing", reviewFeedbackFixPrompt(), {
9
+ label: "Fixing your review feedback",
10
+ file: REQUIREMENTS,
11
+ model: coder(),
12
+ system: finisherSystem(),
13
+ allowEmpty: true
14
+ });
15
+ await run("feedback.closing", removeScript([REQUIREMENTS]), { label: "Closing the feedback" });
16
+ };
5
17
  /**
6
- * Pure review of everything since `reviewBase`: a green gate, the quality
7
- * lap, then the human review. A note ends the process with `.gtd/REVIEW.md`
8
- * left for `fix`; it never re-plans.
18
+ * The feature's review of everything since `reviewBase` — a green gate, the
19
+ * triaged quality lap, then the human review — with no design phase: a
20
+ * review that asks for changes is fixed in place and reviewed again, until
21
+ * sign-off.
9
22
  */
10
23
  const reviewFlow = async () => {
11
24
  refusePendingHandOff();
25
+ if (read(".gtd/REQUIREMENTS.md") !== void 0) refuse(`gtd: ${REQUIREMENTS} is left over — finish or remove it before a review.`);
12
26
  await gate("review-gate", reviewGateBlockedMessage());
13
- await handOffTail(false, start());
27
+ const base = start();
28
+ let outcome = await buildTail(base, { green: true });
29
+ while (outcome.verdict === "feedback") {
30
+ await reUnwind(outcome);
31
+ await fixFeedback();
32
+ outcome = await buildTail(base, { lensBase: outcome.base });
33
+ }
14
34
  };
15
35
  const review = shipped(reviewFlow, { review: {
16
36
  args: [{
@@ -1,5 +1,5 @@
1
1
  import { agent, changesSince, glob, head, mark, read, refuse, removeScript, run, scope, vars } from "../flows.mjs";
2
- import { Ct as envDefaults, Dt as flipProblems, S as coder, St as defaults, Tt as flagMentions, i as skills, kt as requireFlag, n as summary, o as flagShipped, p as FEEDBACK, r as access, t as steering, u as gate } from "../chunks/reserved-COEMFEpy.mjs";
2
+ import { Bt as requireFlag, C as coder, It as flagMentions, Rt as flipProblems, Tt as envDefaults, i as skills, n as summary, o as flagShipped, p as FEEDBACK, r as access, t as steering, u as gate, wt as defaults } from "../chunks/reserved-ty1mtlnS.mjs";
3
3
  //#region src/workflows/rollout/rollout.ts
4
4
  const flippingPrompt = (name, registry) => `Roll out the release flag "${name}": in ${registry}, set \`defaultOn: true\` on its entry. Change nothing else — no other entry, no other field, no other file, no comments.`;
5
5
  const suiteBlockedMessage = () => "The suite is red after the rollout. Fix it by hand — a rollout may only touch the flag's registry entry — then land.";