@pmelab/gtd 28.2.0 → 29.1.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.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +28 -14
- package/claude/hooks/handoff.ts +2 -3
- package/claude/hooks/ship.ts +762 -62
- package/dist/chunks/{reserved-DeBS4Ymh.mjs → reserved-DoHUII2D.mjs} +206 -121
- package/dist/chunks/{tail-B2SER2Ih.mjs → tail-C6YBG5zh.mjs} +19 -12
- package/dist/testkit.mjs +2 -1
- package/dist/workflow-kit.mjs +51 -21
- package/dist/workflows/cleanup.mjs +2 -2
- package/dist/workflows/feature.mjs +247 -96
- package/dist/workflows/fix.mjs +2 -2
- package/dist/workflows/review.mjs +2 -2
- package/dist/workflows/rollout.mjs +1 -1
- package/dist/workflows.mjs +528 -262
- package/package.json +1 -1
- package/src/testkit/TestRepo.ts +3 -0
- package/src/testkit/index.ts +1 -1
- package/src/testkit/steps.ts +44 -0
- package/src/workflow-kit/review.test.ts +366 -8
- package/src/workflow-kit/review.ts +109 -26
- package/src/workflows/feature/feature.test.ts +313 -1
- package/src/workflows/feature/feature.ts +48 -43
- package/src/workflows/feature/one-step-land.feature +4 -0
- package/src/workflows/feature/refactor-first.feature +54 -24
- package/src/workflows/feature/refactor-review.feature +1706 -0
- package/src/workflows/feature/refactor.test.ts +592 -2
- package/src/workflows/feature/refactor.ts +160 -42
- package/src/workflows/feature/review.test.ts +322 -5
- package/src/workflows/feature/review.ts +77 -10
- package/src/workflows/feature/seam-rejection.feature +4 -0
- package/src/workflows/feature/seam.feature +3 -4
- package/src/workflows/feature/seam.test.ts +196 -1
- package/src/workflows/feature/seam.ts +23 -9
- package/src/workflows/feature/steps.test.ts +54 -2
- package/src/workflows/feature/steps.ts +45 -9
- package/src/workflows/feature/tier.test.ts +4 -0
- package/src/workflows/feature/tier.ts +2 -20
- package/src/workflows/shared/__fixtures__/feature-collecting-prompt.txt +97 -0
- package/src/workflows/shared/access.test.ts +31 -1
- package/src/workflows/shared/access.ts +7 -1
- package/src/workflows/shared/fences.ts +19 -0
- package/src/workflows/shared/health.ts +32 -12
- package/src/workflows/shared/options.test.ts +18 -0
- package/src/workflows/shared/options.ts +1 -0
- package/src/workflows/shared/prose.ts +4 -2
- package/src/workflows/shared/skills.test.ts +20 -1
- package/src/workflows/shared/skills.ts +43 -32
- package/src/workflows/shared/steps.ts +33 -19
- package/src/workflows/shared/tail.test.ts +84 -1
- package/src/workflows/shared/tail.ts +30 -12
- package/src/workflows/shared/text.test.ts +266 -3
- package/src/workflows/shared/text.ts +183 -69
- package/src/workflows/shared/vars.ts +12 -2
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { answered, changes, changesSince, glob, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, numeric, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restoreScript, revertScript, run, scope,
|
|
2
|
-
import { review as review$1 } from "../workflow-kit.mjs";
|
|
3
|
-
import { $ as
|
|
4
|
-
import { a as tail, i as refusePendingHandOff, n as closeReview } from "../chunks/tail-
|
|
5
|
-
//#region src/workflows/
|
|
6
|
-
/** GTD_FLAG_NAIVE_PASS, GTD_FLAG_RISK_TIER: both pinned at process start, so a mid-process flip never moves a process. */
|
|
7
|
-
const riskTierOn = () => vars.flag_naive_pass === "1" && vars.flag_risk_tier === "1";
|
|
1
|
+
import { answered, changes, changesSince, glob, hasThreadFor, head, human, judge, judgeDiffShape, kept, mark, matches, numeric, quote, read, refuse, removeScript, requireAnswers, requireProgress, requireReplies, requireRevert, requireThreadsClosed, restoreScript, revertScript, run, scope, slice, start, vars } from "../flows.mjs";
|
|
2
|
+
import { lensesOf, review as review$1 } from "../workflow-kit.mjs";
|
|
3
|
+
import { $ as refactorFixPrompt, A as architectureDecomposePrompt, B as designSystem, C as planner, D as agentWithSkills, E as reviewing, F as buildReviewFixNitsPrompt, G as idleMessage, I as buildReviewTriageMessage, J as naivePassPrompt, K as implementBuildingPrompt, L as builderSystem, M as buildReviewAnswerQuestionsPrompt, N as buildReviewAwaitAfterDeferMessage, O as architectSystem, P as buildReviewCollectingPrompt, Q as refactorDroppedRefusal, R as deferNotesScript, S as fixRisks, V as designTriagePrompt, X as packagesItemFixSuitePrompt, Y as packagesItemBuildingPrompt, Z as refactorBuildingPrompt, _ as REVIEW, at as reviewerSystem, b as fixCheck, ct as startGateBlockedMessage, d as healthy, dt as defaults, et as refactorRules, f as ARCHITECTURE, ft as envDefaults, g as REQUIREMENTS, h as REFACTOR, i as skills, it as reviewNotesCapture, j as architectureGateAnswerMessage, k as architectureAuthorPrompt, l as gate, lt as unwindFailedMessage, n as summary, nt as reviewEditsCapture, o as shipped, ot as seamByHandMessage, p as FEEDBACK, pt as testFileGlobs, q as implementFixPrompt, r as access, st as seamSteerMessage, t as steering, tt as reviewEditNotesCapture, u as guarded, ut as unwindFailure, v as awaitReview, w as reviewMissing, y as coder, z as designGateAnswerMessage } from "../chunks/reserved-DoHUII2D.mjs";
|
|
4
|
+
import { a as tail, i as refusePendingHandOff, n as closeReview } from "../chunks/tail-C6YBG5zh.mjs";
|
|
5
|
+
//#region src/workflows/shared/fences.ts
|
|
8
6
|
const markFenced = (text) => {
|
|
9
7
|
let fence;
|
|
10
8
|
return text.split(/\r?\n/).map((line) => {
|
|
@@ -18,6 +16,10 @@ const markFenced = (text) => {
|
|
|
18
16
|
};
|
|
19
17
|
});
|
|
20
18
|
};
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/workflows/feature/tier.ts
|
|
21
|
+
/** GTD_FLAG_NAIVE_PASS, GTD_FLAG_RISK_TIER: both pinned at process start, so a mid-process flip never moves a process. */
|
|
22
|
+
const riskTierOn = () => vars.flag_naive_pass === "1" && vars.flag_risk_tier === "1";
|
|
21
23
|
const sectionLines = (text, title) => {
|
|
22
24
|
if (text === void 0) return [];
|
|
23
25
|
const heading = new RegExp(`^##[ \\t]+${title}[ \\t]*$`, "i");
|
|
@@ -107,12 +109,18 @@ const answerReviewQuestions = (notes) => agentWithSkills("review.answer-review-q
|
|
|
107
109
|
system: reviewerSystem()
|
|
108
110
|
});
|
|
109
111
|
/** Fix every `nit` note in one turn. */
|
|
110
|
-
const fixNits = (notes
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
const fixNits = (notes, access, rules) => {
|
|
113
|
+
const fixing = (name) => agentWithSkills(name, buildReviewFixNitsPrompt(notes, rules), {
|
|
114
|
+
label: "Fixing your nits",
|
|
115
|
+
file: REVIEW,
|
|
116
|
+
model: planner(),
|
|
117
|
+
system: reviewerSystem()
|
|
118
|
+
});
|
|
119
|
+
return access === void 0 ? fixing("review.fix.nits.fixing") : scope({
|
|
120
|
+
name: "review.fix.nits",
|
|
121
|
+
access
|
|
122
|
+
}, () => fixing("fixing"));
|
|
123
|
+
};
|
|
116
124
|
/** Turn the review round `capture` describes into `.gtd/REQUIREMENTS.md`, or change nothing. */
|
|
117
125
|
const collecting = (capture) => agentWithSkills("review.collecting", buildReviewCollectingPrompt(capture), {
|
|
118
126
|
label: "Collecting your feedback",
|
|
@@ -122,6 +130,17 @@ const collecting = (capture) => agentWithSkills("review.collecting", buildReview
|
|
|
122
130
|
system: reviewerSystem(),
|
|
123
131
|
allowEmpty: true
|
|
124
132
|
});
|
|
133
|
+
/** Turn the refactor review round `capture` describes into `.gtd/REFACTOR.md`, or change nothing. */
|
|
134
|
+
const refactorCollecting = (capture, round, strays) => agentWithSkills("review.collecting", buildReviewCollectingPrompt(capture, {
|
|
135
|
+
strays,
|
|
136
|
+
round
|
|
137
|
+
}), {
|
|
138
|
+
label: "Collecting your refactor feedback",
|
|
139
|
+
file: REFACTOR,
|
|
140
|
+
model: planner(),
|
|
141
|
+
system: reviewerSystem(),
|
|
142
|
+
allowEmpty: true
|
|
143
|
+
});
|
|
125
144
|
/** `build.review.defer`: append `notes` to `.gtd/TODO.md` as one section; the step's landing is the commit. */
|
|
126
145
|
const deferNotes = (notes, round) => run("review.defer", deferNotesScript(notes, round), {
|
|
127
146
|
label: "Deferring could-notes to TODO.md",
|
|
@@ -339,26 +358,37 @@ const isCode$1 = (path) => path !== ".gtd" && !path.startsWith(".gtd/");
|
|
|
339
358
|
* another planning lap, questions are answered in place, nits fixed in one
|
|
340
359
|
* batch, praise dropped.
|
|
341
360
|
*/
|
|
342
|
-
const review = (base, escalations = { rounds: 0 }) => {
|
|
361
|
+
const review = (base, escalations = { rounds: 0 }, options = {}) => {
|
|
362
|
+
const { guard, guardRests, fixAccess, rules } = options;
|
|
363
|
+
const feedback = options.feedback ?? {
|
|
364
|
+
file: ".gtd/REQUIREMENTS.md",
|
|
365
|
+
collecting,
|
|
366
|
+
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"
|
|
367
|
+
};
|
|
343
368
|
const severities = /* @__PURE__ */ new Map();
|
|
344
369
|
let deferred = [];
|
|
345
370
|
return review$1(base, {
|
|
346
371
|
file: REVIEW,
|
|
347
|
-
feedback:
|
|
372
|
+
feedback: feedback.file,
|
|
348
373
|
isCode: isCode$1,
|
|
349
374
|
reviewing: (b, carry) => {
|
|
350
375
|
deferred = [];
|
|
351
|
-
return reviewing(b, carry);
|
|
376
|
+
return reviewing(b, carry, options.lap);
|
|
352
377
|
},
|
|
353
|
-
fixRisks,
|
|
354
|
-
keepGreen: () => healthy(fixCheck, {
|
|
378
|
+
fixRisks: guarded((notes) => fixRisks(notes, fixAccess, rules), guard),
|
|
379
|
+
keepGreen: () => healthy(() => fixCheck(rules), {
|
|
380
|
+
escalations,
|
|
381
|
+
guard,
|
|
382
|
+
guardRests,
|
|
383
|
+
rules
|
|
384
|
+
}),
|
|
355
385
|
awaitReview: (b) => {
|
|
356
386
|
const notes = deferred;
|
|
357
387
|
deferred = [];
|
|
358
388
|
return notes.length > 0 ? awaitReview(b, buildReviewAwaitAfterDeferMessage(b, notes)) : awaitReview(b);
|
|
359
389
|
},
|
|
360
390
|
reviewMissing,
|
|
361
|
-
collecting,
|
|
391
|
+
collecting: feedback.collecting,
|
|
362
392
|
triage: triage$1(severities),
|
|
363
393
|
defer: (notes) => {
|
|
364
394
|
deferred = notes.map((n) => ({
|
|
@@ -370,7 +400,7 @@ const review = (base, escalations = { rounds: 0 }) => {
|
|
|
370
400
|
return deferNotes(deferred, head());
|
|
371
401
|
},
|
|
372
402
|
answerQuestions: answerReviewQuestions,
|
|
373
|
-
fixNits,
|
|
403
|
+
fixNits: guarded((notes) => fixNits(notes, fixAccess, rules), guard),
|
|
374
404
|
close: closeReview,
|
|
375
405
|
captures: {
|
|
376
406
|
edits: reviewEditsCapture,
|
|
@@ -388,21 +418,44 @@ const review = (base, escalations = { rounds: 0 }) => {
|
|
|
388
418
|
},
|
|
389
419
|
refusals: {
|
|
390
420
|
deleted: "gtd land: review-doc: .gtd/REVIEW.md was deleted — restore it, or leave a note (or edit code) to request changes.",
|
|
391
|
-
unmatched:
|
|
421
|
+
unmatched: feedback.unmatched,
|
|
422
|
+
...feedback.dropped === void 0 ? {} : { dropped: feedback.dropped }
|
|
392
423
|
}
|
|
393
424
|
});
|
|
394
425
|
};
|
|
395
426
|
/** The build tail: fix (when entered red), keep green unless `green`, the quality lap, then human review since `base`. */
|
|
396
|
-
const buildTail = (fixFirst, base, green = false) => tail(fixFirst, (escalations) => review(base, escalations), green);
|
|
427
|
+
const buildTail = (fixFirst, base, green = false, options = {}) => tail(fixFirst, (escalations) => review(base, escalations, options), green, options.guard, options.lenses, options.guardRests, options.lensBase, options.rules);
|
|
428
|
+
/**
|
|
429
|
+
* Undo the human's review-round code edits. A path changed since the human
|
|
430
|
+
* left it is not overwritten; requireRevert names it instead.
|
|
431
|
+
*/
|
|
432
|
+
const reUnwind = async (feedback) => {
|
|
433
|
+
const { base, restoreFrom, edited } = feedback;
|
|
434
|
+
await run("re-unwind", restoreScript(base, {
|
|
435
|
+
restore: edited.filter((c) => c.status !== "added").map((c) => c.path),
|
|
436
|
+
remove: edited.filter((c) => c.status === "added").map((c) => c.path)
|
|
437
|
+
}, restoreFrom), {
|
|
438
|
+
label: "Re-unwinding your review edit",
|
|
439
|
+
file: REVIEW,
|
|
440
|
+
base
|
|
441
|
+
});
|
|
442
|
+
requireRevert(edited, base);
|
|
443
|
+
};
|
|
397
444
|
//#endregion
|
|
398
445
|
//#region src/workflows/feature/refactor.ts
|
|
399
|
-
const REFACTOR_PACKAGE =
|
|
446
|
+
const REFACTOR_PACKAGE = REFACTOR;
|
|
400
447
|
const isCode = (path) => path !== ".gtd" && !path.startsWith(".gtd/");
|
|
401
448
|
const requireWithin = (allowed) => {
|
|
402
449
|
const outside = changes().map((c) => c.path).filter((path) => isCode(path) && !allowed.includes(path));
|
|
403
450
|
if (outside.length === 0) return;
|
|
404
451
|
refuse(`gtd land: refactor-scope: the refactor may change only its stray paths — revert these:\n${outside.map((path) => ` - ${path}`).join("\n")}`);
|
|
405
452
|
};
|
|
453
|
+
const isTestFile = (path) => testFileGlobs.some((glob) => matches(path, glob));
|
|
454
|
+
const requireNoTestFiles = () => {
|
|
455
|
+
const tests = changes().map((c) => c.path).filter((path) => isCode(path) && isTestFile(path));
|
|
456
|
+
if (tests.length === 0) return;
|
|
457
|
+
refuse(`gtd land: refactor-tests: the refactor may not change a test file — revert these:\n${tests.map((path) => ` - ${path}`).join("\n")}`);
|
|
458
|
+
};
|
|
406
459
|
const flagExpression = (pattern) => {
|
|
407
460
|
if (pattern.trim() === "") throw new Error("gtd: refactorFlagPattern must not be blank");
|
|
408
461
|
let expression;
|
|
@@ -444,6 +497,8 @@ const requireNoNewFlags = (pattern, since) => {
|
|
|
444
497
|
if (raised.length === 0) return;
|
|
445
498
|
refuse(`gtd land: refactor-flags: the refactor may not introduce or add reads of a flag (${pattern}):\n${raised.map(([name, count]) => ` - ${name}: ${before.get(name) ?? 0} → ${count}`).join("\n")}`);
|
|
446
499
|
};
|
|
500
|
+
/** A test-file stray is never refactored: the implement turn still reads it in the naive tip. */
|
|
501
|
+
const refactorStrays = (shape) => shape.strays.filter((path) => !isTestFile(path));
|
|
447
502
|
const packageLines = (strays, pattern, reference) => [
|
|
448
503
|
"# Behaviour-neutral refactor",
|
|
449
504
|
"",
|
|
@@ -456,7 +511,7 @@ const packageLines = (strays, pattern, reference) => [
|
|
|
456
511
|
"## Rules",
|
|
457
512
|
"",
|
|
458
513
|
"- The suite's outcome must not change",
|
|
459
|
-
"- Change only the paths above
|
|
514
|
+
"- Change only the paths above, and never a test file",
|
|
460
515
|
`- Introduce no flag read and add no second read of an existing one (\`${pattern}\`)`,
|
|
461
516
|
"",
|
|
462
517
|
"## Reference",
|
|
@@ -464,49 +519,125 @@ const packageLines = (strays, pattern, reference) => [
|
|
|
464
519
|
reference === void 0 ? "None." : `Reading material only, never applied: \`git show ${reference} -- ${strays.map(quote).join(" ")}\``,
|
|
465
520
|
""
|
|
466
521
|
];
|
|
522
|
+
const demoteHeadings = (text) => {
|
|
523
|
+
const marked = markFenced(text.trimEnd());
|
|
524
|
+
const lines = marked.map((m) => m.line);
|
|
525
|
+
const headings = marked.map((m) => m.fenced ? void 0 : /^(#{1,6}) /.exec(m.line)?.[1]?.length);
|
|
526
|
+
const shift = 3 - Math.min(...headings.filter((n) => n !== void 0));
|
|
527
|
+
return lines.map((line, i) => {
|
|
528
|
+
const depth = headings[i];
|
|
529
|
+
if (depth === void 0) return line;
|
|
530
|
+
return "#".repeat(Math.min(6, depth + shift)) + line.slice(depth);
|
|
531
|
+
});
|
|
532
|
+
};
|
|
467
533
|
const deriveScript = (lines) => `mkdir -p .gtd
|
|
468
534
|
printf '%s\\n' ${lines.map(quote).join(" ")} > ${quote(REFACTOR_PACKAGE)}
|
|
469
535
|
`;
|
|
470
|
-
|
|
471
|
-
|
|
536
|
+
/** Undefined for every shape that is not a readable `refactor-first`; throws on a bad flag pattern. */
|
|
537
|
+
const plan = (shape, options) => {
|
|
538
|
+
if (shape.choice !== "refactor-first" || shape.unreadable.length > 0) return void 0;
|
|
472
539
|
const pattern = vars.refactorFlagPattern ?? defaults.refactorFlagPattern;
|
|
473
540
|
const expression = flagExpression(pattern);
|
|
474
|
-
const
|
|
541
|
+
const strays = refactorStrays(shape);
|
|
542
|
+
if (strays.length === 0) return void 0;
|
|
475
543
|
const zeroLength = strays.filter((path) => countFlags(read(path) ?? "", expression, /* @__PURE__ */ new Map()));
|
|
476
544
|
if (zeroLength.length > 0) throw new Error(`gtd: refactorFlagPattern (${pattern}) produces a zero-length match in:\n${listPaths(zeroLength)}`);
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
545
|
+
return {
|
|
546
|
+
pattern,
|
|
547
|
+
strays,
|
|
548
|
+
reference: options.reference?.trim() ? options.reference : void 0
|
|
549
|
+
};
|
|
550
|
+
};
|
|
551
|
+
const guardOf = ({ pattern, strays }, entry) => () => {
|
|
552
|
+
requireNoTestFiles();
|
|
553
|
+
requireWithin(strays);
|
|
554
|
+
requireNoNewFlags(pattern, entry);
|
|
555
|
+
};
|
|
556
|
+
/** Derive, build, check and close one refactor lap; `feedback` is the collected review text of the lap before. */
|
|
557
|
+
const refactorLap = async (p, guard, feedback) => {
|
|
558
|
+
const { pattern, strays, reference } = p;
|
|
559
|
+
await run("deriving", deriveScript([...packageLines(strays, pattern, reference), ...feedback === void 0 ? [] : [
|
|
560
|
+
"## Review feedback",
|
|
561
|
+
"",
|
|
562
|
+
...demoteHeadings(feedback),
|
|
563
|
+
""
|
|
564
|
+
]]), {
|
|
565
|
+
label: "Deriving the refactor package",
|
|
566
|
+
file: REFACTOR_PACKAGE
|
|
567
|
+
});
|
|
568
|
+
await agentWithSkills("building", refactorBuildingPrompt(REFACTOR_PACKAGE, strays, pattern, reference, feedback !== void 0), {
|
|
569
|
+
label: "Refactoring",
|
|
570
|
+
model: coder(),
|
|
571
|
+
system: builderSystem()
|
|
572
|
+
});
|
|
573
|
+
guard();
|
|
574
|
+
await healthy(async () => {
|
|
575
|
+
await agentWithSkills("fix.suite.fixing", refactorFixPrompt(strays, pattern), {
|
|
576
|
+
label: "Fixing the check",
|
|
577
|
+
file: FEEDBACK,
|
|
493
578
|
model: coder(),
|
|
494
579
|
system: builderSystem()
|
|
495
580
|
});
|
|
496
581
|
guard();
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
582
|
+
}, {
|
|
583
|
+
describeGuard: guard,
|
|
584
|
+
rules: refactorRules(strays, pattern)
|
|
585
|
+
});
|
|
586
|
+
await run("closing", removeScript([REFACTOR_PACKAGE]), { label: "Closing out the refactor" });
|
|
587
|
+
};
|
|
588
|
+
const strayAccess = (strays) => ({ write: [...strays, ".gtd/**"] });
|
|
589
|
+
const refactorFirst = async (shape, options = {}) => {
|
|
590
|
+
const p = plan(shape, options);
|
|
591
|
+
if (p === void 0) return false;
|
|
592
|
+
return scope({
|
|
593
|
+
name: "refactor",
|
|
594
|
+
access: strayAccess(p.strays)
|
|
595
|
+
}, async () => {
|
|
596
|
+
await refactorLap(p, guardOf(p, head()));
|
|
507
597
|
return true;
|
|
508
598
|
});
|
|
509
599
|
};
|
|
600
|
+
/**
|
|
601
|
+
* `refactorFirst` plus its review, in the one `refactor` scope so the tail's
|
|
602
|
+
* steps are `refactor.build.*`. A review edit re-runs the refactor, never a
|
|
603
|
+
* re-plan: the collected notes ride the re-derived package. Every round
|
|
604
|
+
* reviews the whole refactor since `entry`, since each lap rebuilds over it.
|
|
605
|
+
*/
|
|
606
|
+
const reviewedRefactor = async (shape, options = {}) => {
|
|
607
|
+
const p = plan(shape, options);
|
|
608
|
+
if (p === void 0) return false;
|
|
609
|
+
return scope({
|
|
610
|
+
name: "refactor",
|
|
611
|
+
access: strayAccess(p.strays)
|
|
612
|
+
}, async () => {
|
|
613
|
+
const entry = head();
|
|
614
|
+
const guard = guardOf(p, entry);
|
|
615
|
+
const feedback = {
|
|
616
|
+
file: REFACTOR_PACKAGE,
|
|
617
|
+
collecting: (capture, round) => refactorCollecting(capture, round, p.strays),
|
|
618
|
+
unmatched: "gtd land: refactor-feedback: the review feedback changed files but wrote no .gtd/REFACTOR.md — a refactor cannot act on it; revert the change, or `gtd abandon` and restart",
|
|
619
|
+
dropped: refactorDroppedRefusal
|
|
620
|
+
};
|
|
621
|
+
let collected;
|
|
622
|
+
for (;;) {
|
|
623
|
+
await refactorLap(p, guard, collected);
|
|
624
|
+
const outcome = await buildTail(false, entry, true, {
|
|
625
|
+
guard,
|
|
626
|
+
guardRests: false,
|
|
627
|
+
lenses: lensesOf(vars.refactorQualityReviews ?? ""),
|
|
628
|
+
feedback,
|
|
629
|
+
fixAccess: strayAccess(p.strays),
|
|
630
|
+
lensBase: entry,
|
|
631
|
+
rules: refactorRules(p.strays, p.pattern),
|
|
632
|
+
lap: "refactor"
|
|
633
|
+
});
|
|
634
|
+
if (outcome.verdict === "signoff") return true;
|
|
635
|
+
await reUnwind(outcome);
|
|
636
|
+
collected = read(REFACTOR_PACKAGE);
|
|
637
|
+
if (collected === void 0) throw new Error(`gtd: review feedback left no ${REFACTOR_PACKAGE}`);
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
};
|
|
510
641
|
//#endregion
|
|
511
642
|
//#region src/workflows/feature/seam.ts
|
|
512
643
|
/** Naive-pass retries after a `reject`; the next `reject` rests at `seam.steer`. */
|
|
@@ -523,8 +654,8 @@ const naivePass = (rejection) => agentWithSkills("naive", naivePassPrompt(reject
|
|
|
523
654
|
model: coder(),
|
|
524
655
|
system: builderSystem()
|
|
525
656
|
});
|
|
526
|
-
const implement = (reference, tier) => scope("implement", async () => {
|
|
527
|
-
await agentWithSkills("building", implementBuildingPrompt(reference, tier), {
|
|
657
|
+
const implement = (reference, tier, amend) => scope("implement", async () => {
|
|
658
|
+
await agentWithSkills("building", implementBuildingPrompt(reference, tier, amend), {
|
|
528
659
|
label: "Implementing",
|
|
529
660
|
model: coder(),
|
|
530
661
|
system: builderSystem()
|
|
@@ -545,13 +676,10 @@ const decide = () => scope("seam", async () => {
|
|
|
545
676
|
const tip = kept("naive");
|
|
546
677
|
const shape = await judgeSeam(tip);
|
|
547
678
|
if (shape.choice === "land") return { reference: tip.hash };
|
|
548
|
-
if (shape.choice === "refactor-first") {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
shape
|
|
553
|
-
};
|
|
554
|
-
}
|
|
679
|
+
if (shape.choice === "refactor-first") return {
|
|
680
|
+
reference: tip.hash,
|
|
681
|
+
shape
|
|
682
|
+
};
|
|
555
683
|
rejection = {
|
|
556
684
|
strays: shape.strays,
|
|
557
685
|
p: shape.judgment?.answers.shape?.p ?? 0
|
|
@@ -571,14 +699,32 @@ const decide = () => scope("seam", async () => {
|
|
|
571
699
|
retries = 0;
|
|
572
700
|
}
|
|
573
701
|
});
|
|
574
|
-
const seamBuild = async (
|
|
702
|
+
const seamBuild = async (base) => {
|
|
575
703
|
const { reference, shape } = await decide();
|
|
576
|
-
if (shape
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
704
|
+
if (shape === void 0) return {
|
|
705
|
+
reference,
|
|
706
|
+
base
|
|
707
|
+
};
|
|
708
|
+
if (shape.unreadable.length > 0) {
|
|
709
|
+
await scope("seam", () => human("by-hand", {
|
|
710
|
+
message: seamByHandMessage(shape.strays, shape.unreadable, reference),
|
|
711
|
+
label: "Reviewing unreadable files by hand",
|
|
712
|
+
acceptClean: true
|
|
713
|
+
}));
|
|
714
|
+
return {
|
|
715
|
+
reference,
|
|
716
|
+
base
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
if (refactorStrays(shape).length === 0) return {
|
|
720
|
+
reference,
|
|
721
|
+
base
|
|
722
|
+
};
|
|
723
|
+
await slice("refactor", () => reviewedRefactor(shape, { reference }));
|
|
724
|
+
return {
|
|
725
|
+
reference,
|
|
726
|
+
base: head()
|
|
727
|
+
};
|
|
582
728
|
};
|
|
583
729
|
//#endregion
|
|
584
730
|
//#region src/workflows/feature/feature.ts
|
|
@@ -587,44 +733,49 @@ const unwind = () => {
|
|
|
587
733
|
const commit = head();
|
|
588
734
|
return run("unwind", revertScript(commit, FEEDBACK, unwindFailure(commit)), { label: "Unwinding your input" });
|
|
589
735
|
};
|
|
590
|
-
/**
|
|
591
|
-
* Undo the human's review-round code edits. A path changed since the human
|
|
592
|
-
* left it is not overwritten; requireRevert names it instead.
|
|
593
|
-
*/
|
|
594
|
-
const reUnwind = async (feedback) => {
|
|
595
|
-
const { base, restoreFrom, edited } = feedback;
|
|
596
|
-
await run("re-unwind", restoreScript(base, {
|
|
597
|
-
restore: edited.filter((c) => c.status !== "added").map((c) => c.path),
|
|
598
|
-
remove: edited.filter((c) => c.status === "added").map((c) => c.path)
|
|
599
|
-
}, restoreFrom), {
|
|
600
|
-
label: "Re-unwinding your review edit",
|
|
601
|
-
file: REVIEW,
|
|
602
|
-
base
|
|
603
|
-
});
|
|
604
|
-
requireRevert(edited, base);
|
|
605
|
-
};
|
|
606
736
|
/** Plan, build and review until a review round signs off; feedback re-plans from scratch. */
|
|
607
|
-
const planAndBuild = async (firstBase) => {
|
|
737
|
+
const planAndBuild = async (firstBase, firstLap = "first") => {
|
|
738
|
+
if (vars.flag_naive_pass === "1") return naivePlanAndBuild(firstBase);
|
|
608
739
|
let base = firstBase;
|
|
740
|
+
let lap = firstLap;
|
|
609
741
|
for (;;) {
|
|
610
742
|
await design(base);
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
await architecture();
|
|
615
|
-
await packages();
|
|
616
|
-
}
|
|
617
|
-
const outcome = await buildTail(false, base, true);
|
|
743
|
+
await architecture();
|
|
744
|
+
await packages();
|
|
745
|
+
const outcome = await buildTail(false, base, true, lap === "first" ? {} : { lap });
|
|
618
746
|
if (outcome.verdict === "signoff") return;
|
|
619
747
|
await reUnwind(outcome);
|
|
620
748
|
base = outcome.base;
|
|
749
|
+
lap = "feedback";
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
const naivePlanAndBuild = async (firstBase) => {
|
|
753
|
+
await design(firstBase);
|
|
754
|
+
const tier = riskTierOn() ? recordRisk() : void 0;
|
|
755
|
+
const seam = await seamBuild(firstBase);
|
|
756
|
+
await slice("feature", async () => {
|
|
757
|
+
await implement(seam.reference, tier);
|
|
758
|
+
await featureFeedback(await buildTail(false, seam.base, true, { lensBase: seam.base }), seam.base);
|
|
759
|
+
});
|
|
760
|
+
};
|
|
761
|
+
const featureFeedback = async (first, lensBase) => {
|
|
762
|
+
let outcome = first;
|
|
763
|
+
while (outcome.verdict === "feedback") {
|
|
764
|
+
await reUnwind(outcome);
|
|
765
|
+
await design(outcome.base);
|
|
766
|
+
await implement(void 0, riskTierOn() ? recordRisk() : void 0, true);
|
|
767
|
+
outcome = await buildTail(false, outcome.base, true, {
|
|
768
|
+
lap: "feedback",
|
|
769
|
+
...lensBase === void 0 ? {} : { lensBase }
|
|
770
|
+
});
|
|
621
771
|
}
|
|
622
772
|
};
|
|
623
|
-
/** After a tail that skipped planning: stop on sign-off, re-plan on feedback. */
|
|
773
|
+
/** After a tail that skipped planning: stop on sign-off, re-plan on feedback. Opens no slice: slices cannot nest. */
|
|
624
774
|
const afterTail = async (outcome) => {
|
|
625
775
|
if (outcome.verdict === "signoff") return;
|
|
776
|
+
if (vars.flag_naive_pass === "1") return featureFeedback(outcome);
|
|
626
777
|
await reUnwind(outcome);
|
|
627
|
-
await planAndBuild(outcome.base);
|
|
778
|
+
await planAndBuild(outcome.base, "feedback");
|
|
628
779
|
};
|
|
629
780
|
/** An ordinary start: wait at `idle`, unwind the sketch, check the baseline, then plan and build. */
|
|
630
781
|
const ordinaryStart = async () => {
|
|
@@ -648,4 +799,4 @@ const feature = shipped(ordinaryStart);
|
|
|
648
799
|
//#region src/workflows/feature/index.ts
|
|
649
800
|
var feature_default = feature;
|
|
650
801
|
//#endregion
|
|
651
|
-
export { REFACTOR_PACKAGE, access, afterTail, answerProductQuestions, answerReviewQuestions, answerTechnicalQuestions, architecture, author, build, buildTail, collecting, declaredTests, decompose, feature_default as default, defaults, deferNotes, design, envDefaults, feature, fixNits, fixSuite, implement, judgeSeam, naivePass, nextPackage, ordinaryStart, packageItem, packages, planAndBuild, reUnwind, refactorFirst, requireDeclaredTests, requireNoNewFlags, requireWithin, seamBuild, skills, steering, summary, triage, unwind };
|
|
802
|
+
export { REFACTOR_PACKAGE, access, afterTail, answerProductQuestions, answerReviewQuestions, answerTechnicalQuestions, architecture, author, build, buildTail, collecting, declaredTests, decompose, feature_default as default, defaults, deferNotes, design, envDefaults, feature, fixNits, fixSuite, implement, judgeSeam, naivePass, nextPackage, ordinaryStart, packageItem, packages, planAndBuild, reUnwind, refactorCollecting, refactorFirst, refactorStrays, requireDeclaredTests, requireNoNewFlags, requireNoTestFiles, requireWithin, reviewedRefactor, seamBuild, skills, steering, summary, triage, unwind };
|
package/dist/workflows/fix.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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 {
|
|
4
|
-
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-
|
|
3
|
+
import { D as agentWithSkills, H as finisherSystem, U as fixReviewNotesPrompt, _ as REVIEW, dt as defaults, ft as envDefaults, i as skills, n as summary, o as shipped, r as access, s as baseline, t as steering, y as coder } from "../chunks/reserved-DoHUII2D.mjs";
|
|
4
|
+
import { r as handOffTail, t as REVIEW_ORIG } from "../chunks/tail-C6YBG5zh.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,6 +1,6 @@
|
|
|
1
1
|
import { start } from "../flows.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { i as refusePendingHandOff, r as handOffTail } from "../chunks/tail-
|
|
2
|
+
import { dt as defaults, ft as envDefaults, i as skills, l as gate, n as summary, o as shipped, r as access, rt as reviewGateBlockedMessage, t as steering } from "../chunks/reserved-DoHUII2D.mjs";
|
|
3
|
+
import { i as refusePendingHandOff, r as handOffTail } from "../chunks/tail-C6YBG5zh.mjs";
|
|
4
4
|
//#region src/workflows/review/review.ts
|
|
5
5
|
/**
|
|
6
6
|
* Pure review of everything since `reviewBase`: a green gate, the quality
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { agent, changesSince, glob, head, mark, read, refuse, removeScript, run, scope, vars } from "../flows.mjs";
|
|
2
|
-
import { a as flagShipped,
|
|
2
|
+
import { a as flagShipped, dt as defaults, ft as envDefaults, gt as flipProblems, i as skills, l as gate, mt as flagMentions, n as summary, p as FEEDBACK, r as access, t as steering, vt as requireFlag, y as coder } from "../chunks/reserved-DoHUII2D.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.";
|